iOS開發(fā)-簡單分析防線上crash

我們開發(fā)APP纳胧,雖然在極力避免出現(xiàn)線上crash,但是某些情況還是沒法把控霉撵,比如和后端約定好的數(shù)據(jù)格式磺浙,突然哪天給你換了,很容易導(dǎo)致crash徒坡。但是如果我們在任何地方都做防御性判斷撕氧,代碼會寫得特別難受。

之前看到有人開源了防止crash的代碼喇完,所以分析了下伦泥。這些方案主要利用runtime的方法交換和消息轉(zhuǎn)發(fā)來實(shí)現(xiàn),對那些容易引起crash的方法,添加判斷奄喂,或者在crash之后走消息轉(zhuǎn)發(fā)铐殃。

之前項(xiàng)目用到這個,NSObjectSafe就是這么一個開源庫跨新,只需要拖進(jìn)工程就可以起作用富腊。
NSObjectSafe代碼地址:github鏈接

比如,向NSArray插入一個nil域帐、獲取NSArray長度之外的元素等等赘被,從而導(dǎo)致APP奔潰。NSObjectSafe能有效避免這些奔潰肖揣。這些方案民假,基本都在load中交換這些容易引起crash的方法,并且添加判斷龙优,以此來避免異常羊异。簡化版就是下面這樣:

@implementation NSArray (Safe)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 沒內(nèi)容類型是__NSArray0 */
        swizzleInstanceMethod(NSClassFromString(@"__NSArray0"),  @selector(objectAtIndex:),  @selector(hookObjectAtIndex:));
        /* 有內(nèi)容obj類型才是__NSArrayI */
        swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),  @selector(objectAtIndex:),  @selector(hookObjectAtIndex:));
        .
        .//還有很多
        .
    });
}

- (id) hookObjectAtIndex:(NSUInteger)index {
    @synchronized (self) {
        if (index < self.count) {
            return [self hookObjectAtIndex:index];
        }
        SFAssert(NO, @"NSArray invalid index:[%@]", @(index));
        return nil;
    }
}

可以看到,這里面用了個+load 方法彤断、runtime的方法交換野舶。

那么 +load 到底是如何被調(diào)用的呢?

在 iOS 開發(fā)中宰衙,我們經(jīng)常會使用 +load 方法來做一些在 main 函數(shù)之前的操作平道,比如方法交換(Method Swizzle)等。

+load()方法的調(diào)用時機(jī)是這樣的:
1供炼、當(dāng)類或分類被添加到 Obj-C 運(yùn)行時的時候被調(diào)用一屋;可以實(shí)現(xiàn)該方法用來在加載時刻執(zhí)行特定類的操作。
2袋哼、動態(tài)加載和靜態(tài)鏈接都能將 load 消息發(fā)送到類和分類冀墨,但前提是新加載類或分類實(shí)現(xiàn)了要響應(yīng)的方法。
3先嬉、初始化的順序如下:
鏈接的所有框架(Framework)中全部的構(gòu)造器轧苫。
鏡像(Image)中所有的 +load 方法。
鏡像中所有的 C++ 靜態(tài)構(gòu)造器疫蔓,以及 C/C++ 的 attribute(constructor) 函數(shù)含懊。
框架中鏈接的所有構(gòu)造器。
4衅胀、類的 +load 方法在其所有父類的 +load 方法調(diào)用之后調(diào)用岔乔。
5、分類的 +load 方法在其主類的 +load 方法調(diào)用之后調(diào)用滚躯。

了解到 +load 在運(yùn)行時初始化加載鏡像時就會被調(diào)用雏门,使得可以有機(jī)會預(yù)先做很多事情嘿歌。但正是因?yàn)槠浼虞d的時機(jī)非常靠前茁影,如果在 +load 方法中做比較復(fù)雜且在主線程的操作宙帝,將會影響 App 啟動時間,降低用戶體驗(yàn)募闲。所以APP啟動優(yōu)化可以盡量把放到這個方法的任務(wù)往后放步脓,比如+(void)initialize中,他會在每個類初始化的時候調(diào)用一次浩螺。

runtime會在運(yùn)行時調(diào)用工程中的類的+load方法靴患,并且不需要類在代碼中被顯示import,所以有些第三方可拖進(jìn)工程就會起作用要出,而不需要顯示import鸳君。比如:IQKeyboad、NSObjectSafe...
從這里可以解開我之前的一個誤解:一直以為垃圾代碼(尤其是第三方庫)留在工程里面不管他患蹂,以為它不會起作用或颊,如果他里面實(shí)現(xiàn)了 +load 方法,還是可能會起作用的况脆,一旦出bug了饭宾,那叫一個難找批糟。

接著格了,看看runtime的方法交換:

+ (void)load{
    NSLog(@"UIViewController Hook load");
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method oriMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
        Method repMethod = class_getInstanceMethod([self class], @selector(xx_dealloc));
        method_exchangeImplementations(oriMethod, repMethod);
    });
}

- (void)xx_dealloc{
    NSLog(@"%@ dealloc",NSStringFromClass([self class]));
}

或者像這樣(根據(jù)系統(tǒng)版本修改字號):

@implementation UIFont (hook)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleClassMethod:@selector(systemFontOfSize:) withMethod:@selector(xx_systemFontOfSize:)];
    });
}
+ (UIFont *)xx_systemFontOfSize:(CGFloat)fontSize{
    NSString *version = [UIDevice currentDevice].systemVersion;
    if (version.doubleValue >= 11.0) {
        return [UIFont xx_systemFontOfSize:25.0f];
    } else {
        return [UIFont xx_systemFontOfSize:14.0f];
    }
}
@end

為什么xx_systemFontOfSize里面又調(diào)用了xx_systemFontOfSize?不會死循環(huán)么徽鼎?
嗯盛末,這里不會,因?yàn)榻粨Q了方法的IMP否淤,這里不細(xì)說悄但。

看NSObjectSafe源碼,hook那么多不常見的類石抡,為什么呢檐嚣?比如__NSArray0、__NSArrayI啰扛、__NSSingleObjectArrayI等等嚎京。

這些都是NSArray在某些情況下的實(shí)際類,NSArray在這里使用了類簇的模式隐解。

類簇是一種設(shè)計(jì)模式鞍帝,它包含了一組私有的具體的類,這些類繼承一個公開的抽象類煞茫,也即是基類帕涌,基類負(fù)責(zé)提供對外接口供調(diào)用者使用摄凡,具體類負(fù)責(zé)方法的真正實(shí)現(xiàn), 我們只需要調(diào)用基類提供的接口來實(shí)現(xiàn)相關(guān)功能蚓曼,而無需關(guān)心背后的具體實(shí)現(xiàn)細(xì)節(jié)亲澡。

在Cocoa中,許多類實(shí)際上是以類簇的方式實(shí)現(xiàn)的纫版,即它們是一群隱藏在通用接口之下的與實(shí)現(xiàn)相關(guān)的類谷扣。例如創(chuàng)建NSString對象時,實(shí)際上獲得的可能是NSLiteralString捎琐、NSCFString会涎、NSSimpleCString、NSBallOfString或者其他未寫入文檔的與實(shí)現(xiàn)相關(guān)的對象瑞凑。

NSNumber就是最常用的類簇實(shí)現(xiàn)的類之一末秃。


NSNumber類簇

這種模式的好處就是,可以隱藏私有類籽御,調(diào)用者只能使用基類暴露的API练慕,而無需關(guān)心里面是什么類,以及背后的具體實(shí)現(xiàn)細(xì)節(jié)技掏。即使將來添加更多的私有類铃将,對外的基類API也沒什么變化。

技術(shù)分享記錄~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哑梳,一起剝皮案震驚了整個濱河市劲阎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸠真,老刑警劉巖悯仙,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吠卷,居然都是意外死亡锡垄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門祭隔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來货岭,“玉大人,你說我怎么就攤上這事疾渴∏Ч幔” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵程奠,是天一觀的道長丈牢。 經(jīng)常有香客問我,道長瞄沙,這世上最難降的妖魔是什么己沛? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任慌核,我火速辦了婚禮,結(jié)果婚禮上申尼,老公的妹妹穿的比我還像新娘垮卓。我一直安慰自己,他們只是感情好师幕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布粟按。 她就那樣靜靜地躺著,像睡著了一般霹粥。 火紅的嫁衣襯著肌膚如雪灭将。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天后控,我揣著相機(jī)與錄音庙曙,去河邊找鬼。 笑死浩淘,一個胖子當(dāng)著我的面吹牛捌朴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播张抄,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼砂蔽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了署惯?” 一聲冷哼從身側(cè)響起左驾,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泽台,沒想到半個月后什荣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怀酷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗜闻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜕依。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琉雳,靈堂內(nèi)的尸體忽然破棺而出样眠,到底是詐尸還是另有隱情,我是刑警寧澤翠肘,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布檐束,位于F島的核電站,受9級特大地震影響束倍,放射性物質(zhì)發(fā)生泄漏被丧。R本人自食惡果不足惜盟戏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甥桂。 院中可真熱鬧柿究,春花似錦、人聲如沸黄选。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽办陷。三九已至貌夕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間民镜,已是汗流浹背蜂嗽。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殃恒,地道東北人植旧。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像离唐,于是被迫代替她去往敵國和親病附。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,092評論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b亥鬓、繼承完沪、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來控制對象的生命周期。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,089評論 0 10
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式熟呛,并簡要敘述宽档?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,140評論 0 12
  • 一庵朝、你在項(xiàng)目中用過 runtime 嗎吗冤?舉個例子。 a九府、Method Swizzling動態(tài)交換方法實(shí)現(xiàn)椎瘟,實(shí)則交換...
    寫代碼的小農(nóng)民閱讀 1,365評論 0 4
  • 2019年2月9日 星期六 多云 又睡過頭兒了,不知道為什么侄旬,一覺醒來就到了現(xiàn)在肺蔚!當(dāng)然,不排...
    佳依我心閱讀 155評論 0 2