iOS - 常見問題的整理(一)

一.通知

對于通知,大家想必都不陌生,它是一個單例,允許當事件發生時通知一些對象,讓我們在低程度耦合的情況下,來達到通信的目的。

通知的優勢:
1.不需要編寫太多代碼,實現比較簡單
2.對于一個發出的通知,可以多個對象作出反應,即是說通知是一對多的形式

通知的缺點:
1.在編譯期不會檢查通知是否能夠被觀察者正確處理
2.在釋放注冊的對象時,需要在通知中心取消注冊
3.在調試應用時,難以跟蹤程序
4.發出通知后,不能夠從觀察者那里獲取任何反饋信息

通知的基本實現:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    NSLog(@"注冊通知 - %@",[NSThread currentThread]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
    NSLog(@"發送通知完成 - %@",[NSThread currentThread]);

}

- (void)test {
    NSLog(@"接收到通知 - %@",[NSThread currentThread]);
    sleep(3);
}

打印結果:

2017-06-13 16:53:01.040 通知的基本使用[24531:3283934] 注冊通知 - <NSThread: 0x600000079c80>{number = 1, name = main}
2017-06-13 16:53:10.334 通知的基本使用[24531:3283934] 接收到通知 - <NSThread: 0x600000079c80>{number = 1, name = main}
2017-06-13 16:53:13.335 通知的基本使用[24531:3283934] 發送通知完成 - <NSThread: 0x600000079c80>{number = 1, name = main}

注意打印結果:在test方法執行完畢之后,才會打印發送完成的log。

如果在子線程發送通知:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
    NSLog(@"注冊通知 - %@",[NSThread currentThread]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        
        NSNotification *notification = [NSNotification notificationWithName:@"test"
                                                                     object:nil];
        // NSPostASAP是接收不到通知的 要使用NSPostNow
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostNow];
        NSLog(@"發送通知完成 - %@",[NSThread currentThread]);
        });
}

- (void)test {
    NSLog(@"接收到通知 - %@",[NSThread currentThread]);
    sleep(3);
}

打印結果:

2017-06-13 17:05:01.133 通知的基本使用[25191:3296062] 注冊通知 - <NSThread: 0x608000076440>{number = 1, name = main}
2017-06-13 17:05:02.423 通知的基本使用[25191:3296125] 接收到通知 - <NSThread: 0x608000267980>{number = 3, name = (null)}
2017-06-13 17:05:05.523 通知的基本使用[25191:3296125] 發送通知完成 - <NSThread: 0x608000267980>{number = 3, name = (null)}

得出結論:接收通知的線程和發送通知的線程是一樣的,如果在實際開發過程中,我們是在子線程中發送通知的,在接收到通知之后,需要刷新UI等操作,一定要回到主線程。

- (void)viewDidLoad {
    [super viewDidLoad];
     _observe = [[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
       NSLog(@"接收到通知 - %@",[NSThread currentThread]);
        sleep(3);   
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
        NSLog(@"發送通知完成 - %@",[NSThread currentThread]);
        });
}

打印結果:

2017-06-13 18:21:38.367 通知的基本使用[29365:3382047] 接收到通知 - <NSThread: 0x600000063d80>{number = 1, name = main}
2017-06-13 18:21:41.368 通知的基本使用[29365:3382100] 發送通知完成 - <NSThread: 0x600000071bc0>{number = 3, name = (null)}

得出結論:使用NSOperationQueue可以讓接收通知的線程和發送通知的線程不一樣,讓接收通知的線程在主線程,就可以刷新UI等操作了。

二.Xcode何時會報unrecognized selector 的錯誤

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    WWPerson *person = [[WWPerson alloc] init];
    [person test];
}

當向person發送test這個消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類的方法列表以及父類的方法列表里面找相應的方法運行,如果在最頂層的父類中依然找不到相應的方法實現時,程序在運行時就會報unrecognized selector sent to的錯誤并且崩潰,但是在此之前,objc的運行時給出了避免程序崩潰的三次機會。

  1. Method resolution
    objc運行時會調用+resolveInstanceMethod:或者+resolveClassMethod:,讓我們有機會提供一個函數實現而不導致程序崩潰,如果在這里面添加了函數,系統就會重新啟動一次消息發送的過程,否則就會移到下一步的消息轉發。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"test")) {
        /**
          class: 給哪個類添加方法
          SEL: 添加哪個方法
          IMP: 方法實現 => 函數 => 函數入口 => 函數名
          type: 方法類型:void用v來表示,id參數用@來表示,SEL用:來表示
         */
        class_addMethod(self, sel, (IMP)test, "v@:@");
        return YES;
    }else {
        return [super resolveClassMethod:sel];
    }
}

void test(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"測試 - WWPerson");
}

2.Fast forwarding
如果目標對象實現了-forwardingTargetForSelector:的方法,runtime就會調用這個方法,給我們一個機會把這個消息轉發給其他的對象,只要這個方法返回值不是nilself,整個消息發送的過程就會被重啟,這時發送的對象會變成我們返回的這個對象,否則就會移到下一步。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    WWTarget *target = [[WWTarget alloc] init];
    if ([target respondsToSelector:aSelector]) {
        return target; // 就會去調用WWTarget里面的test方法
    }else {
        return [super forwardingTargetForSelector: aSelector];
    }
}

3.Normal Fowarding
如果上面兩種方法都沒有被實現的話,就會來到第三步,這是runtime給我們最后一次避免崩潰的機會,首先它會-methodSignatureForSelector:來獲得函數的參數和返回值類型,如果返回值為nil,則runtime會發出-doesNotRecognizeSelector: 的消息,程序崩潰。如果返回了一個函數簽名,runtime會創建一個NSInvocation對象并發送-forwardInvocation:的消息給目標對象。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];// anInvocation里面保存的是selector/target/參數
    WWTarget *target = [[WWTarget alloc] init];
    if ([target respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:target];
    }    
}

如果上面的三步都沒有實現的話,就會調用-doesNotRecognizeSelector:,程序崩潰。

三.深拷貝和淺拷貝

深拷貝:內容拷貝,拷貝出來的對象和之前的對象的地址不一樣。
淺拷貝:指針拷貝,拷貝出來的對象和之前的對象的地址一樣。
直接上簡單示例比較好:

1.對可變對象進行 copy操作

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableString *mStr = [NSMutableString stringWithString:@"mStr"];
    NSString *copyStr = [mStr copy];
    [mStr appendString:@"123"];
    // mStr:0x60800007f440 - copyStr:0xa0000007274536d4
    NSLog(@"mStr:%p - copyStr:%p",mStr, copyStr);
}
 結論:1.對可變對象 進行 copy 操作是內容拷貝(深拷貝)
      2. copy 出來的copyStr是NSString類型的,如果對copyStr調用
NSMutableString的方法appendString是會崩潰的。

2.對可變對象進行mutableCopy操作

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableString *mStr = [NSMutableString stringWithString:@"mStr"];
    NSMutableString *mutableCopyStr =  [mStr mutableCopy];
    // str:0x608000260140 - mutableCopyStr:0x608000260440
    NSLog(@"str:%p - mutableCopyStr:%p",mStr, mutableCopyStr); 
}
 結論:1.對可變對象 進行 mutableCopy 操作是內容拷貝(深拷貝)
      2. mutableCopy 出來的mutableCopyStr是 NSMutableString 類型

3.對不可變對象進行copy操作

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *Str = [NSString stringWithFormat:@"Str"];
    NSString *copyStr = [Str copy];
    // str:0x10147e128 - copyStr:0x10147e128
    NSLog(@"str:%p - copyStr:%p",Str, copyStr);
}
結論:對不可變對象 進行 copy 操作是指針拷貝(淺拷貝)

4.對不可變對象進行mutableCopy操作

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *mStr = [NSString stringWithFormat:@"mStr"];
    NSMutableString *mutableCopyStr = [mStr mutableCopy];
   // str:0xa0000007274536d4 - mutableCopyStr:0x60800026a680
    NSLog(@"str:%p - mutableCopyStr:%p",mStr, mutableCopyStr);
}
 結論:1.對不可變對象 進行 mutableCopy操作 是內容拷貝(深拷貝)
      2.對mStr進行mutableCopy操作的mutableCopyStr是NSMutableString類型的

綜合以上所述:只有對 不可變對象進行copy操作是指針拷貝(淺拷貝),其他的都是內容拷貝(深拷貝)

四.調起鍵盤時,如何將鍵盤的“換行”變成“發送/完成”等

設置returnKeyType屬性即可,

    UIReturnKeyDefault,
    UIReturnKeyGo,// 前往
    UIReturnKeyGoogle,// google
    UIReturnKeyJoin,// 加入
    UIReturnKeyNext,// 下一步
    UIReturnKeyRoute,// 路線
    UIReturnKeySearch,// 搜索
    UIReturnKeySend, // 發送
    UIReturnKeyYahoo,// 搜索
    UIReturnKeyDone,// 完成
    UIReturnKeyEmergencyCall,// 緊急電話
    UIReturnKeyContinue NS_ENUM_AVAILABLE_IOS(9_0),// 繼續

五.viewDidLayoutSubviews和layoutSubviews的調用順序

viewDidLayoutSubviewslayoutSubviews前面調用
layoutSubviewsdrawRect :前面調用

2017-06-14 10:31:35.215 layoutSubviews等的調用順序[7357:98975] -[ViewController viewDidLoad]
2017-06-14 10:31:35.215 layoutSubviews等的調用順序[7357:98975] -[WWView initWithFrame:]
2017-06-14 10:31:35.220 layoutSubviews等的調用順序[7357:98975] -[ViewController viewWillLayoutSubviews]
2017-06-14 10:31:35.220 layoutSubviews等的調用順序[7357:98975] -[ViewController viewDidLayoutSubviews]
2017-06-14 10:31:35.220 layoutSubviews等的調用順序[7357:98975] -[WWView layoutSubviews]
2017-06-14 10:31:35.221 layoutSubviews等的調用順序[7357:98975] -[WWView drawRect:]

六.如何給分類動態添加屬性

#import "WWView+Tools.h"
#import <objc/runtime.h>

static char strKey;

@implementation WWView (Tools)

- (void)setDynamicStr:(NSString *)dynamicStr {
    /**
     id object: 需要給哪個對象的屬性賦值
     const void *key:屬性對應的key值
     id value:設置屬性的值為value
     objc_AssociationPolicy policy:關聯策略 枚舉值 一般選擇NONATOMIC
     */
    objc_setAssociatedObject(self, &strKey, dynamicStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)dynamicStr {
    return objc_getAssociatedObject(self, &strKey);
}

七.如何把一個view生成一張圖片,并且保存到本地

因為涉及到訪問相冊,所以先在plist文件里面添加NSPhotoLibraryUsageDescription允許應用程序訪問你的相冊

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // self.testView:要生成為圖片的view  
     UIGraphicsBeginImageContextWithOptions(self.testView.bounds.size, 0, [[UIScreen mainScreen] scale]);
        [self.testView.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        UIImageWriteToSavedPhotosAlbum(viewImage, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
    });
}

- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    if (!error) {
        NSLog(@"成功");
    }else {
        NSLog(@"失敗 - %@",error);
    }
}

八.如果服務器返回給我們的數據是包含標簽的,我們應該如何加載

// html_content:含有html標簽的富文本
1.用UILabel去加載
NSMutableAttributedString *attributeStr = [[NSMutableAttributedString alloc] initWithData:[html_content dataUsingEncoding:NSUnicodeStringEncoding] options:@{                                                                                                                                                                                     NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType                                                                                                                                                                                     }documentAttributes:nil error:nil];
self.contentLabel.attributedText = attributeStr;
PS:如果要改變文本的字體大小顏色等,一定要在這后面改

2.直接使用UIWebView去加載
 //設置字體大小為15,顏色rgb(124,181,236),邊距為15,并且圖片的寬度自動充滿屏幕,高度自適應
 NSString *html_content = @"要加載的html內容";
 NSString *htmls = [NSString stringWithFormat:@"<html> \n"
                           "<head> \n"
                           "<style type=\"text/css\"> \n"
                           "body {margin:15;font-size:15;color:%@}\n"
                           "</style> \n"
                           "</head> \n"
                           "<body>"
                           "<script type='text/javascript'>"
                           "window.onload = function(){\n"
                           "var $img = document.getElementsByTagName('img');\n"
                           "for(var p in  $img){\n"
                           " $img[p].style.width = '100%%';\n"
                           "$img[p].style.height ='auto'\n"
                           "}\n"
                           "}"
                           "</script>%@"
                           "</body>"
                           "</html>",@"rgb(124,181,236)", html_content];
 [self.contentWebView loadHTMLString:htmls baseURL:nil];

九.上傳到應用商店太慢的話,怎么解決

可以考慮使用Xcode - Open Developer Tool - Application Loader來解決

十.利用Application Loader打包提交到App Store可能會遇到錯誤:

The filename 未命名.ipa in the package contains an invalid character(s). The valid characters are:A-Z
,a-z,0-9,dash,period,underscore,but the name cannot start with a dash,period,or underscore.

解決辦法:在Archive之后的包不能再試中文名,把XXX.ipa改成英文名就搞定了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,198評論 30 471
  • 基礎 1. 為什么說Objective-C是一門動態的語言? 2. 講一下MVC和MVVM,MVP? 3. 為...
    波妞和醬豆子閱讀 3,353評論 0 46
  • 面試題參考1 : 面試題[http://www.cocoachina.com/ios/20150803/12872...
    江河_ios閱讀 1,755評論 0 4
  • 有人問我思念到極致是什么感覺。 我曾經發了句晚安給她 一晚上等著手機信息。 就是那種可怕的朦朦朧朧的意識 夢里...
    H夜已殤閱讀 603評論 0 0
  • 01 2013年宣布不再制作長篇動畫的宮崎駿導演近日又表露出復出的意愿,11月13日播出的NHK紀錄節目《永不停歇...
    郁文堂閱讀 2,218評論 19 73