一.在查看優(yōu)化經(jīng)驗文章的時候欣簇,經(jīng)常會看到關(guān)于+load
和initialize
兩個方法巾钉,其中+load
方法翘狱,是只要程序啟動了,程序就會將所有代碼加載到內(nèi)存的代碼區(qū)砰苍,此時潦匈,這個+load
方法就會執(zhí)行,同時赚导,此方法會在main
函數(shù)執(zhí)行前就調(diào)用茬缩,而 initialize
是該類初始化時候才調(diào)用。所以推薦在做啟動優(yōu)化的時候吼旧,要檢查系統(tǒng)中的無用類凰锡、過期類,合并多余類圈暗、中間類掂为,因為每次啟動app,這些都會加載一遍员串。
二.啟動時間大體可分為兩步(冷啟動熱啟動就算了勇哗,不區(qū)分了):
第一步:main函數(shù)加載之前,系統(tǒng)會加載動態(tài)庫寸齐、引入類的+load
方法等欲诺,所以需要:刪減不必要的動態(tài)庫,定期檢查系統(tǒng)中無用類渺鹦,過期類扰法,合并多余中間類(多說一嘴,中小項目沒必要用MVVM)海铆,除了系統(tǒng)動態(tài)庫迹恐,個人創(chuàng)建的動態(tài)庫多常見于一些自己封裝的UIKit,可以考慮封裝成靜態(tài)庫卧斟,或者合并殴边。
第二步:main函數(shù)加載之后,此時珍语,主要是針對業(yè)務(wù)模塊進行優(yōu)化锤岸,
1.日志、統(tǒng)計板乙、廣告業(yè)等必須一開始加載的功能是偷,保留在didFinishLaunchingWithOptions
中拳氢。
2.推送、項目配置蛋铆、環(huán)境配置馋评、信息初始化等必須在主頁面進入前完成的工作,放到廣告頁的viewDidAppear里進行刺啦。
3.其他SDK和配置事件留特,由于啟動時間不是必須的,所以可以放到首頁的viewDidAppear里進行玛瘸。
4.初始化tabbar的時候蜕青,除了首頁外,延遲其他幾個控制器的創(chuàng)建時間糊渊,比如“個人中心”右核,“訂單”等模塊,用空的viewController占位渺绒,第一次點擊的時候再初始化加載贺喝。
其他更厲害的優(yōu)化,可以參考抖音團隊分享的:基于二進制文件重排的解決方案芒篷。http://www.zyiz.net/tech/detail-127196.html
三:KVO是如何實現(xiàn)監(jiān)聽的
KVO底層使用了 isa-swizling的技術(shù).
OC中每個對象/類都有isa指針, isa 表示這個對象是哪個類的對象.
當(dāng)使用KVO給對象的某個屬性注冊了一個 observer搜变,系統(tǒng)會創(chuàng)建一個新的中間類(intermediate class)繼承原來的class,把該對象的isa指針指向中間類(這樣针炉,這個對象的類其實就改變?yōu)檫@個新創(chuàng)建的中間類)。
然后中間類會重寫該屬性的setter方法扳抽,當(dāng)這個屬性的值更改的時候篡帕,會在調(diào)用setter之前調(diào)用willChangeValueForKey, 調(diào)用setter之后調(diào)用didChangeValueForKey,以此通知所有觀察者值發(fā)生更改贸呢。
之后重寫了 -class 方法镰烧,企圖欺騙我們這個類沒有變,就是原本那個類楞陷。
四:NSTimer的循環(huán)引用和Block循環(huán)引用有什么區(qū)別怔鳖?
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timerAction) userInfo:nil repeats:YES];
同樣是用__weak修飾self,為什么NSTimer會導(dǎo)致循環(huán)引用固蛾,而block不會呢结执?
因為block中,引用的是weakSelf這個指針艾凯,而NSTimer引用的是weakSelf指針指向的內(nèi)存地址献幔,所以只要NSTimer不釋放,就會一直持有weakSelf指向的self趾诗,而因為timer被runloop強持有蜡感,runloop又是常駐的,所以timer一直沒有被釋放。
NSTimer解決循環(huán)引用的幾種方式:
1.離開頁面的時候郑兴,置空timer
2.ios 10之后犀斋,系統(tǒng)幫我們對NSTimer進行了block的封裝,防止block循環(huán)引用即可
3.做個中間類情连,幫助控制器持有NSTimer
4.用GCD的定時器
五:GCD什么情況下會開啟多線程叽粹?
1.同步函數(shù)串行隊列:
不會開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)
任務(wù)串行執(zhí)行蒙具,一個接著一個
會產(chǎn)生死鎖
2.同步函數(shù)并行隊列:
不會開啟新線程球榆,在當(dāng)前線程執(zhí)行任務(wù)
任務(wù)依然一個接一個
3.異步函數(shù)串行隊列:
開啟一個新線程,任務(wù)一個接一個執(zhí)行
4.異步函數(shù)并行隊列:
開啟多個線程禁筏,任務(wù)并發(fā)執(zhí)行
5.創(chuàng)建隊列的方式:
- (void)test {
//串行隊列
dispatch_queue_t serial = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
//并發(fā)隊列
dispatch_queue_t concurrent = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
//主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
}
根據(jù)官網(wǎng)介紹持钉,多線程創(chuàng)建需要耗費一定的內(nèi)核空間,主線程一般是1MB,非主線程一般是16Kb到512KB,創(chuàng)建線程的時間大概是90微秒篱昔,切換線程每强,也會消耗一定的資源時間
六:柵欄函數(shù)
1.dispatch_barrier_async
: 當(dāng)前任務(wù)隊列中,前面的任務(wù)執(zhí)行完畢才會執(zhí)行barrier中的邏輯州刽,以及barrier后加入該隊列的任務(wù)空执,但是不影響該柵欄函數(shù)所在隊列后面函數(shù)的執(zhí)行。
2.dispatch_barrier_sync
:作用相同穗椅,區(qū)別是會堵塞隊列后面的函數(shù)執(zhí)行辨绊。
舉例:
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"3:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
}
上面代碼打印出來的數(shù)據(jù),1,2,5的排序是不固定的匹表,门坷,但是3、4一定是最后兩個袍镀,3一定在4前面默蚌。
3.柵欄函數(shù)在全局隊列中是不生效的,因為全局隊列中苇羡,不僅有你的任務(wù)绸吸,還有其他系統(tǒng)的任務(wù),如果加barrier
设江,不僅會影響你自己的任務(wù)锦茁,還會影響系統(tǒng)的任務(wù),對于全局隊列來說绣硝,barrier
相當(dāng)于普通的異步函數(shù)蜻势。
4.柵欄函數(shù)可以當(dāng)成鎖來用:
NSMutableArray *array = [NSMutableArray array];
dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
[array addObject:@(i)];
});
}
上面代碼中,多線程操作一個數(shù)組array鹉胖,在addObject的時候有可能存在同一時間對同一塊內(nèi)存空間寫入數(shù)據(jù)(因為多線程是無序的握玛,i=4的時候和i=100的時候够傍,有可能i=100先運行,那么這時候往數(shù)組里插數(shù)據(jù)挠铲,就有可能數(shù)據(jù)錯誤冕屯,此時就會崩潰報錯)。
比如寫第3個數(shù)據(jù)的時候拂苹,當(dāng)前數(shù)組中數(shù)據(jù)是(1安聘,2)
這個時候有2個線程同時寫入數(shù)據(jù)就存在了(1,2瓢棒,3)
和(1浴韭,2,4)
這個時候數(shù)據(jù)就發(fā)生了混亂造成了錯誤脯宿。
將數(shù)組添加元素的操作放入dispatch_barrier_async
中:
dispatch_queue_t concurrentQueue = dispatch_queue_create("Hotpot", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_barrier_async(concurrentQueue , ^{
[array addObject:@(i)];
});
});
}
七.信號量
dispatch_semaphore_create(long value);
初始化一個值為value的信號量,value等于幾念颈,就支持最多幾個線程并發(fā)訪問。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
此函數(shù)會讓信號量-1(通常來講连霉,我們會將信號量初始化為1)榴芳,信號量小于0,則線程進入堵塞跺撼、等待狀態(tài)窟感。
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
此函數(shù)會釋放信號量,讓信號量+1歉井,信號量不為1柿祈,則該線程繼續(xù)執(zhí)行
信號量的作用:
1.可以限制并發(fā)隊列里最大并發(fā)數(shù)
2.配合線程組,解決多個網(wǎng)絡(luò)請求后哩至,統(tǒng)一刷新頁面谍夭、處理數(shù)據(jù)的問題
3.保護數(shù)據(jù)上鎖
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//此時當(dāng)前并發(fā)隊列,只有一個線程能訪問
NSLog(@"添加的值:%d 當(dāng)前線程%@",i,[NSThread currentThread]);
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
});
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"1 start");
NSLog(@"1 end");
});//由于初始值為0憨募,wait后,信號量-1袁辈,小于0菜谣,所以后面的打印方法不執(zhí)行
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2 start");
NSLog(@"2 end");
dispatch_semaphore_signal(sem);
});//此異步任務(wù)先執(zhí)行
打印出來的值:
2 start
2 end
1 start
1 end
八:線程組:
控制線程任務(wù)執(zhí)行順序,類似于柵欄函數(shù)的作用
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"1");
});
dispatch_group_async(group, queue1, ^{
sleep(2);
NSLog(@"2");
});
dispatch_group_async(group, queue1, ^{
sleep(1);
NSLog(@"3");
});
dispatch_group_async(group, queue, ^{
NSLog(@"4");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
任務(wù)5永遠在任務(wù)1晚缩、2尾膊、3、4之后執(zhí)行荞彼。