開發(fā)小知識(一)
開發(fā)小知識(二)
目錄
- 五十一雨女、關(guān)聯(lián)對象
- 五十二补履、TCP 面向連接的本質(zhì)是什么?TCP 和 UDP 的區(qū)別?
- 五十三、高效安全讀寫方案
- 五十四、死鎖
- 五十五、如何理解代理和協(xié)議?
- 五十六挨约、MVP && MMVM
- 五十七、簡單工廠和工廠模式
- 五十八产雹、適配器模式概念及應(yīng)用
- 五十九诫惭、外觀模式概念及應(yīng)用
- 六十、策略模式概念及應(yīng)用
- 六十一蔓挖、界面卡頓原因
- 六十二夕土、[UIApplication sharedApplication].delegate.window&& [UIApplication sharedApplication].keyWindow的區(qū)別
- 六十三、單例注意事項
- 六十四时甚、性能優(yōu)化總結(jié)
- 六十五隘弊、內(nèi)存區(qū)域
- 六十六、符號表
- 六十七荒适、指針和引用
- 六十八梨熙、static & const & extern
- 六十九、枚舉
- 七十、驗證碼的作用
- 七十一、幀率優(yōu)化
- 七十二、內(nèi)存數(shù)據(jù)擦除
- 七十三应狱、找不到方法怎么辦
- 七十四质欲、卡頓代碼監(jiān)測原理
- 七十五树埠、同時實現(xiàn) set & get
- 七十六、main 中的 UIApplicationMain 函數(shù)
- 七十七嘶伟、nil怎憋、Nil、NULL九昧、NSNull
- 七十八绊袋、iOS 系統(tǒng)結(jié)構(gòu)
- 七十九、鑰匙串訪問
- 八十铸鹰、動態(tài)規(guī)劃思路
- 八十一癌别、主線程更新 UI 原因
- 八十二、設(shè)計原則
- 八十三蹋笼、@selector展姐、_cmd、SEL
- 八十四剖毯、Clang & LLVM & GCC
- 八十五圾笨、生命周期
- 八十六、dealloc中取weakSelf
- 八十七速兔、assign 和 weak
- 八十八墅拭、#import & @class
- 八十九活玲、IQKeyBoard 原理
- 九十涣狗、self.name = 和 name =
- 九十一、ipa 包構(gòu)成
- 九十二舒憾、MRC和ARC的關(guān)系
- 九十三镀钓、Foundation 和 CoreFoundation
- 九十四、簡單對象 MRC
- 九十五镀迂、成員為對象 MRC 管理
- 九十六丁溅、UIScrollView 多頁面?zhèn)然祷厥謩輿_突
- 九十七、gestureRecognizer 和 UIControl 的關(guān)系
- 九十八探遵、FDFullscreenPopGesture 原理
- 九十九窟赏、判斷模擬器和真機的坑
- 一百、內(nèi)存泄漏檢測原理
五十一箱季、關(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_async
,dispatch_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 同 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 總的來說和 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 init
、 copy
和 mutableCopy
方法返回同一個實例對象交洗。關(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
须肆、TestB
、TestC
桩皿。
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è)置 cornerRadius
和 masksToBounds = 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
set
和 get
方法單獨重寫任意一個方法都不會報錯丹禀,但是同時重寫會報錯。主要是因為重寫 get
和 set
方法之后 @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ù)
argc
和argv
是 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ù)組添加新原素等外里。 -
NSNull
和nil
的區(qū)別在于怎爵,nil
是一個空對象,已經(jīng)完全從內(nèi)存中消失了盅蝗,而如果想表達“我們需要有這樣一個容器疙咸,但這個容器里什么也沒有”的觀念時,就用到NSNull
风科,稱之為值為空的對象。NSNull
繼承自NSObject
乞旦,并且只有一個null
類方法贼穆。這就說明NSNull
對象擁有一個有效的內(nèi)存地址,所以在程序中對它的引用不會導致程序崩潰兰粉。 -
nil
和Nil
在使用上是沒有嚴格限定的故痊,也就是說凡是使用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)不同的前后端使用統(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 則負責后端處理枷恕。
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;
}