開發(fā)小知識(二)

開發(fā)小知識(一)

開發(fā)小知識(二)

目錄

五十一箱季、關(guān)聯(lián)對象

關(guān)聯(lián)對象的 key

實際開發(fā)中一般使用屬性名作為key涯穷。

objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

另外一種方式是使用get方法的@selecor作為key。這里要知道 _cmd實際上等價于 @selector(getter)藏雏,兩者都是 SEL類型拷况。

objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// 隱式參數(shù) _cmd == @selector(getter)
objc_getAssociatedObject(obj, _cmd)
objc_getAssociatedObject(obj, @selector(getter))
關(guān)聯(lián)對象的懶加載
- (UIView *) testView{
    UIView * testView = objc_getAssociatedObject(self, _cmd);
    if (! testView) {
        testView = [[UIView alloc]init];
        objc_setAssociatedObject(self, _cmd, testView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return testView;
}

五十二、TCP 面向連接的本質(zhì)是什么?TCP 和 UDP 的區(qū)別赚瘦?

一般面試的時候問UDP和TCP這兩個協(xié)議的區(qū)別粟誓,大部分人會回答,TCP 是面向連接的起意,UDP 是面向無連接的鹰服。什么叫面向連接,什么叫無連接呢揽咕?在互通之前获诈,面向連接的協(xié)議會先建立連接。例如心褐,TCP 會三次握手舔涎,而 UDP 不會。為什么要建立連接呢逗爹?所謂的建立連接亡嫌,是為了在客戶端和服務(wù)端維護連接,而建立一定的數(shù)據(jù)結(jié)構(gòu)來維護雙方交互的狀態(tài)掘而,用這樣的數(shù)據(jù)結(jié)構(gòu)來保證所謂的面向連接的特性挟冠。

為了維護這個連接,雙方都要維護一個狀態(tài)機袍睡,在連接建立的過程中知染,雙方的狀態(tài)變化狀態(tài)如下。最初斑胜,客戶端和服務(wù)端都處于 CLOSED 狀態(tài)控淡。首先,服務(wù)端處于 LISTEN 狀態(tài)止潘,主要為了主動監(jiān)聽某個端口掺炭。客戶端主動發(fā)起連接 SYN凭戴,變?yōu)?SYN-SENT 狀態(tài)涧狮。然后,服務(wù)端收到發(fā)起的連接么夫,返回 SYN者冤,并且 ACK 客戶端的 SYN,之后處于 SYN-RCVD 狀態(tài)档痪∩娣悖客戶端收到服務(wù)端發(fā)送的 SYN 和 ACK 之后,發(fā)送 ACK 的 ACK钞它,之后處于ESTABLISHED 狀態(tài)拜银,因為一發(fā)一收成功了殊鞭。服務(wù)端收到 ACK 的 ACK 之后,也同樣變?yōu)?ESTABLISHED 狀態(tài)尼桶。


另外操灿,TCP 是可以擁塞控制的。它意識到包丟棄了或者網(wǎng)絡(luò)的環(huán)境不好了泵督,就會根據(jù)情況調(diào)整自己的行為趾盐,看看是不是發(fā)快了,要不要發(fā)慢點小腊。UDP 就不會救鲤,應(yīng)用讓發(fā)就發(fā),從不考慮網(wǎng)絡(luò)狀況秩冈。

五十三本缠、高效安全讀寫方案

讀寫操作中為了保證線程安全可以為讀和寫操作都添加鎖。但是此種情況似乎有些浪費入问,往往都是因為寫操作會引發(fā)線程安全問題丹锹,而讀操作一般不會引發(fā)線程安全問題。為了優(yōu)化讀寫效率芬失,一般是允許同一時間有多個讀操作楣黍,但同一時間不能有多個寫操作,且同一時間不能既有讀操作又有寫操作棱烂,即所謂的多讀單寫租漂。針對該種情況,一般有兩種處理方法:讀寫鎖和異步柵欄函數(shù)颊糜。

讀寫鎖方案pthread_rwlock_t
@property (assign, nonatomic) pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);// 初始化鎖

- (void)read {
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)write{
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"%s", __func__);
    pthread_rwlock_unlock(&_lock);
}
- (void)dealloc{
    pthread_rwlock_destroy(&_lock);
}
異步柵欄函數(shù)方案

每次必須等前面所有讀操作執(zhí)行完之后哩治,才能執(zhí)行寫操作。數(shù)據(jù)的正確性主要取決于寫入操作芭析,只要保證寫入時锚扎,線程便是安全的吞瞪,即便讀取操作是并發(fā)的馁启,也可以保證數(shù)據(jù)的正確性。dispatch_barrier_async 使得操作在并發(fā)隊列里“有序進行”芍秆,保證了寫入操作的任務(wù)是在串行隊列里惯疙,即必須等所有讀操作執(zhí)行完畢后再執(zhí)行寫操作。注意這里的 隊列必須是dispatch_queue_create創(chuàng)建的同一個隊列 妖啥,如果dispatch_barrier_async中傳入的是全局并發(fā)隊列霉颠,該函數(shù)就等同于dispatch_async效果。

@property (strong, nonatomic) dispatch_queue_t queue;
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_async(self.queue, ^{
            [self read];
        });
        dispatch_barrier_async(self.queue, ^{
            [self write];
        });
    }


如上圖荆虱,隨著時間的推移有一堆讀和寫操作蒿偎,當執(zhí)行到 dispatch_barrier_async 時會在寫的兩邊加上屏障(柵欄)朽们,使其和讀操作隔離。

另外提示诉位,如果僅僅只是對寫操作加鎖骑脱,讀操作不做任何處理,并不能保證線程安全苍糠,僅對寫操作加鎖僅僅只能保證不會同時出現(xiàn)兩個或多個寫操作叁丧,并不能避免同一時刻既有寫操作又有讀操作。實現(xiàn)正在進行讀操作岳瞭,此時來了第一個寫操作拥娄,但是相關(guān)鎖并沒有加鎖,所以讀寫操作可同時進行瞳筏。

補充:對于線程安全方案稚瘾,除了加鎖之外,還可以借助串行隊列確保代碼執(zhí)行的順序姚炕,保證線程安全孟抗。

dispatch_queue_t writerRecordsQueue = dispatch_queue_create([@"serialQueue.yawei" UTF8String], DISPATCH_QUEUE_SERIAL);
- (void)clearRecords
{
    dispatch_async(writerRecordsQueue, ^{
        [self.records removeAllObjects];
    });
}

- (void)writeData:(id)data
{
    if (!data) {
        return;
    }
    dispatch_async(writerRecordsQueue, ^{
        [self.records addObject:data];
    });
}

五十四、死鎖

所謂死鎖钻心,通常指有兩個線程 T1 和 T2 都卡住了凄硼,并等待對方完成某些操作。T1 不能完成是因為它在等待 T2 完成捷沸。T2 也不能完成摊沉,因為在等待 T1 完成。于是大家都完不成痒给,就導致了死鎖(DeadLock)说墨,就類似一條比較窄的馬路有兩輛車相向而行,互相等著對方過了之后再過苍柏。

重要:sync 函數(shù)當前串行隊列 中添加任務(wù)會卡住當前線程尼斧,產(chǎn)生死鎖。這里要額外注意 當前串行隊列试吁。

案列一:

- (void)ViewDidLoad{
   NSLog(@"1");// 任務(wù)1
  dispatch_sync(dispatch_get_main_queue(),^{
      NSLog(@"2");// 任務(wù)2
  });
  NSLog(@"3");// 任務(wù)3
}
結(jié)合上述重要提示分析:

上述情況會產(chǎn)生死鎖棺棵。 ViewDidLoad 存在于主隊列,向主隊列 (當前串行隊列) 中添加了 NSLog(@"2"); 任務(wù)熄捍,主而隊列又是串行隊列烛恤,所以導致死鎖。

結(jié)合線程和隊列實際情況分析:

首先要知道線程和隊列的關(guān)系余耽。執(zhí)行代碼邏輯時缚柏,線程從隊列中取出任務(wù)并放入線程中執(zhí)行,當線程中對應(yīng)模塊執(zhí)行完畢時碟贾,隊列同時移除對應(yīng)模塊币喧。


述方法展開后轨域,主線程存在任務(wù) 1 、sync 杀餐、任務(wù)3疙挺,主隊列中原本僅存在viewDidLoad ,當主線程從任務(wù) 1 依次執(zhí)行到 sync 時怜浅,此時會往主隊列中追加任務(wù) 2 铐然。dispatch_sync 有個特點,要求立馬在當前線程執(zhí)行任務(wù)恶座。

  • 從主隊列方面看搀暑,按照隊列先進先進先出的原則,主隊列中的viewDidLoad 沒執(zhí)行完跨琳,任務(wù) 2 只能等待 viewDidLoad 執(zhí)行完畢再去執(zhí)行自点;
  • 從主線程方面來看,因為dispatch_sync要求立馬在當前線程執(zhí)行任務(wù)脉让,任務(wù) 3 只能等待桂敛。

如此一來造成一個循環(huán),任務(wù)3 要等同步線程中熱任務(wù)2執(zhí)行完才能執(zhí)行溅潜,而任務(wù) 2 排在任務(wù) 3 后面需要等待任務(wù) 3 執(zhí)行完 术唬,最終誰也無法執(zhí)行完形成死鎖」隼剑可另外參照下圖加深理解粗仓。

案列二:

- (void)ViewDidLoad{
   NSLog(@"1");// 任務(wù)1
  dispatch_ync(dispatch_get_main_queue(),^{
      NSLog(@"2");// 任務(wù)2
  });
  NSLog(@"3");// 任務(wù)3
}

dispatch_sync 改為 dispatch_asyncdispatch_async 不會要求立馬在當前線程執(zhí)行任務(wù)设捐,所以同案列一相比借浊,不存在任務(wù) 3 等待 dispatch_async 的情況,所以這里不會產(chǎn)生死鎖萝招。代碼執(zhí)行順序為任務(wù)1蚂斤、任務(wù)3、任務(wù)2槐沼。

案列三:

- (void)ViewDidLoad{
  dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{ // 0
        [self test];
    });
 }
- (void)test{
  NSLog(@"1");// 任務(wù)1
  dispatch_sync(dispatch_get_main_queue(),^{
      NSLog(@"2");// 任務(wù)2
  });
  NSLog(@"3");// 任務(wù)3
}

同案列一相比曙蒸,上述代碼放到子線程線程中執(zhí)行,而不是放在 viewDidLoad(主線程) 中執(zhí)行母赵。任務(wù)1逸爵、dispatch_sync、任務(wù)1當前所處隊列為 全局并發(fā)隊列凹嘲,而 dispatch_sync 是向 主隊列添加任務(wù),所以不會產(chǎn)生死鎖构韵。

案列四:

- (void)test{
    //手動創(chuàng)建的串行隊列
  dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ 
        NSLog(@"執(zhí)行任務(wù)1");
        //往當前串行隊列添加任務(wù)
        dispatch_sync(queue, ^{ 
            NSLog(@"執(zhí)行任務(wù)2");
        });
        NSLog(@"執(zhí)行任務(wù)3");
    });
}

無論 test 方法在任何地方調(diào)用都會產(chǎn)生死鎖周蹭,因為上述方法已經(jīng)滿足dispatch_sync當前串行隊列添加任務(wù)的條件趋艘,這里的當前串行隊列指手動創(chuàng)建的串行隊列 queue,同案列一相比凶朗,這里只是將主隊列替換為手動創(chuàng)建的串行隊列瓷胧。

案列五:

- (void)test{
    //手動創(chuàng)建的串行隊列1
  dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
//手動創(chuàng)建的串行隊列2
dispatch_queue_t queue2 = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ 
        NSLog(@"執(zhí)行任務(wù)1");
        //往當前串行隊列添加任務(wù)
        dispatch_sync(queue2, ^{ 
            NSLog(@"執(zhí)行任務(wù)2");
        });
        NSLog(@"執(zhí)行任務(wù)3");
    });
}

此種情況不會產(chǎn)生死鎖,因為這里有兩個串行隊列棚愤,任務(wù)1搓萧、dispatch_sync、任務(wù)1當前所處隊列為 queue1 串行隊列宛畦,而 dispatch_sync 是向 queue2 串行隊列添加任務(wù)瘸洛。此種情況同案列三類似。

五十五次和、如何理解代理和協(xié)議反肋?

實際面試過程中有問到應(yīng)試者對協(xié)議和代理的理解,個別應(yīng)試者只知道代理和協(xié)議的用法踏施,連協(xié)議和代理的意義都說不清楚石蔗。舉個簡單的例子:一位導演很忙,因為他要把主要精力放到電影創(chuàng)作上畅形。因此需要找代理人把重要的瑣事分擔出去养距,或者說把重要的瑣事讓”代理人”去做。其中的代理人就是代碼中代理日熬,協(xié)議主要是規(guī)定了代理人要做的事铃在。 協(xié)議的用處還有很多,可看看此篇文章碍遍。

五十六定铜、MVP && MMVM

MVP

MVP 同 MVC 相比,本質(zhì)上是將 Controller 的職責給分離出去怕敬,按照功能和業(yè)務(wù)邏輯劃分為若干個 Presenter揣炕。Controller 中引入 Presenter ,Presenter 中同樣也引入 Controller东跪,Presenter 中處理各種業(yè)務(wù)邏輯畸陡,必要的時候再通過代理或 block 等形式回傳到 Controller 中。要注意虽填,為了避免循環(huán)引用 Presenter 要弱引用 Controller丁恭。

@interface ViewController ()
@property (strong, nonatomic) Presenter *presenter;
@end
@interface Presenter()
@property (weak, nonatomic) UIViewController *controller;
@end
MVVM

MVVM 總的來說和 MVP 非常類似,唯一不同點在于 View 和 ViewModel 雙向綁定斋日。實際開發(fā)通常是 Controller 中引入 ViewModel, ViewModel 中引入 Model牲览,ViewModel 中會進行網(wǎng)絡(luò)請求并進行數(shù)據(jù)處理邏輯。View 中會引入 ViewModel 給 View 設(shè)置內(nèi)容恶守,并且 View 還會監(jiān)聽 ViewModel 的變化第献,當 ViewModel 數(shù)據(jù)變化時贡必,通過監(jiān)聽更新 View 上對應(yīng)內(nèi)容,實現(xiàn)雙向綁定庸毫。因為 UI 的操作事件中可以動態(tài)改變模型仔拟,但是模型的改變不是很直接的體現(xiàn)到界面上,所以通常需要在 View 中監(jiān)聽 ViewModel 的變化飒赃。這種監(jiān)聽也可以通過監(jiān)聽實現(xiàn)利花,可以通過 RAC 實現(xiàn),但是 RAC 過重载佳,有一定的學習和維護成本炒事。建議使用 KVOController 實現(xiàn)這種監(jiān)聽,如下一段代碼是 View 中引入 ViewModel 刚盈,重寫 ViewModel 的 set 方法羡洛,并監(jiān)聽 ViewModel 的變化刷新 UI 。筆者認為沒有絕對好的架構(gòu)模式藕漱,適合特定業(yè)務(wù)場景的架構(gòu)模式才是好的架構(gòu)欲侮。MVVM 特別適合那種模型和視圖雙向反饋較多的場景,比如列表頁面的選中和非選中狀態(tài)肋联,通過改變 ViewModel 很輕松就能實現(xiàn)數(shù)據(jù)和界面的統(tǒng)一威蕉。 但是對于一般的業(yè)務(wù)場景而言(雙向反饋較少的場景),MVVM 同 MVC 相比處理能拆分 Controller 的業(yè)務(wù)邏輯之外橄仍,貌似也沒太多的優(yōu)點韧涨,反而會增加調(diào)試的難度。假設(shè)出現(xiàn)一些 bug 侮繁,該 bug 可能源于視圖也可能源于 ViewModel虑粥,會增加 bug 定位的難度。

- (void)setViewModel:(ViewModel *)viewModel{
    _viewModel = viewModel;
    __weak typeof(self) waekSelf = self;
    [self.KVOController observe:viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
        waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
    }];
}

五十七宪哩、簡單工廠和工廠模式

簡單工廠和工廠模式都屬于類創(chuàng)建型模式娩贷。

簡單工廠模式

簡單工廠主要有三個部分組成:

  • 抽象產(chǎn)品:抽象產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對象的父類,負責聲明所有產(chǎn)品實例所共有的公共接口锁孟。
  • 具體產(chǎn)品:具體產(chǎn)品是工廠所創(chuàng)建的所有產(chǎn)品對象類彬祖,它以自己的方式來實現(xiàn)其共同父類聲明的接口。
  • 工廠類:實現(xiàn)創(chuàng)建所有產(chǎn)品實例的邏輯品抽。
//抽象產(chǎn)品
//Operate.h文件
@interface Operate : NSObject
@property(nonatomic,assign)CGFloat numOne;
@property(nonatomic,assign)CGFloat numTwo;
- (CGFloat)getResult;
@end
//Operate.m文件
@implementation Operate
- (CGFloat)getResult{
    return 0.0;
}
@end
//具體產(chǎn)品1
//OperateAdd.m文件
@implementation OperateAdd
- (CGFloat)getResult{
    return self.numOne + self.numTwo;
}
@end
//具體產(chǎn)品2
//OperateSub.m文件
@implementation OperateSub
- (CGFloat)getResult{
    return self.numOne - self.numTwo;
}
@end

//工廠類
//OperateFactory.h文件
@class Operate;
@interface OperateFactory : NSObject
+ (Operate *)createOperateWithStr:(NSString *)str;
@end

//OperateFactory.m文件
@implementation OperateFactory
+ (Operate *)createOperateWithStr:(NSString *)str{
    if ([str isEqualToString:@"+"]) {
        OperateAdd *operateAdd = [[OperateAdd alloc] init];
        return operateAdd;
    }else if ([str isEqualToString:@"-"]){
        OperateSub *operateSub = [[OperateSub alloc] init];
        return operateSub;
    }else{
        return [[Operate alloc]init];
    }
}
@end
//使用
- (void)simpleFactoryTest{
    Operate *operate = [OperateFactory createOperateWithStr:@"+"];
    operate.numOne = 1;
    operate.numTwo = 2;
    NSLog(@"%f",[operate getResult]);
}

優(yōu)點:最大的優(yōu)點在于工廠類中包含了必要的判斷邏輯储笑,根據(jù)客戶端的選擇條件動態(tài)實例化相關(guān)的類,對于客戶端而言去除了與具體產(chǎn)品的依賴圆恤。有了簡單工廠類后突倍,客戶端在使用的時候只需要傳入“+” 或“-”即可,使用上相對來說簡單了很多。
缺點: 試想此時如果想在上述例子的基礎(chǔ)上增加乘法或除法操作赘方,除了增加相應(yīng)的子類之外烧颖,開發(fā)人員還需要在工廠類中改寫 if else 分支弱左,至少要更改兩處地方窄陡。顯然,工廠類的改動違背了開放-封閉原則(對擴展是開放的拆火,對更改是封閉的)跳夭。正因如此,才出現(xiàn)了所謂的工廠模式们镜,工廠模式僅僅需要添加新的具體產(chǎn)品和新的具體工廠就能實現(xiàn)币叹,原有代碼無需改動。

筆者在實際開發(fā)過程中使用過簡單工廠模式模狭,具體說來:UICollectionView上有很多可動態(tài)配置的模塊颈抚,本地代碼提前寫好不同的模塊,然后根據(jù)后端接口返回的數(shù)據(jù)所包含的不同模塊標志嚼鹉,用工廠類動態(tài)創(chuàng)建不同的模塊贩汉,從而實現(xiàn)模塊的動態(tài)配置。每個模塊實際是一個 UICollectionViewCell 锚赤,它們統(tǒng)一繼承一個基類匹舞,基類中包含一個統(tǒng)一渲染的方法,由于各個不同模塊的基本參數(shù)配置一直,所以比較適合走統(tǒng)一抽象渲染接口。另外厚掷,類簇是簡單工廠的應(yīng)用如:NSNumber 的工廠方法傳入不同類型的數(shù)據(jù)屋吨,則會返回不同數(shù)據(jù)所對應(yīng)的 NSNumber 的子類。

工廠模式

工廠模式主要由四部分組成勤揩。

  • 抽象產(chǎn)品:同簡單工廠。
  • 具體產(chǎn)品:同簡單工廠。
  • 抽象工廠:聲明具體工廠的創(chuàng)建產(chǎn)品的接口括丁。
  • 具體工廠:負責創(chuàng)建特定的產(chǎn)品,每一個具體產(chǎn)品對應(yīng)一個具體工廠零如。

上述三個抽象產(chǎn)品和具體產(chǎn)品類無變化躏将,即 Operate、OperateAdd 和 OperateSub 三個類無變化考蕾。

//抽象工廠
//OperationFactoryProtocol協(xié)議
@class Operate;
@protocol OperationFactoryProtocol <NSObject>
+ (Operate *)createOperate;
@end
//具體工廠1
//AddFactory.h文件
@interface AddFactory : NSObject<OperationFactoryProtocol>
@end
//AddFactory.m文件
@implementation AddFactory
+ (Operate *)createOperate{
    return [[OperateAdd alloc]init];
}
@end
//具體工廠2
//SubFactory.h文件
@interface SubFactory : NSObject<OperationFactoryProtocol>
@end
//SubFactory.m文件
@implementation SubFactory
+ (Operate *)createOperate{
    return [[OperateSub alloc]init];
}
@end

優(yōu)點

  • 工廠模式相比簡單工廠而言祸憋,在擴展新的具體產(chǎn)品時候代碼改動更小。
  • 用戶只需要關(guān)心其所需產(chǎn)品對應(yīng)的具體工廠是哪一個即可肖卧,不需要關(guān)心產(chǎn)品的創(chuàng)建細節(jié)蚯窥,也不需要知道具體產(chǎn)品類的類名。
    缺點
  • 當系統(tǒng)中加入新產(chǎn)品時,除了需要提供新的產(chǎn)品類之外拦赠,還要提供與其對應(yīng)的具體工廠類巍沙。隨著類的個數(shù)增加,系統(tǒng)復(fù)雜度也會有所增加荷鼠。
  • 簡單工廠類只有一個工廠類句携,該工廠類可以創(chuàng)建多個對象;工廠模式中每個子類對應(yīng)一個工廠類允乐,每個工廠僅能創(chuàng)建一個對象矮嫉。

五十八、適配器模式概念及應(yīng)用

適配器設(shè)計模式數(shù)據(jù)接口適配相關(guān)設(shè)計模式牍疏。實際開發(fā)中有個場景特別使用適配器設(shè)計模式蠢笋,一個封裝好的視圖組件可能在工程中不同的地方使用到,但是不同的地方使用的數(shù)據(jù)模型并不相同鳞陨,此時可以借助對象適配器昨寞,創(chuàng)建新的適配器模型數(shù)據(jù),而不應(yīng)該在組件內(nèi)部引入不同的數(shù)據(jù)模型厦滤,依據(jù)類型值進行判斷援岩,使用不同模型的不同數(shù)據(jù)。如電商網(wǎng)站中的加減按鈕可能在不同的頁面中使用到馁害,但不同頁面依賴的數(shù)據(jù)模型不同窄俏,此種情況就特別適合使用適配器模式。
兩個模型類碘菜。

@interface DataModel : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *phoneNumber;
@property (nonatomic, strong)UIColor *lineColor;
@end

@interface NewDataModel : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *phoneNumber;
@end

適配器協(xié)議凹蜈。

@protocol BusinessCardAdapterProtcol <NSObject>
- (NSString *)name;
- (NSString *)phoneNumber;
@end

適配器類。

//.h 文件
@interface ModelAdapter : NSObject<BusinessCardAdapterProtcol>
@property (nonatomic, weak)id data;
- (instancetype)initWithData:(id)data;
@end
//.m 文件
- (instancetype)initWithData:(id)data{
    self = [super init];
    if (self) {
        self.data = data;
    }
    return self;
}
//根據(jù)類名適配
- (NSString *)name{
    NSString *name = nil;
    if ([self.data isMemberOfClass:[DataModel class]]) {
         DataModel *data = self.data;
         name = data.name;
    }else if ([self.data isMemberOfClass:[NewDataModel class]]){
        NewDataModel *data = self.data;
        name = data.name;
    }
    return name;
}
- (NSString *)phoneNumber{
    NSString *phoneNumber = nil;
    if ([self.data isMemberOfClass:[DataModel class]]) {
        DataModel *data = self.data;
        phoneNumber = data.phoneNumber;
    }else if ([self.data isMemberOfClass:[NewDataModel class]]){
        NewDataModel *data = self.data;
        phoneNumber = data.phoneNumber;
    }
    return phoneNumber;
}

視圖忍啸。

//.h 文件
@interface BusinessCardView : UIView
- (void)loadData:(id<BusinessCardAdapterProtcol>)data;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *phoneNumber;
@end

//.m 文件
- (void)loadData:(id<BusinessCardAdapterProtcol>)data{
    self.name = [data name];
    self.phoneNumber = [data phoneNumber];
}
- (void)setName:(NSString *)name{
    _name = name;
    _nameLabel.text = name;
}
- (void)setPhoneNumber:(NSString *)phoneNumber{
    _phoneNumber = phoneNumber;
    _phoneNumberLabel.text = phoneNumber;
}

使用仰坦。

- (void)viewDidLoad {
    [super viewDidLoad];
    // 創(chuàng)建UI控件
    cardView = [[BusinessCardView alloc] initWithFrame:CGRectMake(0, 0, 375, 667.5)];
    cardView.center = self.view.center;
    [self.view addSubview:cardView];
    // 初始化兩種不同d類型的模型
    model = [[DataModel alloc] init];
    model.name = @"測試一";
    model.phoneNumber = @"電話1";

    newmodel = [[NewDataModel alloc]init];
    newmodel.name = @"測試二";
    newmodel.phoneNumber = @"電話2";
    //設(shè)置初始數(shù)據(jù)
    BusinessCardAdapter *adapter = [[BusinessCardAdapter alloc] initWithData:model];
    [cardView loadData:adapter];
    UISwitch *btn = [[UISwitch alloc]initWithFrame:CGRectMake(50, 340, 50, 20)];
    [btn addTarget:self action:@selector(change:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:btn];
}
- (void)change:(UISwitch *)btn{
    //切換數(shù)據(jù)
    ModelAdapter *adapter;
    if (btn.on == YES) {
       adapter = [[ModelAdapter alloc] initWithData:newmodel];
    }else{
       adapter = [[ModelAdapter alloc] initWithData:model];
    }
    //cardView與適配器連接
    [cardView loadData:adapter];
}

五十九、外觀模式概念及應(yīng)用

外觀模式相對比較好理解计雌,主要為子系統(tǒng)中的一組接口提供一個統(tǒng)一的接口悄晃。外觀模式定義了一個更高層次的接口,這個接口使得這一子系統(tǒng)更加容易使用凿滤。以下情況下可以考慮使用外觀模式:

  • 設(shè)計初期階段妈橄,應(yīng)該有意識的將不同層分離,層與層之間建立外觀模式翁脆。
  • 開發(fā)階段眷蚓,子系統(tǒng)越來越復(fù)雜,增加外觀模式提供一個簡單的調(diào)用接口反番。
  • 維護一個大型遺留系統(tǒng)的時候沙热,可能這個系統(tǒng)已經(jīng)非常難以維護和擴展叉钥,但又包含非常重要的功能,為其開發(fā)一個外觀類篙贸,以便新系統(tǒng)與其交互投队。

說的再直白一些,外觀模式就相當于在客戶端和子系統(tǒng)中間加了一個中間層爵川。使用外觀模式可以使項目更好的分層敷鸦,增強了代碼的擴展性。另外雁芙,客戶端屏蔽了子系統(tǒng)組件轧膘,使客戶端和子系統(tǒng)之間實現(xiàn)了松耦合關(guān)系钞螟。即使將后來想替換子系統(tǒng)客戶端也無需改動兔甘。

六十、策略模式概念及應(yīng)用

策略模式由三部分組成:抽象策略鳞滨、具體策略以及引入策略的主體洞焙。實際開發(fā)中有一種場景特別適合使用策略模式,輸入框 UITextField 的輸入規(guī)則可以使用該設(shè)計模式拯啦,判斷是輸入電話號碼澡匪、郵箱等格式是否正確。
抽象策略:

//.h 文件
@interface InputValidator : NSObject
@property (strong, nonatomic)NSString *errorMessage;
- (BOOL)validateInput:(UITextField *)input;
@end
//.m 文件
@implementation InputValidator
- (BOOL)validateInput:(UITextField *)input {
    return NO;
}
@end

兩個具體策略:

//郵箱策略
@implementation EmailValidator
- (BOOL)validateInput:(UITextField *)input {
    if (input.text.length <= 0) {
        self.errorMessage = @"沒有輸入";
    } else {
        BOOL isMatch = [input.text isEqualToString:@"1214729173@qq.com"];
        if (isMatch == NO) {
            self.errorMessage = @"請輸入正確的郵箱";
        } else {
            self.errorMessage = nil;
        }
    }
    return self.errorMessage == nil ? YES : NO;
}
@end

//電話號碼策略
@implementation PhoneNumberValidator
- (BOOL)validateInput:(UITextField *)input {
    if (input.text.length <= 0) {
        self.errorMessage = @"沒有輸入";
    } else {
        BOOL isMatch = [input.text isEqualToString:@"15201488116"];
        if (isMatch == NO) {
            self.errorMessage = @"請輸入正確的手機號碼";
        } else {
            self.errorMessage = nil;
        }
    }
    return self.errorMessage == nil ? YES : NO;
}
@end

引入策略的主體:

//.h 文件
@interface CustomTextField : UITextField
//抽象的策略
@property (strong, nonatomic) InputValidator *validator;
//初始化
- (instancetype)initWithFrame:(CGRect)frame;
//驗證輸入合法性
- (BOOL)validate;
@end

//.m 文件
@implementation CustomTextField
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}
- (void)setup {
    UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, self.frame.size.height)];
    self.leftView = leftView;
    self.leftViewMode = UITextFieldViewModeAlways;
    self.font = [UIFont fontWithName:@"Avenir-Book" size:12.f];
    self.layer.borderWidth = 0.5f;
}
- (BOOL)validate {
    return [self.validator validateInput:self];
}
@end

外部使用:

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self initButton];
    [self initCustomTextFields];
}
- (void)initCustomTextFields {
    self.emailTextField = [[CustomTextField alloc] initWithFrame:CGRectMake(30, 80, Width - 60, 30)];
    self.emailTextField.placeholder = @"請輸入郵箱";
    self.emailTextField.delegate = self;
    self.emailTextField.validator = [EmailValidator new];
    [self.view addSubview:self.emailTextField];
    
    self.phoneNumberTextField = [[CustomTextField alloc] initWithFrame:CGRectMake(30, 80 + 40, Width - 60, 30)];
    self.phoneNumberTextField.placeholder = @"請輸入電話號碼";
    self.phoneNumberTextField.delegate = self;
    self.phoneNumberTextField.validator = [PhoneNumberValidator new];
    [self.view addSubview:self.phoneNumberTextField];
}
#pragma mark - 文本框代理
- (void)textFieldDidEndEditing:(UITextField *)textField {
    CustomTextField *customTextField = (CustomTextField *)textField;
    if ([customTextField validate] == NO) {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:customTextField.validator.errorMessage preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        }];
        [alertController addAction:alertAction];
        [self presentViewController:alertController animated:YES completion:nil];
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.view endEditing:YES];
}

六十一褒链、界面卡頓原因

屏幕成像的過程如下圖:



按照60FPS的刷幀率唁情,每隔16ms就會有一次 VSync 到來(垂直同步信號)。VSync 到來意味著要將 GPU 渲染好的數(shù)據(jù)拿出來顯示到屏幕上甫匹,但是下圖中紅色區(qū)域中甸鸟,由于CPU + GPU 的處理時間在 VSync 之后,所以此時紅色框右邊的時間段顯示的始終是上一幀的畫面兵迅,因此出現(xiàn)卡頓現(xiàn)象抢韭。所以實際開發(fā)中無論是 CPU 還是 GPU 消耗資源較多都可能造成卡頓現(xiàn)象。


六十二恍箭、[UIApplication sharedApplication].delegate.window&& [UIApplication sharedApplication].keyWindow的區(qū)別

參考此篇文章刻恭,實際開發(fā)中要格外留意 [UIApplication sharedApplication].keyWindow 的坑。

六十三扯夭、單例注意事項

創(chuàng)建單例的時候除了要考慮對象的唯一性和線程安全之外鳍贾,還要考慮alloc initcopymutableCopy 方法返回同一個實例對象交洗。關(guān)于allocWithZone可看此篇文章骑科。

+ (instancetype)sharedInstance {
    return [[self alloc] init];
}
- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}
//當執(zhí)行 `alloc` 的時候,系統(tǒng)會自動調(diào)用分配內(nèi)存地址的方法`allocWithZone:`藕筋。
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static LogManager * _sharedInstanc = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstanc = [super allocWithZone:zone];//最先執(zhí)行纵散,只執(zhí)行了一次
    });
    return _sharedInstanc;
}
-(id)copyWithZone:(struct _NSZone *)zone{
    return [LogManager sharedInstance];
}
-(id)mutableCopyWithZone:(NSZone *)zone{
    return [LogManager sharedInstance];
}

六十四梳码、性能優(yōu)化總結(jié)

待更新。伍掀。掰茶。。蜜笤。

六十五濒蒋、內(nèi)存區(qū)域

  • 1、棧:局部變量(基本數(shù)據(jù)類型把兔、指針變量)作用域執(zhí)行完畢之后沪伙,就會被系統(tǒng)立即收回,無需程序員管理(分配地址由高到低分配)县好。
  • 2围橡、堆:程序運行的過程中動態(tài)分配的存儲空間(創(chuàng)建的對象),需要主動申請和釋放缕贡。
  • 3翁授、BSS 段:沒有初始化的全局變量和靜態(tài)變量,一旦初始化就會從 BSS 段中收回掉晾咪,轉(zhuǎn)存到數(shù)據(jù)段中收擦。
  • 4、(全局區(qū))數(shù)據(jù)段:存放已經(jīng)初始化的全局變量和靜態(tài)變量谍倦,以及常量數(shù)據(jù)塞赂,直到程序結(jié)束才會被立即收回。
  • 5昼蛀、代碼段:程序編譯后的代碼內(nèi)容宴猾,直到結(jié)束程序才會被收回。

六十六曹洽、符號表

iOS 構(gòu)建時產(chǎn)生的符號表鳍置,是內(nèi)存地址、函數(shù)名送淆、文件名和行號的映射表税产。格式大概是:

<起始地址> <結(jié)束地址> <函數(shù)> [<文件名:行號>]

Crash 時的堆棧信息,全是二進制的地址信息偷崩。如果利用這些二進制的地址信息來定位問題是不可能的辟拷,因此我們需要將這些二進制的地址信息還原成源代碼種的函數(shù)以及行號,這時候符號表就起作用了阐斜。利用符號表將原始的 Crash 的二進制堆棧信息還原成包含行號的源代碼文件信息衫冻,可以快速定位問題。iOS 中的符號表文件(DSYM) 是在編譯源代碼后谒出,處理完 Asset Catalog 資源和 info.plist 文件后開始生成隅俘,生成符號表文件(DSYM)之后邻奠,再進行后續(xù)的鏈接、打包为居、簽名碌宴、校驗等步驟。

六十七蒙畴、指針和引用

在 C 和 OC 語言中贰镣,使用指針(Pointer)可以間接獲取、修改某個變量的值膳凝,C++中碑隆,使用引用(Reference)可以起到跟指針類似的功能。引用相當于是變量的別名蹬音,對引用做計算上煤,就是對引用所指向的變量做計算,在定義的時候就必須初始化祟绊,一旦指向了某個變量楼入,就不可以再改變從一而終。所以這也是存在的價值之一:比指針更安全牧抽、函數(shù)返回值可以被賦值。引用的本質(zhì)就是指針遥赚,只是編譯器削弱了它的功能扬舒,所以引用就是弱化了的指針。

六十八凫佛、static & const & extern


  • static修飾局部變量:讓局部變量永遠只初始化一次讲坎。將局部變量的本來分配在棧區(qū)改為分配在靜態(tài)存儲區(qū),靜態(tài)存儲區(qū)伴隨著整個應(yīng)用愧薛,也就延長了局部變量的生命周期晨炕。
  • static修飾全局變量:本來是在整個源程序的所有文件都可見,static修飾后毫炉,改為只在申明自己的文件可見瓮栗,即修改了作用域。

  • const:修飾變量主要強調(diào)變量是不可修改的瞄勾。const 修飾的是其右邊的值费奸,也就是 const 右邊的這個整體的值不能改變。
//如下代碼無法編譯通過
//const修飾str指針进陡,所以str指針的內(nèi)存地址無法改變愿阐,也即str指針不能改變內(nèi)存地址指向。
 NSString * const str = @"test";
 //該行代碼表示:str指針指向了其它的內(nèi)存
 str = @"123";

//const修飾 *str趾疚,也即str指針指向的內(nèi)存地址缨历,所以對修改str指針的指向無任何影響以蕴。
NSString const *str = @"test";
//該行代碼表示:str指針指向了其它的內(nèi)存
 str = @"123";

一般聯(lián)合使用static和const來定義一個只能在本文件中使用的,不能修改的變量辛孵。相對于用#define來定義的話舒裤,優(yōu)點就在于它指定了變量的類型。

//防止 reuseIdentifier 指針指向其它內(nèi)存
static NSString * const reuseIdentifier = @"reuseIdentifier";

  • extern:主要是用來引用全局變量觉吭,先在本文件中查找腾供,本文件中查找不到再到其他文件中查找。常把 extern 和 const 聯(lián)合使用在項目中創(chuàng)建一個文件鲜滩,這個文件中包含整個項目中都能訪問的全局常量伴鳖。

六十九、枚舉

枚舉的目的只是為了增加代碼的可讀性徙硅。iOS6 中引入了兩個宏來重新定義枚舉類型 NS_ENUM 與 NS_OPTIONS 榜聂,兩者在本質(zhì)上并沒有差別,NS_ENUM多用于一般枚舉, NS_OPTIONS 則多用于帶有移位運算的枚舉嗓蘑。

NS_ENUM
typedef NS_ENUM(NSInteger, Test){
    TestA = 0,
    TestB,
    TestC,
    TestD
};
NS_OPTIONS
typedef NS_OPTIONS(NSUInteger, Test) {
    TestA = 1 << 0,
    TestB = 1 << 1,
    TestC = 1 << 2,
    TestD = 1 << 3
};

使用按位或(|)為枚舉 變量test 同時賦值枚舉成員TestA须肆、TestBTestC桩皿。

Test test = TestA | TestB;
test |= TestC;

使用按位異或(^)為枚舉 變量 test 去掉一個枚舉成員 TestC豌汇。ps: 兩者相等為0,不等為1泄隔。

Test test = TestA | TestB | TestC;
test ^= TestC;

使用按位與(&)判斷枚舉 變量test 是否賦值了枚舉成員 TestA拒贱。

Test test = TestA | TestB;
if (test & TestA){
    NSLog(@"yes");
}else{
    NSLog(@"no");
}

七十、驗證碼的作用

待更新佛嬉。逻澳。。暖呕。

七十一斜做、幀率優(yōu)化

Color Blended Layers(red)

png 圖片是支持透明的,對系統(tǒng)性能也會有影響的。最好不要設(shè)置透明度湾揽,因為透明的圖層和其他圖層重疊在一塊的部分瓤逼,CPU 會做處理圖層疊加顏色計算,這種處理是比較消耗資源的钝腺。

Color Copied Images (cyan)

蘋果的 GPU 只解析 32bit 的顏色格式抛姑。
如果一張圖片,顏色格式不是 32bit 艳狐,CPU 會先進行顏色格式轉(zhuǎn)換定硝,再讓 GPU 渲染。 就算異步轉(zhuǎn)換顏色毫目,也會導致性能損耗蔬啡,比如電量增多诲侮、發(fā)熱等等。解決辦法是讓設(shè)計師提供 32bit 顏色格式的圖片箱蟆。圖片顏色科普文章:圖片的顏色深度/顏色格式(32bit,24bit,12bit)

Color Misaligned Images 像素對齊(yellow)

iOS設(shè)備上沟绪,有邏輯像素(point)和 物理像素(pixel)之分,像素對齊指的是物理像素對齊空猜,對齊就是像素點的值是整數(shù)绽慈。UI 設(shè)計師提供的設(shè)計稿標注以及中的 frame 是 邏輯像素。GPU在渲染圖形之前辈毯,系統(tǒng)會將邏輯像素換算成 物理像素坝疼。point 和 pixel 的比例是通過[[UIScreen mainScreen] scale] 來制定的。在沒有視網(wǎng)膜屏之前谆沃,1point = 1pixel钝凶;但是2x和3x的視網(wǎng)膜屏出來之后,1point = 2pixel 或 3pixel唁影。

邏輯像素乘以 2 或 3 得到整數(shù)值就像素對齊了耕陷,反之則像素不對齊。像素不對齊會導致 GPU 渲染時据沈,對沒對齊的邊緣進行插值計算哟沫,插值計算會有性能損耗。

原圖片大小和視圖控件大小不一致卓舵,圖片為了對應(yīng)在控件的相應(yīng)的位置就需要做一些計算南用,然后確定圖片的位置,該種情況也比較消耗資源掏湾。一般可以通過繪制指定尺寸大小、不透明的圖片來優(yōu)化性能肿嘲。

Color Off-screen Rendered (yellow)

cornerRadius 屬性只應(yīng)用于 layer 的背景色和邊線融击。將 masksToBounds 屬性設(shè)置為 YES 才能把內(nèi)容按圓角形狀裁剪。同時設(shè)置 cornerRadiusmasksToBounds = YES 雳窟,并且屏幕中同時顯示的圓角個數(shù)過多尊浪,就會明顯感覺到卡頓和跳幀,只是設(shè)置 cornerRadius 并不會觸發(fā)此種現(xiàn)象封救。當使用圓角拇涤,陰影,遮罩的時候誉结,圖層屬性的混合體被指定為在未預(yù)合成之前不能直接在屏幕中繪制鹅士,所以就需要屏幕外渲染被喚起。使用離屏渲染的時候會很容易造成性能消耗惩坑,因為在 OpenGL 里離屏渲染會單獨在內(nèi)存中創(chuàng)建一個屏幕外緩沖區(qū)并進行渲染掉盅,而屏幕外緩沖區(qū)跟當前屏幕緩沖區(qū)上下文切換是很耗性能的也拜。iOS9 之后系統(tǒng)設(shè)置圓角不再產(chǎn)生離屏渲染。設(shè)置 shadow***相關(guān)陰影屬性也會產(chǎn)生離屏渲染趾痘,解決方法是設(shè)置陰影路徑 shadowPath慢哈。

無法避免離屏渲染的時候可嘗試使用光柵化來進一步做優(yōu)化。光柵化是指將圖轉(zhuǎn)化為一個個柵格組成的圖象永票。shouldRasterize = YES 在其他屬性觸發(fā)離屏渲染的同時卵贱,會將光柵化后的內(nèi)容緩存起來,如果對應(yīng)的layer 及其 sublayers 沒有發(fā)生改變侣集,在下一幀的時候可以直接復(fù)用键俱,從而減少渲染的頻率。當使用光柵化時肚吏,可以在 Core Animation 開啟 Color Hits Green and Misses Red 來檢查該場景下光柵化操作是否是一個好的選擇方妖。綠色表示緩存被復(fù)用,紅色表示緩存在被重復(fù)創(chuàng)建罚攀。如果光柵化的層變紅得太頻繁那么光柵化對優(yōu)化可能沒有多少用處党觅,反之就可以開啟。

七十二斋泄、內(nèi)存數(shù)據(jù)擦除

敏感數(shù)據(jù)不想一直保留在內(nèi)存中杯瞻,可以通過特定的 API 擦除內(nèi)存中的數(shù)據(jù),比如 NSString:

@implementation NSString (MemoryClear)
/**
 內(nèi)存數(shù)據(jù)及時擦除
 */
-(void)memoryClearStirng{
    const char*string = (char *)CFStringGetCStringPtr((CFStringRef)self,CFStringGetSystemEncoding());
    memset(&string, 0, sizeof(self));
}
@end

七十三炫掐、找不到方法怎么辦

交叉替換消息轉(zhuǎn)發(fā)機制的 forwardingTargetForSelector: 方法魁莉。

@implementation NSObject (Exception)

+ (void)load {
    //防止外部手動調(diào)用load方法,固load方法中最好都要寫上dispatch_once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
           //交叉方法
           [objc_getClass("NSObject") swizzleMethod:@selector(forwardingTargetForSelector:) swizzledSelector:@selector(replace_forwardingTargetForSelector:)];
        }
    });
}
//替換后的消息轉(zhuǎn)發(fā)階段
- (id)replace_forwardingTargetForSelector:(SEL)aSelector{
    //先處理自身能處理的消息(這一塊的邏輯和一般的交叉方法有點區(qū)別:一般的交叉方法先處理異常募胃,再調(diào)用之前的方法旗唁,這里相反)
    NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
    if ([self respondsToSelector:aSelector] || signature) {
        return [self replace_forwardingTargetForSelector:aSelector];
    }
    //返回其他消息處理對象,并在內(nèi)部動態(tài)添加方法
    FakeForwardTargetObject *fakeTaget = [[FakeForwardTargetObject alloc] initWithSelector:aSelector];
    return fakeTaget;
}
@end

FakeForwardTargetObject 類痹束。

id fakeIMP(id sender,SEL sel,...){
    return nil;
}
@interface FakeForwardTargetObject : NSObject
- (instancetype)initWithSelector:(SEL)aSelector;
@end

@implementation FakeForwardTargetObject
- (instancetype)initWithSelector:(SEL)aSelector{
    if (self = [super init]) {
        if(class_addMethod([self class], aSelector, (IMP)fakeIMP, NULL)) {
            MCLog(@"add Fake Selector:[instance %@]", NSStringFromSelector(aSelector));
            NSString *string = [NSString stringWithFormat:@"[%s:%d行]",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],__LINE__];
             showExceptionAlert(string);
        }
    }
    return self;
}
@end

七十四检疫、卡頓代碼監(jiān)測原理

所謂的卡頓一般是在主線程做了耗時操作,卡頓監(jiān)測的主要原理是在主線程的 RunLoop 中添加一個 observer祷嘶,檢測從 即將處理Source(kCFRunLoopBeforeSources)即將進入休眠 (kCFRunLoopBeforeWaiting) 花費的時間是否過長屎媳。如果花費的時間大于某一個闕值,則認為卡頓论巍,此時可以輸出對應(yīng)的堆棧調(diào)用信息烛谊。具體可以參考此篇文章

七十五嘉汰、同時實現(xiàn) set & get

setget 方法單獨重寫任意一個方法都不會報錯丹禀,但是同時重寫會報錯。主要是因為重寫 getset 方法之后 @property 默認生成的 @synthesize 就不起作用,也就意味著對應(yīng)的類不會自動生成成員變量湃崩,解決方案是手動添加成員變量荧降。

七十六、main 中的 UIApplicationMain 函數(shù)

 int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
 }

UIApplicationMain 函數(shù)主要有以下兩個作用:

  • 創(chuàng)建一個應(yīng)用程序以及創(chuàng)建應(yīng)用程序代理
  • 建立一個事件循環(huán)來捕捉處理用戶的行為

UIApplicationMain 函數(shù)參數(shù)說明:

  • 1攒读、參數(shù) argcargv 是 C 標準的 main 函數(shù)的參數(shù)朵诫。其中, argc 表示參數(shù)個數(shù)薄扁;argv 表示參數(shù)指針,是指向指針的指針剪返,也可以替換為 char **argv
  • 2邓梅、principalClassName 是應(yīng)用程序?qū)ο笏鶎俚念愅衙ぃ擃惐仨毨^承自 UIApplication 類。如果所屬類字符串的值為 nil, UIKit 就缺省使用UIApplication 類日缨。
  • 3钱反、delegateClassName 是應(yīng)用程序類的代理類,該函數(shù)跟據(jù) delegateClassName 創(chuàng)建一個 delegate對象匣距,并將 UIApplication 對象中的 delegate 屬性設(shè)置為 delegate 對象面哥。

七十七、nil毅待、Nil尚卫、NULL、NSNull

  • object = nil 表示把這個對象釋放掉,稱為“空對象”尸红。對于這種空對象吱涉,所有關(guān)于 retain 的操作都會引起程序崩潰,例如字典添加鍵值或數(shù)組添加新原素等外里。
  • NSNullnil 的區(qū)別在于怎爵,nil 是一個空對象,已經(jīng)完全從內(nèi)存中消失了盅蝗,而如果想表達“我們需要有這樣一個容器疙咸,但這個容器里什么也沒有”的觀念時,就用到NSNull风科,稱之為值為空的對象NSNull 繼承自 NSObject乞旦,并且只有一個 null 類方法贼穆。這就說明 NSNull 對象擁有一個有效的內(nèi)存地址,所以在程序中對它的引用不會導致程序崩潰兰粉。
  • nilNil 在使用上是沒有嚴格限定的故痊,也就是說凡是使用 nil 的地方都可以用 Nil 來代替,反之亦然玖姑。
  • NULL就是典型 C 語言的語法愕秫,它表示一個空指針慨菱。如:int *ponit = NULL

七十八、iOS 系統(tǒng)結(jié)構(gòu)

待更新戴甩。符喝。。甜孤。

七十九协饲、鑰匙串訪問

UUID 保存到 KeyChain 里面,即使APP刪了再裝回來缴川,也可以從KeyChain中讀取回來茉稠,常第三方庫SSKeychain。使用keyChain Sharing還可以保證同一個開發(fā)商的所有程序針對同一臺設(shè)備能夠獲取到相同的不變的UDID把夸。但是刷機或重裝系統(tǒng)后 UUID 還是會改變而线。假如項目 2 想使用項目 1 的 Keychain 旬渠,項目 2 要開啟Keychain Sharing 且 Keychain Groups 要包含項目 1刹淌。

八十、動態(tài)規(guī)劃思路

動態(tài)規(guī)劃算法一般有兩種求解方式:1谨湘、自頂向下的備忘錄法 2谚鄙、自底向上各拷。比如斐波拉契數(shù)列(Fibonacci)問題中,使用動態(tài)規(guī)劃的思路解決問題闷营,可以避免類似遞歸解決方案的重復(fù)計算問題烤黍。保留計算結(jié)果,避免重復(fù)計算這也是動態(tài)規(guī)劃和分治策略的最大區(qū)別所在傻盟。

八十一速蕊、主線程更新 UI 原因

UI 必須放在主線程是因為 UIKit 為了提升性能,壓根沒加鎖娘赴,所以 UIKit 不是線程安全的规哲。如果不在主線程里面操作,會出現(xiàn)什么樣的 UI 诽表,誰也不敢保證唉锌。

八十二、設(shè)計原則

待更新竿奏。袄简。。泛啸。

八十三绿语、@selector、_cmd、SEL

@selector 是指向?qū)嶋H執(zhí)行的函數(shù)指針(function pointer)的一個C字符串吕粹。
_cmd 實際上等價于 @selector(getter)种柑,兩者都是 SEL類型,都是方法選擇器匹耕,用于在類結(jié)構(gòu)的方法分發(fā)表中搜索指定名字的方法實現(xiàn)/地址聚请。

objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)

// 隱式參數(shù) _cmd == @selector(getter)
objc_getAssociatedObject(obj, _cmd)
objc_getAssociatedObject(obj, @selector(getter))

八十四、Clang & LLVM & GCC

LLVM 架構(gòu)

LLVM 架構(gòu)

LLVM 架構(gòu)不同的前后端使用統(tǒng)一的中間代碼 LLVM Intermediate Representation (LLVM IR)泌神。如上圖良漱,如果需要支持一種新的編程語言(如C、Fortran欢际、Haskell)母市,只需要實現(xiàn)一個新的編譯器前端;如果需要支持支持一種新的硬件設(shè)備(如X86损趋、PowerPC患久、ARM),只需要實現(xiàn)一個新的編譯器后端即可浑槽。而傳統(tǒng)的變異架構(gòu)因為沒有LLVM IR蒋失,如果需要支持C、Fortran桐玻、Haskell三種語言和X86篙挽、PowerPC、ARM三種硬件镊靴,則需要九個編譯器(3*3 = 9铣卡,不區(qū)分前后端),而在 LLVM 架構(gòu)中之需要支持三種編譯器前端和三種編譯器后端即可偏竟。

Clang

Clang 是 LLVM中的一個子項目煮落,是基于 LLVM 架構(gòu)的C/C++/Objective-C編譯器前端。

LLVM && Clang

LLVM是構(gòu)架編譯器(compiler)的框架系統(tǒng)踊谋,以C++編寫而成蝉仇,可以處理C、C++殖蚕、OC等轿衔。在理解LLVM時,可以認為它包括了一個狹義的LLVM和一個廣義的LLVM睦疫。

  • 廣義的LLVM其實就是指整個LLVM編譯器架構(gòu)呀枢,包括了前端(Clang)、后端(LLVM)笼痛、優(yōu)化器、眾多的庫函數(shù)以及很多的模塊;
  • 狹義的LLVM其實就是聚焦于編譯器后端功能(代碼生成缨伊、代碼優(yōu)化摘刑、JIT等)的一系列模塊和庫。Clang 就是一個編譯前端工具刻坊,而 LLVM 則負責后端處理枷恕。


    Clang & LLVM
Clang為前端,LLVM為后端

GCC

GCC 是另一個知名工具 GCC(GNU Compile Collection)則是一個套裝谭胚,包攬了前后端的所有任務(wù)徐块,可以處理C、C++灾而。

Clang 相比于 GCC 優(yōu)勢:

  • Clang是一個高度模塊化開發(fā)的輕量級編譯器
  • 編譯速度快
  • 占用內(nèi)存小胡控、已與擴展(非常方便進行二次開發(fā))。

八十五旁趟、生命周期

面試過程中昼激,突然有人問到應(yīng)用生命周期,瞬間懵掉锡搜。平時開發(fā)很少涉及橙困,面試之前也沒看,自然不容易想起來耕餐。按照 App 操作凡傅,一般分為下面三種情況:

從非運行到前臺活躍:

  • [AppDelegate application:didFinishLaunchingWithOptions:]
  • [AppDelegate applicationDidBecomeActive:]

前臺活躍到退出:

  • [AppDelegate applicationWillResignActive:]
  • [AppDelegate applicationDidEnterBackground:]
  • [AppDelegate applicationWillTerminate:]

后臺到前臺:

  • [AppDelegate applicationWillEnterForeground:]
  • [AppDelegate applicationDidBecomeActive:]
順便補充下控制器生命周期:
1. initWithCoder:通過 nib 文件初始化時觸發(fā)
2. awakeFromNib:nib 文件被加載的時候肠缔,會發(fā)生一個awakeFromNib的消息到nib文件中的每個對象桩砰。     
3. loadView:開始加載視圖控制器自帶的 view
4. viewDidLoad:視圖控制器的 view 被加載完成  
5. viewWillAppear:視圖控制器的view將要顯示在 window上
6. updateViewConstraints:視圖控制器的 view 開始更新 AutoLayout 約束
7. viewWillLayoutSubviews:視圖控制器的 view 將要更新內(nèi)容視圖的位置
8. viewDidLayoutSubviews:視圖控制器的 view 已經(jīng)更新視圖的位置
9. viewDidAppear:視圖控制器的view已經(jīng)展示到 window 上 
10. viewWillDisappear:視圖控制器的 view 將要從 window 上消失
11. viewDidDisappear:視圖控制器的 view 已經(jīng)從 window 上消失

八十六拓春、dealloc中取weakSelf

參考

八十七亚隅、assign 和 weak

如果以MRC和ARC進行區(qū)分修飾符使用情況,可以按照如下方式進行分組:

  • MRC:assign煮纵、retain、copy匆光、readwrite终息、readonly、nonatomic柳譬、atomic 等。
  • ARC:assign制跟、strong雨膨、weak哥放、copy甥雕、readwrite社露、readonly峭弟、nonatomic、atomic 等情臭。
    MRC 和 ARC 都可以用assign俯在,但 weak 只存在于 ARC 中跷乐。assign 和weak 的主要區(qū)在于:當它們指向的對象釋放以后愕提,weak 會被自動設(shè)置為nil筷黔,而 assign 不會,所以會導致野指針的出現(xiàn)挨决,可能會導致crash脖祈。實際面試過面試官可會問把 delegate 的屬性修飾符 weak 改為 assign 會有什么影響。

八十八喻奥、#import & @class

#import#include 無需多說撞蚕,二者功能基本相同甥厦,不過 #import 避免了重復(fù)引用的問題,在引用文件的時候不用自己進行重復(fù)引用處理谦秧。重點說一下 #import 和 @class 在實際開發(fā)中的注意注意事項(提高編譯速度油够、避免編譯錯誤)石咬。

  • @class 只是告訴編譯器删性,其后面聲明的名稱是類的名稱蹬挺,至于這些類是如何定義的巴帮,暫時不用考慮。但 #import 會鏈入該頭文件的全部信息用押,包括實體變量和方法等,但是這樣做會對編譯效率造成影響蜻拨。比如有100個類都 #import ClassA.h, 那么在編譯的時候這100個類都會去對 ClassA 處理。如果 ClassA 被修改,那么這 100 個類都需要重新進行編譯休涤,無疑增加了編譯時間不利于開發(fā)效率的提升功氨。一般的做法是:頭文件中一般只需要知道被引用的類的名稱就可以了, 不需要知道其內(nèi)部的實體變量和方法跺涤,所以在頭文件中一般使用 @class 來聲明這個名稱是類的名稱桶错; 實現(xiàn)文件中,如果需要引用這個類的實體變量或者方法就用 #import退腥,否則不做任何處理狡刘。這種做法可以避免掉一些不必要的 #import 操作剑按,一定程度上可能提高編譯效率吕座。

  • 如果有循環(huán)依賴關(guān)系漆诽,如: A–>B , B–>A 這樣的相互依賴關(guān)系厢拭,如果使用 #import 來相互包含供鸠,就會出現(xiàn)編譯錯誤薄坏;如果使用@class在兩個類的頭文件中相互聲明胶坠,則不會有編譯錯誤出現(xiàn)。

八十九闻牡、IQKeyBoard 原理

待更新罩润。烧栋。审姓。。

九十酬姆、self.name = 和 name =

待更新。相满。立美。。

九十一洞慎、ipa 包構(gòu)成

  • _CodeSignature:文件的 hash 列表。里面有一個文件 CodeResources 谆棱,它是一個屬性列表个从,包含 bundle 中所有其他文件的列表嫌松。內(nèi)部是一個字典,key 為文件名,value 是 Base64 格式的散列值。主要用于判斷一個應(yīng)用程序的完整性,可防止修改文件蒋譬。
  • AppIcon 圖片
  • LaunchImage 圖片
  • bundle 資源文件
  • Assets.car 資源文件
  • info.plist
  • nib 文件
  • 相應(yīng)程序的 Unix 代碼可執(zhí)行文件
  • embedded.mobileprovision 描述文件
  • 其它(工程中引入的 xml 文件癣漆、iconfont.ttf、gif 圖等)

九十二较性、MRC和ARC的關(guān)系

自動引用計數(shù)管理中(ARC)攀操,內(nèi)存的申請歹垫、使用和釋放過程都交給系統(tǒng)自動實現(xiàn)若贮,開發(fā)者不用關(guān)心里面的過程,事實上還是 MRC 的原理面哼,只是系統(tǒng)幫我們做了管理闯袒。MRC 和 ARC 使用的編譯器也不相同前者是 GCC 后者是 LLVM 3.0。

ARC 是 LLVM 編譯器和 Runtime 相互協(xié)作的結(jié)果。ARC相對于MRC,不是在編譯時添加retain/release/autorelease這么簡單。應(yīng)該是編譯期和運行期兩部分共同幫助開發(fā)者管理內(nèi)存。

  • 編譯時期:ARC 利用LLVM編譯器幫助我們自動生成類似MRC 的 release、retain羡滑、autorelease 代碼。
  • 運行時期:類似弱引用指向的對象釋放問題和 Runtime 相關(guān)有梆。程序運行過程中,監(jiān)測到對象銷毀的時候陨囊,把對象對應(yīng)的弱引用清空压语。

九十三允懂、Foundation 和 CoreFoundation

Foundation對象是Objective-C對象,而Core Foundation對象是C對象缀程,二者比較相似蠢甲, Foundation下的類基本都是NS開頭曼追,Core Foundation下的類基本是CF開頭的。

這里說一下兩者在iOS中的內(nèi)存管理問題婚陪,以前在MRC情況下沽一,都是開發(fā)人員手動管理對象內(nèi)存,二者區(qū)別不大。但在ARC情況下牍蜂,由于Foundation框架是OC對象,所以由系統(tǒng)自動管理內(nèi)存僵井,而Core Foundation框架是C對象乳规,所以需要開發(fā)人員手動管理內(nèi)存冻辩,不然會引起內(nèi)存泄露。在ARC下犁珠,可以對兩個框架的類進行相互轉(zhuǎn)換豹休,以NSString為例威根,有與之對應(yīng)的CFStringRef洛搀,兩者之間可以通過__bridge留美、__bridge_transfer谎砾、__bridge_retained轉(zhuǎn)換:

  • __bridge:用于NSString與CFStringRef相互轉(zhuǎn)換,不改變對象的管理權(quán)所有者郎笆,按照本來對象的內(nèi)存管理宛蚓。本來是NSString凄吏,轉(zhuǎn)換為CFStringRef類型痕钢,依舊由系統(tǒng)管理任连;本來是CFStringRef裁着,轉(zhuǎn)換為NSString二驰,由開發(fā)人員管理桶雀。
  • __bridge_transfer:用于CFStringRef轉(zhuǎn)換成NSString背犯,進行管理權(quán)移交漠魏,由系統(tǒng)自動管理柱锹。
  • __bridge_retained:用于NSString轉(zhuǎn)換成CFStringRef禁熏,剝奪了ARC管理權(quán),需要開發(fā)人員手動管理宙彪。

九十四释漆、簡單對象 MRC

不借助 autorelease男图。

Person *person2 = [[Person alloc] init];
[person2 release];//直接釋放

借助 autorelease(實際 MRC 開發(fā))。

Person *person1 = [[[Person alloc] init] autorelease];//適當時機自動釋放

類似 [NSArray arrayWithObject:@""] 內(nèi)部一般是有 autorelease 操作难裆。

九十五差牛、成員為對象 MRC 管理

不借助 autorelease 偏化。

//.h文件
@interface Person : NSObject
{
   Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;


//.m文件
//person擁有dog,非autorelease技術(shù)韵卤,先release之前的再持有現(xiàn)在的
- (void)setDog:(Dog *)dog{
    //如果中途換了dog,不是之前的dog沈条,先釋放之前的dog(對現(xiàn)在的dog沒影響)蜡歹,再retain現(xiàn)在的dog
    if (_dog != dog) {
        [_dog release];
        //該處的retain 和 dealloc 中的 release 對應(yīng),也即引用計數(shù)誰+1父款,最終誰負責-1憨攒。
        _dog = [dog retain];
    }
    //如果dog是現(xiàn)在的dog:第一次調(diào)用setDog方法時浓恶,即dog為nil的時候,已經(jīng)對dog進行retain操作了伐憾,之后就無需再retain树肃,因為第一次的結(jié)果導致dog的引用計數(shù)大于0胸嘴,肯定不會被釋放乡话,所以之后增加引用計數(shù)是無意義的
}
- (Dog *)dog{
    return _dog;
}

//先釋放成員變量绑青,在釋放父類本身
- (void)dealloc{
    [_dog release];
    _dog = nil;
    //self.dog = nil;
    // 父類的dealloc放到最后
    [super dealloc];
}
Dog *dog = [[Dog alloc] init]; // dog:1
    
    Person *person = [[Person alloc] init];
    [person setDog:dog]; // dog:2
    [dog release]; // dog:1 (對應(yīng)alloc)
    
    //因為是person操作,不能造成崩潰邪乍,所以setDog方法內(nèi)部需要retain操作
    //如果中途換了dog不是之前的dog溺欧,要先釋放之前的dog姐刁,再retain操作
    [person setDog:dog];// dog:1
    
    //set內(nèi)部有判斷,首次已經(jīng)retain操作了柏靶,
    [person setDog:dog];// dog:1(引用計數(shù)不變)
    [person setDog:dog];// dog:1(set內(nèi)部有判斷屎蜓,引用計數(shù)不變) 
    
    [person release]; // dog:0

借助 autorelease(實際 MRC 開發(fā))。

//.h 文件
@interface Person : NSObject
//注意這里的retain屬性修飾符
@property (nonatomic, retain) MJDog *dog;
+ (instancetype)person;
@end


//.m 文件
@implementation MJPerson
+ (instancetype)person{
    return [[[self alloc] init] autorelease];
}
- (void)dealloc{
    self.dog = nil;
    [super dealloc];
}
@end
//使用
 MJPerson *person = [MJPerson person];

九十六扼劈、UIScrollView 多頁面?zhèn)然祷厥謩輿_突

相關(guān)參考
參考
參考
iOS 觸控事件 UITouch 和手勢識別 UIGestureRecognizer

#import "UIScrollView+PanGesture.h"
@implementation UIScrollView (PanGesture)
//location_X可自己定義,其代表的是滑動返回距左邊的有效長度
- (BOOL)panBack:(UIGestureRecognizer *)gestureRecognizer {
    //是滑動返回距左邊的有效長度
    int location_X =0.15*[MCDevice screenWidth];
    if (gestureRecognizer == self.panGestureRecognizer) {
        UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
        CGPoint point = [pan translationInView:self];
        UIGestureRecognizerState state = gestureRecognizer.state;
        if (UIGestureRecognizerStateBegan == state ||UIGestureRecognizerStatePossible == state) {
            CGPoint location = [gestureRecognizer locationInView:self];
            //允許每個頁面都可實現(xiàn)滑動返回
            int temp1 = location.x;
            int temp2 =[MCDevice screenWidth];
            NSInteger XX = temp1 % temp2;
            if (point.x > 0 && XX < location_X) {
                return YES;
            }
            //只允許在第一張時滑動返回生效
            //            if (point.x > 0 && location.x < location_X && self.contentOffset.x <= 0) {
            //                return YES;
            //            }
        }
    }
    return NO;
}
/*
 開始進行手勢識別時調(diào)用的方法,返回NO則結(jié)束識別贼涩,不再觸發(fā)手勢,用處:可以在控件指定的位置使用手勢識別
 該功能中禁止scrollView 自己的側(cè)滑返回手勢,走導航控制器的側(cè)滑返回手勢
 */
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([self panBack:gestureRecognizer]) {
        return NO;
    }
    return YES;
}
@end

九十七烟央、gestureRecognizer 和 UIControl 的關(guān)系

待更新疑俭。。哩照。飘弧。

九十八、FDFullscreenPopGesture 原理

+ (void)load
{
    // Inject "-pushViewController:animated:"
    Method originalMethod = class_getInstanceMethod(self, @selector(pushViewController:animated:));
    Method swizzledMethod = class_getInstanceMethod(self, @selector(fd_pushViewController:animated:));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
        
        // Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to.
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];

        // Forward the gesture events to the private handler of the onboard gesture recognizer.
        NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
        id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
        SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
        self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
        [self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];

        // Disable the onboard gesture recognizer.
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    // Handle perferred navigation bar appearance.
    [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
    
    // Forward to primary implementation.
    [self fd_pushViewController:viewController animated:animated];
}
  • load 方法交叉替換 NavigationController 的 pushViewController:animated: 方法。
    系統(tǒng)中每一個 NavigationController 默認有一個 interactivePopGestureRecognizer柱彻,但是這里把系統(tǒng)的 interactivePopGestureRecognizer 設(shè)置為禁用方式。創(chuàng)建自定義 pan 手勢并添加到 interactivePopGestureRecognizer 對應(yīng)的View上吓蘑,interactivePopGestureRecognizer 會操作一個指定的 target 溃蔫、action(handleNavigationTransition), 通過Runtime動態(tài)獲取到指定的target 和 action添加到自定義的手勢上伟叛。

九十九、判斷模擬器和真機的坑

//正確的方法
#if TARGET_IPHONE_SIMULATOR  //模擬器

#elif TARGET_OS_IPHONE      //真機

#endif
//錯誤的方法
#if TARGET_OS_IPHONE      //真機

#endif

上述第二種方法無論是在真機還是模擬器環(huán)境侥蒙,中間代碼都會執(zhí)行鞭衩,是蘋果的一個坑。

一百坯台、內(nèi)存泄漏檢測原理 (待補充)

MLeaksFinder 為基類 NSObject 添加一個方法 -willDealloc 方法,該方法的作用是滥搭,先用一個弱指針指向 self瑟匆,并在一小段時間 (3 秒) 后,通過這個弱指針調(diào)用 -assertNotDealloc冕象,而 -assertNotDealloc 主要作用是直接彈框提醒該對象可能存在內(nèi)存泄漏论悴。當我們認為某個對象應(yīng)該要被釋放了膀估,在釋放前調(diào)用這個方法,如果 3 秒后它被釋放成功饼记,weakSelf 就指向 nil,不會調(diào)用到 -assertNotDealloc 方法,也就不會彈框提示泄漏匕坯;如果它沒被釋放(泄露了),-assertNotDealloc 就會被調(diào)用术奖,具體是遍歷基于 UIViewController 的整棵 View-ViewController 樹,通過 UIViewController 的 presentedViewController 和 view 屬性唧龄,UIView 的 subviews 屬性等遞歸遍歷既棺,依次調(diào) -willDealloc,若 3 秒后沒被釋放胖烛,則存在泄漏『榧海可參考該篇文章;

- (BOOL)willDealloc {
    __weak id weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf assertNotDealloc];
    });
    return YES;
}
- (void)assertNotDealloc {
     NSAssert(NO, @“”);
}
例外機制

對于有些 ViewController妥凳,在被 pop 或 dismiss 后,不會被釋放(比如單例)答捕,因此需要提供機制讓開發(fā)者指定哪個對象不會被釋放逝钥,這里可以通過重載上面的 -willDealloc 方法拱镐,直接 return NO 即可艘款。

手動擴展

MLeaksFinder目前只檢測 ViewController 跟 View 對象。為此沃琅,MLeaksFinder 提供了一個手動擴展的機制哗咆,你可以從 UIViewController 跟 UIView 出發(fā),去檢測其它類型的對象的內(nèi)存泄露益眉。如下所示晌柬,我們可以檢測 UIViewController 內(nèi)的 View Model。宏 MLCheck() 做的事就是為傳進來的對象建立 View-ViewController stack 信息郭脂,并對傳進來的對象調(diào)用 -willDealloc 方法年碘。

- (BOOL)willDealloc {
    if (![super willDealloc]) {
        return NO;
    }
    MLCheck(self.viewModel);
    return YES;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市展鸡,隨后出現(xiàn)的幾起案子屿衅,更是在濱河造成了極大的恐慌,老刑警劉巖莹弊,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涤久,死亡現(xiàn)場離奇詭異,居然都是意外死亡忍弛,警方通過查閱死者的電腦和手機响迂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來细疚,“玉大人栓拜,你說我怎么就攤上這事』菸簦” “怎么了幕与?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長镇防。 經(jīng)常有香客問我啦鸣,道長,這世上最難降的妖魔是什么来氧? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任诫给,我火速辦了婚禮香拉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘中狂。我一直安慰自己凫碌,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布胃榕。 她就那樣靜靜地躺著盛险,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勋又。 梳的紋絲不亂的頭發(fā)上苦掘,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音楔壤,去河邊找鬼鹤啡。 笑死,一個胖子當著我的面吹牛蹲嚣,可吹牛的內(nèi)容都是我干的递瑰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼隙畜,長吁一口氣:“原來是場噩夢啊……” “哼抖部!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起禾蚕,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤您朽,失蹤者是張志新(化名)和其女友劉穎狂丝,沒想到半個月后换淆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡几颜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年倍试,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛋哭。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡县习,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谆趾,到底是詐尸還是另有隱情躁愿,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布沪蓬,位于F島的核電站彤钟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏跷叉。R本人自食惡果不足惜逸雹,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一营搅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梆砸,春花似錦转质、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狮暑,卻和暖如春鸡挠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搬男。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工拣展, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缔逛。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓备埃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親褐奴。 傳聞我的和親對象是個殘疾皇子按脚,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344