iOS知識小集 第8期(2016.09.20)

今年的Apple發(fā)布會也開完了避矢,沒有什么太出彩的地方帽驯。不過廣受非議的iPhone 7依然大賣龟再。群里、微信里都是各種討論外加各種炫尼变,而我只能靜靜地看著利凑,等著公司的測試機(jī)了。

每次都感嘆時間過得快,總是有各種事情哀澈,這一晃又三個星期了牌借,哎。這期整理了之前的5個問題日丹,無規(guī)則無主題走哺,大伙慢慢看:

  1. block未判空導(dǎo)致的EXC_BAD_ACCESS崩潰;
  2. 多Target開發(fā);
  3. dispatch_sync導(dǎo)致死鎖;
  4. makeObjectsPerformSelector:;
  5. NSSetUncaughtExceptionHandler

block未判空導(dǎo)致的EXC_BAD_ACCESS崩潰

我們在調(diào)用block時,如果這個block為nil哲虾,則程序會崩潰,報類似于EXC_BAD_ACCESS(code=1, address=0xc)異常[32位下的結(jié)果择示,如果是64位束凑,則address=0x10]。如下圖所示栅盲,這個異常表示程序在試圖讀取內(nèi)存地址0xc的信息時出錯汪诉。

在定義一個block時,編譯器會在棧上創(chuàng)建一個結(jié)構(gòu)體谈秫,類似于圖2的結(jié)構(gòu)體扒寄。

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
}

block就是指向這個結(jié)構(gòu)體的指針。其中的invoke就是指向具體實現(xiàn)的函數(shù)指針拟烫。當(dāng)block被調(diào)用時该编,程序最終會跳轉(zhuǎn)到這個函數(shù)指針指向的代碼區(qū)。而當(dāng)block為nil時硕淑,程序就會試圖去讀取0xc地址的信息课竣,而這個地址什么都不會有(duff address),于是拋出一個segmentation fault置媳。在32位系統(tǒng)下于樟,之所以是0xc,是因為invoke前面的三個成員變量的大小正好是12拇囊。

所以我們在使用block時迂曲,應(yīng)該首先去判斷block是否為空。一種比較優(yōu)雅的寫法是:


!block ?: block()

參考

  1. Why do nil / NULL blocks cause bus errors when run?

多Target開發(fā)

在Xcode中寥袭,一個target表示工程中的一個product路捧,target用于組織product所需要的源文件、資源文件纠永、配置信息等鬓长。

在一些情況下,我們可以為一個工程設(shè)置多個target尝江,如:同時開發(fā)Lite版和正式版涉波;開發(fā)版本和發(fā)布版本需要不同配置;單工程構(gòu)建多個相似的App等等。如下圖所示啤覆。

這么做的好處是在共用一份代碼的情況下苍日,可以為不同的target配置不同的資源、信息等窗声,如不同的Info.plist, Build Setting, Build Phase配置等相恃,最后得到不同的product。

參考

  1. Xcode Target
  2. 猿題庫iOS客戶端的技術(shù)細(xì)節(jié)(一):使用多target來構(gòu)建大量相似App

dispatch_sync導(dǎo)致死鎖

dispatch_sync函數(shù)用于將一個block提交到隊列中同步執(zhí)行笨觅,直到block執(zhí)行完后拦耐,這個函數(shù)才會返回。

這里有一個潛在的問題见剩,如果我們在某個串行隊列中調(diào)用dispatch_sync杀糯,并將其block提交到這個串行隊列中執(zhí)行,則會引發(fā)死鎖苍苞。如下代碼所示固翰。

/ 死鎖
dispatch_queue_t queue = dispatch_queue_create("com.apple.test", NULL);
dispatch_async(queue, ^{

  dispatch_sync(queue, ^{
      NSLog(@"B");
  });

  NSLog(@"A");
});

其實還是很好理解,在com.apple.test這個串行隊列中羹呵,我們執(zhí)行一個task A骂际,在這個task A中,我們又向隊列提交了一個同步的task B冈欢。由于是串行隊列歉铝,task A在task B之前,所以task B的執(zhí)行依賴于task A的完成涛癌,而task B又包含在task A中犯戏,task A的完成依賴于task B的完成。這樣就成了一個死鎖拳话。

所以先匪,千萬不要在主隊列中這樣調(diào)用dispatch_sync,否則會導(dǎo)致主線程卡死弃衍。

當(dāng)然呀非,如果在并行隊列中這樣使用是沒有問題的,如下代碼所示镜盯,可以正常打印出B,A岸裙。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

  dispatch_sync(queue, ^{
      NSLog(@"B");
  });

  NSLog(@"A");
});

又或是dispatch_sync的目標(biāo)隊列不是當(dāng)前隊列,如下代碼所示速缆,也可以正常打印出B,A降允。

dispatch_queue_t queue1 = dispatch_queue_create("com.apple.test1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("com.apple.test2", NULL);
dispatch_async(queue1, ^{

  dispatch_sync(queue2, ^{
      NSLog(@"B");
  });

  NSLog(@"A");
});

我們在使用dispatch_sync提交task時,可以看到大部分情況下task是在dispatch_sync所在的上下文線程中執(zhí)行艺糜,而不管dispatch_sync指定的隊列是什么【串行或并行】剧董,如下代碼所示:

// 串行隊列
NSLog(@"%@", [NSThread currentThread]);     // <NSThread: 0x100303310>{number = 1, name = main}

dispatch_queue_t queue = dispatch_queue_create("com.apple.test", NULL);

dispatch_sync(queue, ^{
  NSLog(@"%@", [NSThread currentThread]);     // <NSThread: 0x100303310>{number = 1, name = main}
});
// 并行隊列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

  NSLog(@"%@", [NSThread currentThread]);     // <NSThread: 0x100505ea0>{number = 2, name = (null)}

  dispatch_queue_t queue = dispatch_queue_create("com.apple.test", NULL);

  dispatch_sync(queue, ^{
      NSLog(@"%@", [NSThread currentThread]);    // <NSThread: 0x100505ea0>{number = 2, name = (null)}
  });
});

官方文檔給我們的解釋是這么做的目的是為了優(yōu)化性能:

As an optimization, this function invokes the block on the current thread when possible幢尚。

我們需要了解的是隊列和線程并不是一回事。我們將任務(wù)以block的形式提交到隊列翅楼,然后由GCD來決定將隊列中的block分發(fā)到系統(tǒng)管理的線程池中的某個線程中去執(zhí)行尉剩。

參考

  1. dispatch_sync

makeObjectsPerformSelector:

遍歷一個數(shù)組的方法有幾種,for, forin, enumerateObjectsUsingBlock:方法∫汶現(xiàn)在用得比較多的可能是enumerateObjectsUsingBlock:理茎,它能很方便地讓我們獲取到數(shù)組中的元素及對應(yīng)的索引,然后根據(jù)這些信息做一些操作管嬉,如下代碼所示:

NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSInteger index = 0; index < 10; index++) {

  Test *t = [[Test alloc] init];
  t.index = index;
  [array addObject:t];
}

[array enumerateObjectsUsingBlock:^(Test *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  [obj test];
}];

不過皂林,如果在循環(huán)中,只是想調(diào)用元素的某一個方法蚯撩,則可以考慮使用makeObjectsPerformSelector:或者makeObjectsPerformSelector:withObject:式撼,這兩個方法會按元素的順序向數(shù)組中的每個元素發(fā)送Selector指定的消息。如下代碼所示:

NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSInteger index = 0; index < 10; index++) {

  Test *t = [[Test alloc] init];
  t.index = index;
  [array addObject:t];
}

[array makeObjectsPerformSelector:@selector(test)];
[array makeObjectsPerformSelector:@selector(testWithNumber:) withObject:@10];

當(dāng)然求厕,Selector不能是NULL,否則會拋NSInvalidArgumentException異常扰楼。大家如果熟悉runtime的話呀癣,應(yīng)該知道消息機(jī)制是如何處理調(diào)用不存在方法的。

NSSetUncaughtExceptionHandler

Foundation里面提供了一個NSSetUncaughtExceptionHandler函數(shù)弦赖,可以設(shè)置一個頂層異常處理函數(shù)项栏,讓我們在程序發(fā)生異常并終止前,有最后的機(jī)會來捕獲并輸出異常信息蹬竖,如下代碼所示沼沈。

void UncaughtExceptionHandler(NSException *exception) {

    NSArray *symbols = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];

    NSLog(@"reason = %@", reason);
    NSLog(@"name = %@", name);
    NSLog(@"symbols = %@", symbols);
}
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);

    return YES;
}
@end

這個函數(shù)的參數(shù)是一個函數(shù)指針,指向的函數(shù)其簽名是:void NSUncaughtExceptionHandler(NSException *exception)币厕×辛恚可以看到這個函數(shù)有參數(shù)是一個NSException對象,通過這個對象我們就可以獲取到異常的信息旦装。假定發(fā)生數(shù)組越界異常時页衙,會有如下輸出。

2016-09-20 11:55:36.719 Test111[5548:199035] reason = *** -[__NSSingleObjectArrayI objectAtIndex:]: index 10 beyond bounds [0 .. 0]
2016-09-20 11:55:36.720 Test111[5548:199035] name = NSRangeException
2016-09-20 11:55:36.720 Test111[5548:199035] symbols = (
    0   CoreFoundation                      0x0000000106cef34b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010675021e objc_exception_throw + 48
    2   CoreFoundation                      0x0000000106d47bdf -[__NSSingleObjectArrayI objectAtIndex:] + 111
    3   Test111                             0x000000010617d87b -[AppDelegate application:didFinishLaunchingWithOptions:] + 235
    4   UIKit                               0x000000010710968e -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 290
    5   UIKit                               0x000000010710b013 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4236
    6   UIKit                               0x00000001071113b9 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
    7   UIKit                               0x000000010710e539 -[UIApplication workspaceDidEndTransaction:] + 188
    8   FrontBoardServices                  0x000000010a2ee76b __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
    9   FrontBoardServices                  0x000000010a2ee5e4 -[FBSSerialQueue _performNext] + 189
    10  FrontBoardServices                  0x000000010a2ee96d -[FBSSerialQueue _performNextFromRunLoopSource] + 45
    11  CoreFoundation                      0x0000000106c94311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    12  CoreFoundation                      0x0000000106c7959c __CFRunLoopDoSources0 + 556
    13  CoreFoundation                      0x0000000106c78a86 __CFRunLoopRun + 918
    14  CoreFoundation                      0x0000000106c78494 CFRunLoopRunSpecific + 420
    15  UIKit                               0x000000010710cdb6 -[UIApplication _run] + 434
    16  UIKit                               0x0000000107112f34 UIApplicationMain + 159
    17  Test111                             0x000000010617db4f main + 111
    18  libdyld.dylib                       0x000000010928a68d start + 1
    19  ???                                 0x0000000000000001 0x0 + 1
)

不過這個函數(shù)有效范圍局限于異常阴绢,還有很多錯誤是無法處理的店乐,如EXC_BAD_ACCESS內(nèi)存訪問錯誤,這類錯誤拋出的是Signal呻袭,需要專門做Signal處理眨八。

小結(jié)

Crash始終是我們開發(fā)最大最頭疼的問題,總會有各種各樣的Crash情況出現(xiàn)左电×啵看著Fabric里面長長的Crash列表页响,總是很傷感的。我們的成長史也是一部和Bug戰(zhàn)斗的斗爭史伏穆,自己寫的Bug拘泞,熬夜也要把它們搞完。繼續(xù)戰(zhàn)斗吧枕扫,Bug君陪腌。


南峰子的技術(shù)博客

歡迎關(guān)注我的微信公眾號:iOS知識小集,掃掃左邊站點概覽里的二維碼就OK了。對了伞辛,還有微博:@南峰子_老驢弓坞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市强岸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砾赔,老刑警劉巖蝌箍,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異暴心,居然都是意外死亡妓盲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門专普,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悯衬,“玉大人,你說我怎么就攤上這事檀夹〗畲郑” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵炸渡,是天一觀的道長娜亿。 經(jīng)常有香客問我,道長偶摔,這世上最難降的妖魔是什么暇唾? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮辰斋,結(jié)果婚禮上策州,老公的妹妹穿的比我還像新娘。我一直安慰自己宫仗,他們只是感情好够挂,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藕夫,像睡著了一般孽糖。 火紅的嫁衣襯著肌膚如雪枯冈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天办悟,我揣著相機(jī)與錄音尘奏,去河邊找鬼。 笑死病蛉,一個胖子當(dāng)著我的面吹牛炫加,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铺然,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼俗孝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了魄健?” 一聲冷哼從身側(cè)響起赋铝,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沽瘦,沒想到半個月后革骨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡析恋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年苛蒲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绿满。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窟扑,靈堂內(nèi)的尸體忽然破棺而出喇颁,到底是詐尸還是另有隱情,我是刑警寧澤嚎货,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布橘霎,位于F島的核電站,受9級特大地震影響殖属,放射性物質(zhì)發(fā)生泄漏姐叁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一洗显、第九天 我趴在偏房一處隱蔽的房頂上張望外潜。 院中可真熱鬧,春花似錦挠唆、人聲如沸处窥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滔驾。三九已至谒麦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哆致,已是汗流浹背绕德。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留摊阀,地道東北人耻蛇。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像驹溃,于是被迫代替她去往敵國和親城丧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 程序中同步和異步是什么意思豌鹤?有什么區(qū)別亡哄? 解釋一:異步調(diào)用是通過使用單獨的線程執(zhí)行的。原始線程啟動異步調(diào)用布疙,異步調(diào)...
    風(fēng)繼續(xù)吹0閱讀 1,027評論 1 2
  • GCD (Grand Central Dispatch) :iOS4 開始引入蚊惯,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,328評論 0 2
  • iOS中GCD的使用小結(jié) 作者dullgrass 2015.11.20 09:41*字?jǐn)?shù) 4996閱讀 20199...
    DanDanC閱讀 820評論 0 0
  • 一灵临、GCD的API 1. Dispatch queue 在執(zhí)行處理時存在兩種Dispatch queue: 等待現(xiàn)...
    doudo閱讀 498評論 0 0
  • 又是一年將盡時截型。年底總要搞點大事情, 不 然 都 沒 法 安 心 過 年儒溉。 盤點16年宦焦,大事不斷,小事新鮮顿涣, 1月...
    酒莊惠小九閱讀 397評論 0 0