## iOS常用問(wèn)題總結(jié)####
iOS基礎(chǔ)知識(shí)回顧#####
1、為什么說(shuō)Objective-C是一門動(dòng)態(tài)的語(yǔ)言?靜態(tài)、動(dòng)態(tài)是相對(duì)的,這里動(dòng)態(tài)語(yǔ)言指的是不需要在編譯時(shí)確定所有的東西,在運(yùn)行時(shí)還可以動(dòng)態(tài)的添加變量、方法和類Objective-C 可以通過(guò)Runtime 這個(gè)運(yùn)行時(shí)機(jī)制,在運(yùn)行時(shí)動(dòng)態(tài)的添加變量、方法、類等,所以說(shuō)Objective-C 是一門動(dòng)態(tài)的語(yǔ)言O(shè)bjective-C 是C 的超集,在C 語(yǔ)言的基礎(chǔ)上添加了面向?qū)ο筇匦裕⑶依肦untime 這個(gè)運(yùn)行時(shí)機(jī)制,為Objective-C 增添了動(dòng)態(tài)的特性。Objective-C 使用的是 “消息結(jié)構(gòu)” 并非 “函數(shù)調(diào)用”:使用消息結(jié)構(gòu)的的語(yǔ)言,其運(yùn)行時(shí)所應(yīng)執(zhí)行的代碼由運(yùn)行期決定;而使用函數(shù)調(diào)用的語(yǔ)言,則由編譯器決定
> (1)動(dòng)態(tài)類型語(yǔ)言:動(dòng)態(tài)類型語(yǔ)言是指在運(yùn)行期間才去做數(shù)據(jù)類型檢查的語(yǔ)言,也就是說(shuō),在用動(dòng)態(tài)類型的語(yǔ)言編程時(shí),永遠(yuǎn)也不用給任何變量指定數(shù)據(jù)類型,該語(yǔ)言會(huì)在你第一次賦值給變量時(shí),在內(nèi)部將數(shù)據(jù)類型記錄下來(lái)。Python和Ruby就是一種典型的動(dòng)態(tài)類型語(yǔ)言,其他的各種腳本語(yǔ)言如VBScript也多少屬于動(dòng)態(tài)類型語(yǔ)言
。> (2)靜態(tài)類型語(yǔ)言:靜態(tài)類型語(yǔ)言與動(dòng)態(tài)類型語(yǔ)言剛好相反,它的數(shù)據(jù)類型是在編譯其間檢查的,也就是說(shuō)在寫程序時(shí)要聲明所有變量的數(shù)據(jù)類型,C/C++是靜態(tài)類型語(yǔ)言的典型代表,其他的靜態(tài)類型語(yǔ)言還有C#、JAVA等。因?yàn)樵谶\(yùn)行期可以繼續(xù)向類中添加方法,所以編譯器在編譯時(shí)還無(wú)法確定類中是否有某個(gè)方法的實(shí)現(xiàn)。對(duì)于類無(wú)法處理一個(gè)消息就會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制消息轉(zhuǎn)發(fā)分為兩大階段:1. “動(dòng)態(tài)方法解析”:先征詢接收者,所屬的類,能否動(dòng)態(tài)添加方法,來(lái)處理這個(gè)消息,若可以則結(jié)束,如不能則繼續(xù)往下走2. “完整的消息轉(zhuǎn)發(fā)機(jī)制”: - 請(qǐng)接收者看看有沒其他對(duì)象能處理這條消息,若有,就把這個(gè)消息轉(zhuǎn)發(fā)給那個(gè)對(duì)象然后結(jié)束 - 運(yùn)行時(shí)系統(tǒng)會(huì)把與消息有關(guān)細(xì)節(jié)全部封裝到NSInvocation 對(duì)象中,再給對(duì)象最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息
#####2、講一下MVC和MVVM,MVP?[MVC、MVVM、MVP](http://www.cocoachina.com/ios/20170313/18870.html)
#####3、為什么代理要用weak?代理的delegate和dataSource有什么區(qū)別?block和代理的區(qū)別? 為了避免循環(huán)引用。weak指明該對(duì)象并不負(fù)責(zé)保持delegate這個(gè)對(duì)象,delegate這個(gè)對(duì)象的銷毀由外部控制。strong該對(duì)象強(qiáng)引用delegate,外界不能銷毀delegate對(duì)象,會(huì)導(dǎo)致循環(huán)引用。DataSource是關(guān)于View的內(nèi)容的東西包括屬性,數(shù)據(jù)等等,而Delegate則是一些我們可以調(diào)用的方法,全是操作。block和代理都能解決對(duì)象間交互的問(wèn)題,block更輕型,更簡(jiǎn)單,能夠直接訪問(wèn)上下文,代碼通常在同一個(gè)地方,這樣讀代碼也連貫。缺點(diǎn)是容易引起循環(huán)引用。delegate更重一些,需要實(shí)現(xiàn)接口,它的方法分開來(lái),很多時(shí)候需要存儲(chǔ)一些臨時(shí)數(shù)據(jù),另外相關(guān)的代碼需要分離到各處沒有block好讀,其優(yōu)點(diǎn)就是它是用weak關(guān)鍵字修飾的,不會(huì)引起循環(huán)引用。weak指明該對(duì)象并不負(fù)責(zé)保持delegate這個(gè)對(duì)象,delegate這個(gè)對(duì)象的銷毀由外部控制delegate偏重于與用戶交互的回調(diào),有那些方法可以供我使用,例如UITableviewDelegate;dataSource偏重于數(shù)據(jù)的回調(diào),view里面有什么東西,屬性都是什么,例如UITableviewDatasource;
[block和代理的區(qū)別](http://www.lxweimin.com/p/6bba9b4a25d5)
**代理的好處:
**> ?delegate運(yùn)行成本低。block成本很高的。
>> block出棧需要將使用的數(shù)據(jù)從棧內(nèi)存拷貝到堆內(nèi)存,當(dāng)然對(duì)象的話就是加計(jì)數(shù),使用完或者block置nil后才消除;delegate只是保存了一個(gè)對(duì)象指針,直接回調(diào),沒有額外消耗。相對(duì)C的函數(shù)指針,只多做了一個(gè)查表動(dòng)作**delegate:
**> 1,“一對(duì)一”,對(duì)同一個(gè)協(xié)議,一個(gè)對(duì)象只能設(shè)置一個(gè)代理delegate,單例對(duì)象就不能用代理這是不對(duì)的?,任何人,任何對(duì)象,只要接受,只要允許,只要遵守了相關(guān)的協(xié)議,TA就可以使用代理(感謝http://www.lxweimin.com/users/22fefaea871c同學(xué)指出錯(cuò)誤);
>> 2,代理更注重過(guò)程信息的傳輸:比如發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,可能想要知道此時(shí)請(qǐng)求是否已經(jīng)開始、是否收到了數(shù)據(jù)、數(shù)據(jù)是否已經(jīng)接受完成、數(shù)據(jù)接收失敗
**block:**> 1:寫法更簡(jiǎn)練,不需要寫protocol、函數(shù)等等
>> 2,block注重結(jié)果的傳輸:比如對(duì)于一個(gè)事件,只想知道成功或者失敗,并不需要知道進(jìn)行了多少或者額外的一些信息
>> 3,block需要注意防止循環(huán)引用:**ARC下這樣防止:
**> __weak typeof(self) weakSelf = self;>> [yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {>> [weakSelf doSomething];>> }];
**非ARC**> __block typeof(self) weakSelf = self;>> [yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {>> [weakSelf doSomething];>> }];
**什么時(shí)候用代理,什么時(shí)候用block
**> 公共接口,方法較多也選擇用delegate進(jìn)行解耦
>> iOS有很多例子比如最常用tableViewDelegate,textViewDelegate
**異步和簡(jiǎn)單的回調(diào)用block更好
**> iOS有很多例子比如常用的網(wǎng)絡(luò)庫(kù)AFNetwork,ASIHTTP庫(kù),UIAlertView類。
#####4、屬性的實(shí)質(zhì)是什么?包括哪幾個(gè)部分?屬性默認(rèn)的關(guān)鍵字都有哪些?@dynamic關(guān)鍵字和@synthesize關(guān)鍵字是用來(lái)做什么的?A:屬性的本質(zhì)是@property = ivar+getter+setter,也就是說(shuō)@property系統(tǒng)會(huì)自動(dòng)生成getter和setter方法。
屬性默認(rèn)的關(guān)鍵字包括atomic,nonatomic,@synthesize,@dynamic,getter=getterName,setter=setterName,readwrite,readonly,assign,retain,copy。@dynamic:表示變量對(duì)應(yīng)的屬性訪問(wèn)器方法,是動(dòng)態(tài)實(shí)現(xiàn)的,你需要在 NSObject 中繼承而來(lái)的?
+(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 動(dòng)態(tài)實(shí)現(xiàn)的方法或者函數(shù)。@synthesize:如果沒有實(shí)現(xiàn)setter和getter,編譯器能夠自動(dòng)實(shí)現(xiàn)getter和setter方法。synthesize是實(shí)現(xiàn)一個(gè)setter的,synthesize是一個(gè)編譯器指令, 它可以簡(jiǎn)化我們getter/setter方法的實(shí)現(xiàn)@dynamic 是相對(duì)于 @synthesize 的,它們用樣用于修飾 @property,用于生成對(duì)應(yīng)的的 getter 和 setter 方法。但是 @ dynamic 表示這個(gè)成員變量的 getter 和 setter 方法并不是直接由編譯器生成,而是手工生成或者運(yùn)行時(shí)生成。
1. 由于是手工創(chuàng)建 setter 和 getter,沒有帶下劃線成員變量需要手工去創(chuàng)建.不像@synthesize發(fā)現(xiàn)沒有會(huì)自動(dòng)去創(chuàng)建
2. @dynamic告訴編譯器我自己手工實(shí)現(xiàn) setter 和 getter, 至于實(shí)不實(shí)現(xiàn),就是自己的問(wèn)題了
.#####5、屬性的默認(rèn)關(guān)鍵字是什么?
#####6、NSString為什么要用copy關(guān)鍵字,如果用strong會(huì)有什么問(wèn)題?(注意:這里沒有說(shuō)用strong就一定不行。使用copy和strong是看情況而定的)
A:針對(duì)于當(dāng)把NSMutableString賦值給NSString的時(shí)候,才會(huì)有不同,用copy的話NSString的值不會(huì)發(fā)生變化,用strong則會(huì)發(fā)生變化,隨著NSMutableString的值變化。如果是賦值是NSString對(duì)象,那么使用copy還是strong,結(jié)果都是一樣的,因?yàn)镹SString對(duì)象根本就不能改變自身的值,他是不可變的。
#####7、如何令自己所寫的對(duì)象具有拷貝功能?若想讓自己寫的對(duì)象具有拷貝功能,則需要實(shí)現(xiàn)NSCopying協(xié)議。如果自定義的對(duì)象分為可變版本和非可變版本,那么就要同時(shí)實(shí)現(xiàn)NSCopying和NSMutableCopying協(xié)議,不過(guò)一般沒什么必要,實(shí)現(xiàn)NSCopying協(xié)議就夠了。#####8、可變集合類 和 不可變集合類的 copy 和 mutablecopy有什么區(qū)別?如果是集合是內(nèi)容復(fù)制的話,集合里面的元素也是內(nèi)容復(fù)制么?對(duì)于不可變對(duì)象,copy操作是淺復(fù)制,mutableCopy是深復(fù)制。對(duì)于不可變對(duì)象,mutableCopy不僅僅是深復(fù)制,返回的對(duì)象類型還是不可變對(duì)象類型相應(yīng)的可變對(duì)象的類型。內(nèi)容復(fù)制也就是深拷貝,集合的深復(fù)制有兩個(gè)方法,可以用initWithArray:copyItems:將第二個(gè)參數(shù)設(shè)置為YES即可進(jìn)行深復(fù)制,如:NSDictionary *shallowCopyDict = [NSDictionary alloc]initWithDictionary:someDictionary copyItems:YES];如果用這個(gè)方法深復(fù)制,集合里的每個(gè)元素都會(huì)收到copyWithZone:消息。如果集合里的對(duì)象遵循NSCopying協(xié)議,那么對(duì)象就會(huì)深復(fù)制到新的集合。如果對(duì)象沒有遵循NSCopying協(xié)議,而嘗試用這種方法進(jìn)行深復(fù)制則會(huì)出錯(cuò)。copyWithZone:這種拷貝方式只能提供一層內(nèi)存拷貝,而非真正的深拷貝。第二種方法是將集合進(jìn)行歸檔解檔,如:NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
#####9、為什么IBOutlet修飾的UIView也適用weak關(guān)鍵字?因?yàn)榧热挥型怄溎敲匆晥D在xib或者storyboard中肯定存在,視圖已經(jīng)對(duì)它有一個(gè)強(qiáng)引用了。
#####10、nonatomic和atomic的區(qū)別?atomic是絕對(duì)的線程安全么?為什么?如果不是,那應(yīng)該如何實(shí)現(xiàn)?nonatomic非原子屬性,atomic原子屬性,相對(duì)來(lái)說(shuō),原子屬性比非原子屬性較安全,但是在多個(gè)對(duì)象同時(shí)訪問(wèn)該屬性的時(shí)候并不能保證其絕對(duì)安全。同時(shí),原子屬性更加損耗性能。所以在iOS編程中一般都使用非原子屬性。使用線程鎖來(lái)保證其線程安全。nonatomic和atomic的區(qū)別在于兩者自動(dòng)生成getter和setter的方法不一樣,如果你自己寫getter和setter方法,那么(getter,setter,retain,copy,assign)只起提示作用,寫不寫都一樣。對(duì)于atomic的屬性,系統(tǒng)生成的getter和setter會(huì)保證get,set的操作完整性,不受其他線程影響。比如線程A的getter方法運(yùn)行到一半,線程B調(diào)用了setter,那么線程A的getter還是能得到一個(gè)完整的對(duì)象。而nonatomic就沒有這個(gè)保證了,所以速度要比atomic快。不過(guò)atomic可不能保證線程安全,如果線程A調(diào)用了getter,與此同時(shí)線程B和線程C都調(diào)了setter,那最后線程Aget到的值,三種都有可能:可能是B,C set之前原始的值,也可能是B set的值,也可能是C set的值。同時(shí)這個(gè)最終的值,也可能是B set的值,也可能是C set的值。要保證安全,可以使用線程鎖。
#####11、UICollectionView自定義layout如何實(shí)現(xiàn)?UICollectionViewLayoutAttributes,UICollectionViewFlowLayout。
#####12、用StoryBoard開發(fā)界面有什么弊端?如何避免?難以維護(hù),如果需要改動(dòng)全局的一個(gè)字體,如果是代碼的話就很好辦,pch或頭文件中改動(dòng)就好了。如果是storyboard中就需要一個(gè)一個(gè)改動(dòng)很麻煩。如果storyboard中scene太多,打開storyboard會(huì)比較慢。錯(cuò)誤定位比較困難,好多錯(cuò)誤提示模棱兩可。storyboard開發(fā)界面性能要求更高,因?yàn)榫幾g器會(huì)將storyboard轉(zhuǎn)化為機(jī)器可識(shí)別的代碼。然后在編譯運(yùn)行。消耗性能。使用純代碼相對(duì)來(lái)說(shuō)更好。#####13、進(jìn)程和線程的區(qū)別?同步異步的區(qū)別?并行和并發(fā)的區(qū)別?進(jìn)程是一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序,比如在Windows系統(tǒng)中,一個(gè)運(yùn)行的exe就是一個(gè)進(jìn)程。線程是指進(jìn)程中的一個(gè)執(zhí)行流程。同步是順序執(zhí)行,執(zhí)行完一個(gè)再執(zhí)行下一個(gè)。需要等待,協(xié)調(diào)運(yùn)行。異步就是彼此獨(dú)立,在等待某事件的過(guò)程中繼續(xù)做自己的事,不需要等待這些事件完成后再工作。并行和并發(fā) 是前者相當(dāng)于三個(gè)人同時(shí)吃一個(gè)饅頭,后者相當(dāng)于一個(gè)人同時(shí)吃三個(gè)饅頭。并發(fā)性(Concurrence):指兩個(gè)或兩個(gè)以上的事件或活動(dòng)在同一時(shí)間間隔內(nèi)發(fā)生。并發(fā)的實(shí)質(zhì)是一個(gè)物理CPU(也可以多個(gè)物理CPU) 在若干道程序之間多路復(fù)用,并發(fā)性是對(duì)有限物理資源強(qiáng)制行使多用戶共享以提高效率。并行性(parallelism)指兩個(gè)或兩個(gè)以上事件或活動(dòng)在同一時(shí)刻發(fā)生。在多道程序環(huán)境下,并行性使多個(gè)程序同一時(shí)刻可在不同CPU上同時(shí)執(zhí)行。區(qū)別:(并發(fā))一個(gè)處理器同時(shí)處理多個(gè)任務(wù)和(并行)多個(gè)處理器或者是多核的處理器同時(shí)處理多個(gè)不同的任務(wù)。作者:feedback1991鏈接:http://www.lxweimin.com/p/5e5377b244e2來(lái)源:簡(jiǎn)書著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
#####14、線程間通信?NSThread、GCD、NSOperation。
#####15、GCD的一些常用的函數(shù)?(group,barrier,信號(hào)量,線程同步)
1. 延遲執(zhí)行任務(wù)函數(shù):dispatch_after(.....)。
2. 一次性執(zhí)行dispatch_once(...)。
3. 柵欄函數(shù)dispatch_barrier_async/dispatch_barrier_sync。
4. 隊(duì)列組的使用dispatch_group_t。
5. GCD定時(shí)器。
#####16、如何使用隊(duì)列來(lái)避免資源搶奪?dispatch_barrior_async 作用是在并行隊(duì)列中,等待前面兩個(gè)操作并行操作完成。
#####17、數(shù)據(jù)持久化的幾個(gè)方案(fmdb用沒用過(guò))Coredata,realm,fmdb。
#####18、說(shuō)一下AppDelegate的幾個(gè)方法?從后臺(tái)到前臺(tái)調(diào)用了哪些方法?第一次啟動(dòng)調(diào)用了哪些方法?從前臺(tái)到后臺(tái)調(diào)用了哪些方法?
1.當(dāng)程序第一次運(yùn)行并且將要顯示窗口的時(shí)候執(zhí)行,在該方法中我們完成的操作```objective-c- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions`
``2.程序進(jìn)入后臺(tái)的時(shí)候首先執(zhí)行程序?qū)⒁∠钴S該方法
```objective-c- (void)applicationWillResignActive:(UIApplication *)application
```3.該方法當(dāng)應(yīng)用程序進(jìn)入后臺(tái)的時(shí)候調(diào)用```objective-c- (void)applicationDidEnterBackground:(UIApplication *)application```
4.當(dāng)程序進(jìn)入將要前臺(tái)的時(shí)候調(diào)用
```objective-c- (void)applicationWillEnterForeground:(UIApplication *)application```
5.應(yīng)用程序已經(jīng)變得活躍(應(yīng)用程序的運(yùn)行狀態(tài))
```objective-c - (void)applicationDidBecomeActive:(UIApplication *)application```
6.當(dāng)程序?qū)⒁顺龅臅r(shí)候調(diào)用,如果應(yīng)用程序支持后臺(tái)運(yùn)行,該方法被applicationDidEnterBackground:替換
```objective-c- (void)applicationWillTerminate:(UIApplication *)application```
#####19、NSCache優(yōu)于NSDictionary的幾點(diǎn)?NSCache 是一個(gè)容器類,類似于NSDIctionary,通過(guò)key-value 形式存儲(chǔ)和查詢值,用于臨時(shí)存儲(chǔ)對(duì)象。注意一點(diǎn)它和NSDictionary區(qū)別就是,NSCache 中的key不必實(shí)現(xiàn)copy,NSDictionary中的key必須實(shí)現(xiàn)copy。NSCache中存儲(chǔ)的對(duì)象也不必實(shí)現(xiàn)NSCoding協(xié)議,因?yàn)楫吘故桥R時(shí)存儲(chǔ),類似于內(nèi)存緩存,程序退出后就被釋放了。
#####20、知不知道Designated Initializer?使用它的時(shí)候有什么需要注意的問(wèn)題?#####21、實(shí)現(xiàn)description方法能取到什么效果?
1.NSLog(@"%@", objectA);這會(huì)自動(dòng)調(diào)用objectA的description方法來(lái)輸出ObjectA的描述信息.
2.description方法默認(rèn)返回對(duì)象的描述信息(默認(rèn)實(shí)現(xiàn)是返回類名和對(duì)象的內(nèi)存地址)3.description方法是基類NSObject 所帶的方法,因?yàn)槠淠J(rèn)實(shí)現(xiàn)是返回類名和對(duì)象的內(nèi)存地址, 這樣的話,使用NSLog輸出OC對(duì)象,意義就不是很大,因?yàn)槲覀儾⒉魂P(guān)心對(duì)象的內(nèi)存地址,比較關(guān)心的是對(duì)象內(nèi)部的一些成變量的值。因此,會(huì)經(jīng)常重寫description方法,覆蓋description方法的默認(rèn)實(shí)現(xiàn)。
#####22、objc使用什么機(jī)制管理對(duì)象內(nèi)存?通過(guò) retainCount 的機(jī)制來(lái)決定對(duì)象是否需要釋放。 每次 runloop 的時(shí)候,都會(huì)檢查對(duì)象的 retainCount,如果retainCount 為 0,說(shuō)明該對(duì)象沒有地方需要繼續(xù)使用了,可以釋放掉了。
####**中級(jí)Block**#####
1、block的實(shí)質(zhì)是什么?一共有幾種block?都是什么情況下生成的?block對(duì)象就是一個(gè)結(jié)構(gòu)體,里面有isa指針指向自己的類(global malloc stack),有desc結(jié)構(gòu)體描述block的信息,__forwarding指向自己或堆上自己的地址,如果block對(duì)象截獲變量,這些變量也會(huì)出現(xiàn)在block結(jié)構(gòu)體中。最重要的block結(jié)構(gòu)體有一個(gè)函數(shù)指針,指向block代碼塊。block結(jié)構(gòu)體的構(gòu)造函數(shù)的參數(shù),包括函數(shù)指針,描述block的結(jié)構(gòu)體,自動(dòng)截獲的變量(全局變量不用截獲),引用到的__block變量。(__block對(duì)象也會(huì)轉(zhuǎn)變成結(jié)構(gòu)體)block代碼塊在編譯的時(shí)候會(huì)生成一個(gè)函數(shù),函數(shù)第一個(gè)參數(shù)是前面說(shuō)到的block對(duì)象結(jié)構(gòu)體指針。執(zhí)行block,相當(dāng)于執(zhí)行block里面__forwarding里面的函數(shù)指針。
#####2、為什么在默認(rèn)情況下無(wú)法修改被block捕獲的變量? __block都做了什么?在block中訪問(wèn)的外部變量是復(fù)制過(guò)去的,寫操作不對(duì)原變量生效。#####3、模擬一下循環(huán)引用的一個(gè)情況?block實(shí)現(xiàn)界面反向傳值如何實(shí)現(xiàn)??jī)蓚€(gè).h文件互相import了對(duì)方造成循環(huán)引用。block先聲明(在要傳值的controller里聲明typedef void(^MyBlock)(NSString *name);//block的重命名@property (nonatomic,copy) MyBlock block;//block的聲明),在準(zhǔn)備接收值的頁(yè)面里實(shí)現(xiàn)block,secondVC.block = ^void(NSString *name){_label.text = name;};,誰(shuí)要傳值就在誰(shuí)那里調(diào)用self.block(@"lalala");。
####**Runtime**#####
1、objc在向一個(gè)對(duì)象發(fā)送消息時(shí),發(fā)生了什么?objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行,然后在發(fā)送消息的時(shí)候,objc_msgSend方法不會(huì)返回值,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的。那么,回到本題,如果向一個(gè)nil對(duì)象發(fā)送消息,首先在尋找對(duì)象的isa指針時(shí)就是0地址返回了,所以不會(huì)出現(xiàn)任何錯(cuò)誤。
#####2、什么時(shí)候會(huì)報(bào)unrecognized selector錯(cuò)誤?iOS有哪些機(jī)制來(lái)避免走到這一步?
1. 對(duì)象未實(shí)現(xiàn)該方法。
2. 對(duì)象已經(jīng)被釋放。使用[id respondsToSelector:]進(jìn)行判斷。
#####3、能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?- 不能向編譯后得到的類中增加實(shí)例變量;- 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量;解釋如下:因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定,同時(shí)runtime 會(huì)調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來(lái)處理 strong weak 引用。所以不能向存在的類中添加實(shí)例變量;運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
#####4、runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?runtime 對(duì)注冊(cè)的類, 會(huì)進(jìn)行布局,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc, 在這個(gè) weak 表中搜索,找到所有以a為鍵的 weak 對(duì)象,從而設(shè)置為 nil。weak 修飾的指針默認(rèn)值是 nil (在Objective-C中向nil發(fā)送消息是安全的)
#####5、給類添加一個(gè)屬性后,在類結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化?
####**類結(jié)構(gòu)**#####1、isa指針?(對(duì)象的isa,類對(duì)象的isa,元類的isa都要說(shuō))
#####2、類方法和實(shí)例方法有什么區(qū)別?
#####3、介紹一下分類,能用分類做什么??jī)?nèi)部是如何實(shí)現(xiàn)的?它為什么會(huì)覆蓋掉原來(lái)的方法?
#####4、運(yùn)行時(shí)能增加成員變量么?能增加屬性么?如果能,如何增加?如果不能,為什么?
#####5、objc中向一個(gè)nil對(duì)象發(fā)送消息將會(huì)發(fā)生什么?(返回值是對(duì)象,是標(biāo)量,結(jié)構(gòu)體)那么,回到本題,如果向一個(gè)nil對(duì)象發(fā)送消息,首先在尋找對(duì)象的isa指針時(shí)就是0地址返回了,所以不會(huì)出現(xiàn)任何錯(cuò)誤。
####**高級(jí)**#####
1、UITableview的優(yōu)化方法(緩存高度,異步繪制,減少層級(jí),hide,避免離屏渲染###1.tableview性能優(yōu)化
####1.1cell重用 #####?
1.1.1 數(shù)據(jù)源方法優(yōu)化
```objective-c(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath```
?在可見的頁(yè)面會(huì)重復(fù)繪制頁(yè)面,每次刷新顯示都會(huì)去創(chuàng)建新的Cell,非常耗費(fèi)性能。 解決方案:首先創(chuàng)建一個(gè)靜態(tài)變量reuseID(代理方法返回Cell會(huì)調(diào)用很多次,防止重復(fù)創(chuàng)建,static保證只會(huì)被創(chuàng)建一次,提高性能),然后,從緩存池中取相應(yīng)identifier的Cell并更新數(shù)據(jù),如果沒有,才開始alloc新的Cell,并用identifier標(biāo)識(shí)Cell。每個(gè)Cell都會(huì)注冊(cè)一個(gè)identifier(重用標(biāo)識(shí)符)放入緩存池,當(dāng)需要調(diào)用的時(shí)候就直接從緩存池里找對(duì)應(yīng)的id,當(dāng)不需要時(shí)就放入緩存池等待調(diào)用。(移出屏幕的Cell才會(huì)放入緩存池中,并不會(huì)被release)所以在數(shù)據(jù)源方法中做出如下優(yōu)化:
```objective-c// 調(diào)用次數(shù)太多,static 保證只創(chuàng)建一次reuseID,提高性能static NSString *reuseID = “reuseCellID”;``````objective-c// 緩存池中取已經(jīng)創(chuàng)建的cellUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
```#####1.1.2 緩存池的實(shí)現(xiàn)當(dāng)Cell要alloc時(shí),UITableView會(huì)在堆中開辟一段內(nèi)存以供Cell緩存之用。Cell的重用通過(guò)identifier標(biāo)識(shí)不同類型的Cell,由此可以推斷出,緩存池外層可能是一個(gè)可變字典,通過(guò)key來(lái)取出內(nèi)部的Cell,而緩存池為存儲(chǔ)不同高度、不同類型(包含圖片、Label等)的Cell,可以推斷出緩存池的字典內(nèi)部可能是一個(gè)可變數(shù)組,用來(lái)存放不同類型的Cell,緩存池中只會(huì)保存已經(jīng)被移出屏幕的不同類型的Cell。
```objective-c-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; ```
這個(gè)方法會(huì)查詢可重用Cell,如果注冊(cè)了原型Cell,能夠查詢到,否則,返回nil;而且需要判斷if(cell == nil),才會(huì)創(chuàng)建Cell,不推薦```objective-c-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);``` 使用這個(gè)方法之前,必須通過(guò)xib(storyboard)或是Class(純代碼)注冊(cè)可重用Cell,而且這個(gè)方法一定會(huì)返回一個(gè)Cell注冊(cè)Cell```objective-c- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
```####1.2 定義一種(盡量少)類型的Cell及善用hidden隱藏(顯示)subviews
#####1.2.1 定義一種類型的 cell分析Cell結(jié)構(gòu),盡可能的將 相同內(nèi)容的抽取到一種樣式Cell中,前面已經(jīng)提到了Cell的重用機(jī)制,這樣就能保證UITbaleView要顯示多少內(nèi)容,真正創(chuàng)建出的Cell可能只比屏幕顯示的Cell多一點(diǎn)。雖然Cell的’體積’可能會(huì)大點(diǎn),但是因?yàn)镃ell的數(shù)量不會(huì)很多,完全可以接受的。好處:- 減少代碼量,減少Nib文件的數(shù)量,統(tǒng)一一個(gè)Nib文件定義Cell,容易修改、維護(hù)- 基于Cell的重用,真正運(yùn)行時(shí)鋪滿屏幕所需的Cell數(shù)量大致是固定的,設(shè)為N個(gè)。所以如果如果只有一種Cell,那就是只有N個(gè)Cell的實(shí)例;但是如果有M種Cell,那么運(yùn)行時(shí)最多可能會(huì)是“M x N = MN”個(gè)Cell的實(shí)例,雖然可能并不會(huì)占用太多內(nèi)存,但是能少點(diǎn)不是更好嗎。
#####1.2.2 善用hidden隱藏(顯示)subviews只定義一種Cell,那該如何顯示不同類型的內(nèi)容呢?答案就是,把所有不同類型的view都定義好,放在cell里面,通過(guò)hidden顯示、隱藏,來(lái)顯示不同類型的內(nèi)容。畢竟,在用戶快速滑動(dòng)中,只是單純的顯示、隱藏subview比實(shí)時(shí)創(chuàng)建要快得多。
#### 1.3 提前計(jì)算并緩存Cell的高度 在[iOS](http://lib.csdn.net/base/1)中,不設(shè)UITableViewCell的預(yù)估行高的情況下,會(huì)優(yōu)先調(diào)用”tableView:heightForRowAtIndexPath:”方法,獲取每個(gè)Cell的即將顯示的高度,從而確定UITableView的布局,實(shí)際就是要獲取contentSize(UITableView繼承自UIScrollView,只有獲取滾動(dòng)區(qū)域,才能實(shí)現(xiàn)滾動(dòng)),然后才調(diào)用”tableView:cellForRowAtIndexPath”,獲取每個(gè)Cell,進(jìn)行賦值。如果項(xiàng)目中模塊有10000個(gè)Cell需要顯示,可想而知…
?解決方案:我個(gè)人認(rèn)為,可以創(chuàng)建一個(gè)frame模型,提前計(jì)算每個(gè)Cell的高度。參考其中一篇博客的時(shí)候,在解決這個(gè)問(wèn)題的時(shí)候,可以將計(jì)算Cell的高度放入數(shù)據(jù)模型,但這與MVC設(shè)計(jì)模式可能稍微有點(diǎn)沖突,這個(gè)時(shí)候我就想到MVVM這種設(shè)計(jì)模式,這個(gè)時(shí)候才能稍微有點(diǎn)MVVM這種設(shè)計(jì)模式的優(yōu)點(diǎn)(其實(shí)還是很不理解的),可以講計(jì)算Cell高度放入ViewModel(視圖模型)中,讓Model(數(shù)據(jù)模型)只負(fù)責(zé)處理數(shù)據(jù)。 在上面的基礎(chǔ)上,還可以繼續(xù)進(jìn)行優(yōu)化,提前創(chuàng)建真正顯示的、需要加工的數(shù)據(jù)并緩存。不過(guò)這方面優(yōu)化我好像之前沒有接觸。大家可以去看看這篇博客,其實(shí)本篇性能優(yōu)化也借鑒了好多這篇文章,這是土土大神的博客地址,大家可以進(jìn)去看看,好多iOS開發(fā)的知識(shí),當(dāng)然也有我參照的這篇博客。[http://tutuge.me](http://tutuge.me/)
#### 1.4 異步繪制(自定義cell繪制) 遇到比較復(fù)雜的界面的時(shí)候,如復(fù)雜點(diǎn)的圖文混排,上面的那種優(yōu)化行高的方式可能就不能滿足要求了,當(dāng)然了,由于我的開發(fā)經(jīng)驗(yàn)尚短,說(shuō)實(shí)話,還沒遇到要將自定義的Cell重新繪制。至于這方面,大家可以參考這篇博客,絕對(duì)是開發(fā)經(jīng)驗(yàn)十足的大神,分享足夠多的UITableView方面的性能優(yōu)化,好多借鑒自這里,我都不好意思了。
####1.5.滑動(dòng)時(shí),按需加載 開發(fā)的過(guò)程中,自定義Cell的種類千奇百怪,但Cell本來(lái)就是用來(lái)顯示數(shù)據(jù)的,不說(shuō)100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過(guò)程中可能會(huì)有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會(huì)想到,但是如果給每個(gè)循環(huán)對(duì)象都加上異步加載,開啟的線程太多,一樣會(huì)卡頓,我記得好像線程條數(shù)一般3-5條,最多也就6條吧。這個(gè)時(shí)候利用UIScrollViewDelegate兩個(gè)代理方法就能很好地解決這個(gè)問(wèn)題。```objective-c- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView```思想就是識(shí)別UITableView禁止或者減速滑動(dòng)結(jié)束的時(shí)候,進(jìn)行異步加載圖片,快滑動(dòng)過(guò)程中,只加載目標(biāo)范圍內(nèi)的Cell,這樣按需加載,極大的提高流暢度。而SDWebImage可以實(shí)現(xiàn)異步加載,與這條性能配合就完美了,尤其是大量圖片展示的時(shí)候。而且也不用擔(dān)心圖片緩存會(huì)造成內(nèi)存警告的問(wèn)題。```objective-c//獲取可見部分的CellNSArray *visiblePaths = [self.tableView indexPathsForVisibleRows]; for (NSIndexPath *indexPath in visiblePaths) { //獲取的dataSource里面的對(duì)象,并且判斷加載完成的不需要再次異步加載}```記得在記得在“tableView:cellForRowAtIndexPath:”方法中加入判斷:```objective-c// tableView 停止滑動(dòng)的時(shí)候異步加載圖片- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ if (self.tableView.dragging == NO && self.tableView.decelerating == NO) { //開始異步加載圖片}}```
####1.6 緩存View 當(dāng)Cell中的部分View是非常獨(dú)立的,并且不便于重用的,而且“體積”非常小,在內(nèi)存可控的前提下,我們完全可以將這些view緩存起來(lái)。當(dāng)然也是緩存在模型中。
#### 1.7 避免大量的圖片縮放、顏色漸變等,盡量顯示“大小剛好合適的圖片資源”
####1.8 避免同步的從網(wǎng)絡(luò)、文件獲取數(shù)據(jù),Cell內(nèi)實(shí)現(xiàn)的內(nèi)容來(lái)自web,使用異步加載,緩存請(qǐng)求結(jié)果
####1.9 渲染 #####?
1.9.1 減少subviews的個(gè)數(shù)和層級(jí)? 子控件的層級(jí)越深,渲染到屏幕上所需要的計(jì)算量就越大;如多用drawRect繪制元素,替代用view顯示
?##### 1. 9.2 少用subviews的透明圖層? 對(duì)于不透明的View,設(shè)置opaque為YES,這樣在繪制該View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置Cell的view為opaque,避免GPU對(duì)Cell下面的內(nèi)容也進(jìn)行繪制)?
?##### 1.9.3 避免CALayer特效(shadowPath)? 給Cell中View加陰影會(huì)引起性能問(wèn)題,如下面代碼會(huì)導(dǎo)致滾動(dòng)時(shí)有明顯的卡頓:```objective-cview.layer.shadowColor = color.CGColor;view.layer.shadowOffset = offset;view.layer.shadowOpacity = 1;view.layer.shadowRadius = radius;```
#### 總結(jié):UITableView的優(yōu)化主要從三個(gè)方面入手:- 提前計(jì)算并緩存好高度(布局),因?yàn)閔eightForRowAtIndexPath:是調(diào)用最頻繁的方法;(這個(gè)是開發(fā)中肯定會(huì)要優(yōu)化的,不可能一個(gè)app就幾個(gè)Cell吧)- 滑動(dòng)時(shí)按需加載,防止卡頓,這個(gè)我也認(rèn)為是很有必要做的性能優(yōu)化,配合SDWebImage- 異步繪制,遇到復(fù)雜界面,遇到性能瓶頸時(shí),可能就是突破口(如題,遇到復(fù)雜的界面,可以從這入手)- 緩存一切可以緩存的,這個(gè)在開發(fā)的時(shí)候,往往是性能優(yōu)化最多的方向[優(yōu)化UITableViewCell高度計(jì)算的那些事](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)
##### 2、有沒有用過(guò)運(yùn)行時(shí),用它都能做什么?(交換方法,創(chuàng)建類,給新創(chuàng)建的類增加方法,改變isa指針)
##### 3、看過(guò)哪些第三方框架的源碼?都是如何實(shí)現(xiàn)的?(如果沒有,問(wèn)一下多圖下載的設(shè)計(jì))
##### 4、SDWebImage的緩存策略?**基本結(jié)構(gòu)**閑言少敘,咱們這就開始。 首先咱們來(lái)看看 SDWebImage 的整體結(jié)構(gòu):[](http://swiftcafe.io/articleimg/sdimage/1.png)有一個(gè)專門的 Cache 分類用來(lái)處理圖片的緩存。 這里面也有兩個(gè)類 SDImageCache 和 SDImageCacheConfig。 大部分的緩存處理都在 SDImageCache 這個(gè)類中實(shí)現(xiàn)。其他幾個(gè)文件夾咱們分別有個(gè)字的功能,因?yàn)樵蹅冞@次專門討論緩存策略,所以其他內(nèi)容暫時(shí)略過(guò)。**Memory 和 Disk 雙緩存**首先,SDWebImage 的圖片緩存采用的是 Memory 和 Disk 雙重 Cache 機(jī)制, 聽起來(lái)挺高大上吧。其實(shí)也不復(fù)雜。 我們先來(lái)看看 Memory Cache,貼一段 SDImageCache 的代碼:```objective-c@interface SDImageCache ()#pragma mark - Properties@property (strong, nonatomic, nonnull) NSCache *memCache;...```這里我們發(fā)現(xiàn), 有一個(gè)叫做 memCache 的屬性,它是一個(gè) NSCache 對(duì)象,用于實(shí)現(xiàn)我們對(duì)圖片的 Memory Cache。 SDWebImage 還專門實(shí)現(xiàn)了一個(gè)叫做 AutoPurgeCache 的類, 相比于普通的 NSCache, 它提供了一個(gè)在內(nèi)存緊張時(shí)候釋放緩存的能力:```objective-c@interface AutoPurgeCache : NSCache@end@implementation AutoPurgeCache- (nonnull instancetype)init { self = [super init]; if (self) {#if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];#endif } return self;}```其實(shí)就是接受系統(tǒng)的內(nèi)存警告通知,然后清除掉自身的圖片緩存。 這里大家比較少見的一個(gè)類應(yīng)該是 NSCache 了。 簡(jiǎn)單來(lái)說(shuō),它是一個(gè)類似于 NSDictionary 的集合類,用于在內(nèi)存中存儲(chǔ)我們要緩存的數(shù)據(jù)。詳細(xì)信息大家可以參考官方文檔:。說(shuō)完 Memory Cache, 我們?cè)賮?lái)說(shuō)說(shuō) Disk Cache,也就是文件緩存。 SDWebImage 會(huì)將圖片存放到 NSCachesDirectory 目錄中:```objective-c- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace { NSArray*paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); return [paths[0] stringByAppendingPathComponent:fullNamespace];}```然后為每一個(gè)緩存文件生成一個(gè) md5 文件名, 存放到文件中。**整體機(jī)制**為了節(jié)約篇幅,提升大家的閱讀體驗(yàn),這里盡量少貼大段代碼。 我們前面介紹了 SDWebImage 同時(shí)使用內(nèi)存和硬盤兩種緩存。 那么我們來(lái)看看當(dāng)使用 SDWebImage 讀取圖片時(shí)候的完整流程。 我們一般會(huì)使用 SDWebImage 對(duì) UIKit 的擴(kuò)展,直接加載圖片:```objective-c[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcode.jpg"]];```首先這個(gè) Category 方法 sd_setImageWithURL 內(nèi)部會(huì)調(diào)用 SDWebImageManager 的 downloadImageWithURL 方法來(lái)處理這個(gè)圖片 URL:```objective-cidoperation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { ...}];```SDWebImageManager 內(nèi)部的 downloadImageWithURL 方法會(huì)先使用我們前面提到的 SDImageCache 類的 queryDiskCacheForKey 方法,查詢圖片緩存:```objective-coperation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {...}];```再來(lái)看 queryDiskCacheForKey 方法內(nèi)部, 先會(huì)查詢 Memory Cache :```objective-cUIImage *image = [self imageFromMemoryCacheForKey:key];if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil;}```如果 Memory Cache 查找不到, 就會(huì)查詢 Disk Cache: ```objective-cdispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); }});```查詢 Disk Cache 的時(shí)候有一個(gè)小插曲,就是如果 Disk Cache 查詢成功,還會(huì)把得到的圖片再次設(shè)置到 Memory Cache 中。 這樣做可以最大化那些高頻率展現(xiàn)圖片的效率。如果緩存查詢成功, 那么就會(huì)直接返回緩存數(shù)據(jù)。 如果不成功,接下來(lái)就開始請(qǐng)求網(wǎng)絡(luò):```objective-cid subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
}
```
請(qǐng)求網(wǎng)絡(luò)使用的是 imageDownloader 屬性,這個(gè)示例專門負(fù)責(zé)下載圖片數(shù)據(jù)。 如果下載失敗, 會(huì)把失敗的圖片地址寫入 failedURLs 集合:
```objective-c
if (? error.code != NSURLErrorNotConnectedToInternet
? ? && error.code != NSURLErrorCancelled
? ? && error.code != NSURLErrorTimedOut
? ? && error.code != NSURLErrorInternationalRoamingOff
? ? && error.code != NSURLErrorDataNotAllowed
? ? && error.code != NSURLErrorCannotFindHost
? ? && error.code != NSURLErrorCannotConnectToHost) {
? ? @synchronized (self.failedURLs) {
? ? ? ? [self.failedURLs addObject:url];
? ? }
}
```
為什么要有這個(gè) failedURLs 呢, 因?yàn)?SDWebImage 默認(rèn)會(huì)有一個(gè)對(duì)上次加載失敗的圖片拒絕再次加載的機(jī)制。 也就是說(shuō),一張圖片在本次會(huì)話加載失敗了,如果再次加載就會(huì)直接拒絕。SDWebImage 這樣做可能是為了提高性能吧。這個(gè)機(jī)制可能會(huì)容易被大家忽略,所以這里特意提一下,說(shuō)不定哪天遇到一些奇怪問(wèn)題時(shí)候,這個(gè)知識(shí)點(diǎn)會(huì)幫你快速定位問(wèn)題~
如果下載圖片成功了,接下來(lái)就會(huì)使用 [self.imageCache storeImage] 方法將它寫入緩存,并且調(diào)用 completedBlock 告訴前端顯示圖片:
```objective-c
if (downloadedImage && finished) {
? ? [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
? ? if (strongOperation && !strongOperation.isCancelled) {
? ? ? ? completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
? ? }
});
```
好了,到此為止 SDWebImage 的整體圖片加載流程就都走完了。 由于要控制篇幅,我這里只挑了最重點(diǎn)的幾個(gè)節(jié)點(diǎn)寫出來(lái),SDWebImage 的完整機(jī)制肯定會(huì)更全面,先幫大家疏通思路。
**是否要重試失敗的 URL**
SDWebImage 的整體圖片處理流程咱們體驗(yàn)了一遍。 那么有哪些重要的點(diǎn)對(duì)咱們使用它會(huì)有幫助呢? 我?guī)痛蠹艺砹艘恍?/p>
你可以在加載圖片的時(shí)候設(shè)置 SDWebImageRetryFailed 標(biāo)記,這樣 SDWebImage 就會(huì)加載之前失敗過(guò)的圖片了。 記得我們前面提到的 failedURLs 屬性了吧,這個(gè)屬性是在內(nèi)存中存儲(chǔ)的,如果圖片加載失敗, SDWebImage 會(huì)在本次 APP 會(huì)話中都不再重試這張圖片了。當(dāng)然這個(gè)加載失敗是有條件的,如果是超時(shí)失敗,不記在內(nèi)。
總之,如果你更需要圖片的可用性,而不是這一點(diǎn)點(diǎn)的性能優(yōu)化,那么你就可以帶上 SDWebImageRetryFailed 標(biāo)記:
```objective-c
[_image sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcodexx.jpg"] placeholder:]
```
**Disk 緩存清理策略**
SDWebImage 會(huì)在每次 APP 結(jié)束的時(shí)候執(zhí)行清理任務(wù)。 清理緩存的規(guī)則分兩步進(jìn)行。 第一步先清除掉過(guò)期的緩存文件。 如果清除掉過(guò)期的緩存之后,空間還不夠。 那么就繼續(xù)按文件時(shí)間從早到晚排序,先清除最早的緩存文件,直到剩余空間達(dá)到要求。
具體點(diǎn),SDWebImage 是怎么控制哪些緩存過(guò)期,以及剩余空間多少才夠呢? 通過(guò)兩個(gè)屬性:
```objective-c
@interface SDImageCache : NSObject
@property (assign, nonatomic) NSInteger maxCacheAge;
@property (assign, nonatomic) NSUInteger maxCacheSize;
```
maxCacheAge 是文件緩存的時(shí)長(zhǎng), SDWebImage 會(huì)注冊(cè)兩個(gè)通知:
```objective-c
[[NSNotificationCenter defaultCenter] addObserver:self
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selector:@selector(cleanDisk)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? name:UIApplicationWillTerminateNotification
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selector:@selector(backgroundCleanDisk)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? name:UIApplicationDidEnterBackgroundNotification
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? object:nil];
```
分別在應(yīng)用進(jìn)入后臺(tái)和結(jié)束的時(shí)候,遍歷所有的緩存文件,如果緩存文件超過(guò) maxCacheAge 中指定的時(shí)長(zhǎng),就會(huì)被刪除掉。
同樣的, maxCacheSize 控制 SDImageCache 所允許的最大緩存空間。 如果清理完過(guò)期文件后緩存空間依然沒達(dá)到 maxCacheSize 的要求, 那么就會(huì)繼續(xù)清理舊文件,直到緩存空間達(dá)到要求為止。
了解了這個(gè)機(jī)制對(duì)我們有什么幫助呢? 我們來(lái)繼續(xù)講解,我們平時(shí)在使用 SDWebImage 的時(shí)候是沒接觸過(guò)它們的。那么以此推理,它們一定有默認(rèn)值,也確實(shí)有:
```objective-c
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
```
上面是 maxCacheAge 的默認(rèn)值,注釋上寫的很清楚,緩存一周。 再來(lái)看看 maxCacheSize。 翻了一遍 SDWebImage 的代碼,并沒有對(duì) maxCacheSize 設(shè)置默認(rèn)值。 這就意味著 SDWebImage 在默認(rèn)情況下不會(huì)對(duì)緩存空間設(shè)限制。
這一點(diǎn)可以在 SDWebImage 清理緩存的代碼中求證:
```objective-c
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
//清理緩存代碼
}
```
說(shuō)明一下, 上面代碼中的 currentCacheSize 變量代表當(dāng)前圖片緩存占用的空間。 從這里可以看出, 只有在 maxCacheSize 大于 0 并且當(dāng)前緩存空間大于 maxCacheSize 的時(shí)候才進(jìn)行第二步的緩存清理。
這意味著什么呢? 其實(shí)就是 SDWebImage 在默認(rèn)情況下是不對(duì)我們的緩存大小設(shè)限制的,理論上,APP 中的圖片緩存可以占滿整個(gè)設(shè)備。
SDWebImage 的這個(gè)特性還是比較容易被大家忽略的,如果你開發(fā)的類似信息流的 APP,應(yīng)該會(huì)加載大量的圖片,如果這時(shí)候按照默認(rèn)機(jī)制,緩存尺寸是沒有限制的,并且默認(rèn)的緩存周期是一周。 就很容易造成應(yīng)用存儲(chǔ)空間占用偏大的問(wèn)題。
那么可能有人會(huì)說(shuō)了,現(xiàn)在 iPhone 的存儲(chǔ)空間都很大,多緩存一點(diǎn)也不是問(wèn)題吧? 但你是否知道 iOS 上還有一個(gè)用量查詢的功能呢。在設(shè)置項(xiàng)中用戶可以查看每個(gè) APP 的空間使用情況, 如果你的 APP 占用空間比較大的話,就很容易成為用戶的卸載目標(biāo),這應(yīng)該是需要關(guān)注的一個(gè)細(xì)節(jié)。
另外,過(guò)多的占用緩存空間其實(shí)并不一定有用。大部分情況是一些圖片被緩存下來(lái)后,很少再被重復(fù)展現(xiàn)。所以合理的規(guī)劃緩存空間尺寸還是很有必要的。可以這樣設(shè)置:
```objective-c
[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M
```
maxCacheSize 是以字節(jié)來(lái)表示的,我們上面的計(jì)算代表 50M 的最大緩存空間。 把這行代碼寫在你的 APP 啟動(dòng)的時(shí)候,這樣 SDWebImage 在清理緩存的時(shí)候,就會(huì)清理多余的緩存文件了。
##### 5、AFN為什么添加一條常駐線程?
##### 6、KVO的使用?實(shí)現(xiàn)原理?(為什么要?jiǎng)?chuàng)建子類來(lái)實(shí)現(xiàn))
##### 7、KVC的使用?實(shí)現(xiàn)原理?(KVC拿到key以后,是如何賦值的?知不知道集合操作符,能不能訪問(wèn)私有屬性,能不能直接訪問(wèn)_ivar)
### 2 寫一個(gè)斐波那契函數(shù)
使用python實(shí)現(xiàn):
```python
def fibs(n):
result = [0,1]
for i in range(n-2):
? ? ? result.append(result[-2]+result[-1])
? ? ? ? return result
def fibs1(n):
? a,b = 0 ,1
? while a < n:
? ? print(a)
? ? a ,b = b ,a+b
```
### 3. HTTP協(xié)議和HTTPS websocket
#### 3.1? HTTP協(xié)議簡(jiǎn)介
HTTP是Hyper Text Transfer Protocol(超文本傳輸協(xié)議)的縮寫,現(xiàn)在使用的協(xié)議版本是1.1(http0.9已經(jīng)成為過(guò)去,http2.0尚在開發(fā)中)。
#### 3.2? 在TCP/IP協(xié)議棧中的位置
HTTP協(xié)議通常承載于TCP協(xié)議之上,有時(shí)也承載于TLS或SSL協(xié)議層之上,這個(gè)時(shí)候,就成了我們常說(shuō)的HTTPS。如下圖所示默認(rèn)HTTP的端口號(hào)為80,HTTPS的端口號(hào)為443。
#### 3.3 TCP/IP通信傳輸流
|? ? ? | 客戶端? | 服務(wù)端? |
| :---: | :--: | :--: |
|? 應(yīng)用層? | HTTP | HTTP |
|? 傳輸層? | TCP? | TCP? |
|? 網(wǎng)絡(luò)層? |? IP? |? IP? |
| 數(shù)據(jù)鏈路層 |? 網(wǎng)絡(luò)? |? 網(wǎng)絡(luò)? |
在進(jìn)行網(wǎng)絡(luò)通信時(shí),會(huì)通過(guò)分層順序與對(duì)方進(jìn)行通信,發(fā)送端從應(yīng)用層往下走,接收端則從數(shù)據(jù)鏈路層往上走。發(fā)送端每經(jīng)過(guò)一層傳輸數(shù)據(jù)時(shí),就會(huì)被打上對(duì)應(yīng)層所屬的首部信息進(jìn)行封裝,接收端在層與層之間傳輸數(shù)據(jù)時(shí),每經(jīng)過(guò)一層就會(huì)把對(duì)應(yīng)的首部去掉。
#####在傳輸數(shù)據(jù)時(shí),http主要承載的協(xié)議是IP、TCP、DNS
IP層主要用于網(wǎng)絡(luò)傳輸,確保數(shù)據(jù)能夠正確傳輸給對(duì)方,在這里就會(huì)用到IP地址和Mac地址,IP和mac地址是相互綁定和映射的關(guān)系,傳輸過(guò)程中,使用ARP協(xié)議就可以通過(guò)IP地址反查出Mac地址。為了將數(shù)據(jù)準(zhǔn)確可靠的傳給對(duì)方,我們使用傳輸層的TCP協(xié)議(UDP協(xié)議不可靠),TCP協(xié)議通過(guò)三次握手策略進(jìn)行傳輸確認(rèn),(發(fā)送端)SYN—(接收端)SYN/ACK—(發(fā)送端)ACK,代表握手成功。DNS服務(wù)和http協(xié)議一樣位于應(yīng)用層 ,提供域名到IP地址之間的解析服務(wù)。
#### 3.4 HTTP是不保存狀態(tài)的協(xié)議
即無(wú)狀態(tài)協(xié)議,對(duì)請(qǐng)求和響應(yīng)之間的通信狀態(tài)不盡興保存。每當(dāng)有新的請(qǐng)求發(fā)送,就會(huì)有新的響應(yīng)產(chǎn)生。
####3.5 HTTP請(qǐng)求方式
|? 請(qǐng)求方式? |? 作用? |
| :----: | :----: |
|? GET? |? 獲取資源? |
|? POST? | 傳輸實(shí)體主題 |
|? PUT? |? 傳輸文件? |
| DELETE |? 刪除文件? |
GET和POST的區(qū)別:
HTTP超文本傳輸協(xié)議,是短連接,是客戶端主動(dòng)發(fā)送請(qǐng)求,服務(wù)器做出響應(yīng),服務(wù)器響應(yīng)之后,鏈接斷開。HTTP是一個(gè)屬于應(yīng)用層面向?qū)ο蟮膮f(xié)議,HTTP有兩類報(bào)文:請(qǐng)求報(bào)文和響應(yīng)報(bào)文。
HTTP請(qǐng)求報(bào)文:一個(gè)HTTP請(qǐng)求報(bào)文由請(qǐng)求行、請(qǐng)求頭部、空行和請(qǐng)求數(shù)據(jù)4部分組成。
HTTP響應(yīng)報(bào)文:由三部分組成:狀態(tài)行、消息報(bào)頭、響應(yīng)正文。
GET請(qǐng)求:參數(shù)在地址后拼接,沒有請(qǐng)求數(shù)據(jù),不安全(因?yàn)樗袇?shù)都拼接在地址后面),不適合傳輸大量數(shù)據(jù)(長(zhǎng)度有限制,為1024個(gè)字節(jié))。
GET提交、請(qǐng)求的數(shù)據(jù)會(huì)附在URL之后,即把數(shù)據(jù)放置在HTTP協(xié)議頭中。
以?分割URL和傳輸數(shù)據(jù),多個(gè)參數(shù)用&連接。如果數(shù)據(jù)是英文字母或數(shù)字,原樣發(fā)送,
如果是空格,轉(zhuǎn)換為+,如果是中文/其他字符,則直接把字符串用BASE64加密。
POST請(qǐng)求:參數(shù)在請(qǐng)求數(shù)據(jù)區(qū)放著,相對(duì)GET請(qǐng)求更安全,并且數(shù)據(jù)大小沒有限制。把提交的數(shù)據(jù)放置在HTTP包的包體中.
GET提交的數(shù)據(jù)會(huì)在地址欄顯示出來(lái),而POST提交,地址欄不會(huì)改變。
傳輸數(shù)據(jù)的大小:
GET提交時(shí),傳輸數(shù)據(jù)就會(huì)受到URL長(zhǎng)度限制,POST由于不是通過(guò)URL傳值,理論上書不受限。
安全性:
POST的安全性要比GET的安全性高;
通過(guò)GET提交數(shù)據(jù),用戶名和密碼將明文出現(xiàn)在URL上,比如登陸界面有可能被瀏覽器緩存。
####3.6 HTTP狀態(tài)碼
當(dāng)瀏覽者訪問(wèn)一個(gè)網(wǎng)頁(yè)時(shí),瀏覽者的瀏覽器會(huì)向網(wǎng)頁(yè)所在服務(wù)器發(fā)出請(qǐng)求。當(dāng)瀏覽器接收并顯示網(wǎng)頁(yè)前,此網(wǎng)頁(yè)所在的服務(wù)器會(huì)返回一個(gè)包含HTTP狀態(tài)碼的信息頭(server header)用以響應(yīng)瀏覽器的請(qǐng)求。
HTTP狀態(tài)碼的英文為HTTP Status Code。
下面是常見的HTTP狀態(tài)碼:
- 200 - 請(qǐng)求成功
- 301 - 資源(網(wǎng)頁(yè)等)被永久轉(zhuǎn)移到其它URL
- 404 - 請(qǐng)求的資源(網(wǎng)頁(yè)等)不存在
- 500 - 內(nèi)部服務(wù)器錯(cuò)誤
#####HTTP狀態(tài)碼分類
HTTP狀態(tài)碼由三個(gè)十進(jìn)制數(shù)字組成,第一個(gè)十進(jìn)制數(shù)字定義了狀態(tài)碼的類型,后兩個(gè)數(shù)字沒有分類的作用。HTTP狀態(tài)碼共分為5種類型:
| 分類? | 分類描述? ? ? ? ? ? ? ? ? ? |
| ---- | ----------------------- |
| 1**? | 信息,服務(wù)器收到請(qǐng)求,需要請(qǐng)求者繼續(xù)執(zhí)行操作? |
| 2**? | 成功,操作被成功接收并處理? ? ? ? ? |
| 3**? | 重定向,需要進(jìn)一步的操作以完成請(qǐng)求? ? ? |
| 4**? | 客戶端錯(cuò)誤,請(qǐng)求包含語(yǔ)法錯(cuò)誤或無(wú)法完成請(qǐng)求? |
| 5**? | 服務(wù)器錯(cuò)誤,服務(wù)器在處理請(qǐng)求的過(guò)程中發(fā)生了錯(cuò)誤 |
#### 3.7 確保Web安全的HTTPS
##### 3.7.1 HTTP 的缺點(diǎn)
1. 通信使用明文(不加密),內(nèi)容可能會(huì)被竊聽
2. 不驗(yàn)證通信方的身份,因此有可能遭遇偽裝
3. 無(wú)法證明報(bào)文的完整性,所以有可能已遭篡改
##### 3.7.2 HTTP+加密+認(rèn)證+完整性保護(hù)=HTTPS
HTTPS并非是一種新的協(xié)議,只是HTTP通信接口部分用SSL和TLS協(xié)議代替而已。通常,HTTP直接和TCP通信。當(dāng)使用SSL時(shí),則演變成先和SSL通信,再由SSL和TCP通信,所謂HTTPS,實(shí)際上就是身披SSL協(xié)議的外殼的HTTP。
| 應(yīng)用(HTTP) | 應(yīng)用(HTTPS) |
| :------: | :-------: |
|? ? ? ? ? |? ? SSL? ? |
|? TCP? ? |? ? TCP? ? |
|? ? IP? ? |? ? IP? ? |
|? HTTP? |? HTTPS? |
采用SSL后,HTTP就擁有了HTTPS的加密、證書和完整性保護(hù)這些功能。HTTPS采用共享密鑰加密(對(duì)稱密鑰加密)和公開密鑰加密(非對(duì)稱加密)兩者并用的混合加密機(jī)制。在交換密鑰環(huán)節(jié)使用公開密鑰加密方式,之后建立通信交換報(bào)文階段則使用共享密鑰加密方式。公開密鑰加密處理起來(lái)比共享密鑰方式更為復(fù)雜,因此若在通信時(shí)使用公開密鑰加密方式,效率很低。
> ? 共享密鑰加密方式加密和解密公用一個(gè)密鑰的方式,公開密鑰非對(duì)稱,一把叫做私用密鑰,另一把叫做公開密鑰。發(fā)送密鑰方使用公開密鑰,接收方收到加密信息后使用私鑰對(duì)數(shù)據(jù)進(jìn)行解密。
##### 3.7.3 HTTPS的安全通信機(jī)制
HTTPS通過(guò)十次握手確立SSL連接成功,連接建立后,通信受到SSL的保護(hù),從此開始進(jìn)行應(yīng)用層協(xié)議的通信,發(fā)送HTTP請(qǐng)求。
當(dāng)然HTTPS也存在一些問(wèn)題
當(dāng)使用SSL時(shí),它的處理速度會(huì)變慢。由于HTTPS還需要做服務(wù)器、客戶端雙方加密及解密處理,因此會(huì)消耗CPU和內(nèi)存等硬件資源。
#### 3.8 使用瀏覽器進(jìn)行全雙工通信的WebSocket
WebSocket,即Web瀏覽器與Web服務(wù)器之間全雙工通信標(biāo)準(zhǔn)。一旦Web服務(wù)器與客戶端之間建立起WebSocket協(xié)議的通信連接,之后所有的通信都依靠這個(gè)專用協(xié)議進(jìn)行。通信過(guò)程中可以互發(fā)XML,JSON,HTML,圖片等任一格式的數(shù)據(jù)。任何一方都可以向?qū)Ψ桨l(fā)送報(bào)文。
##### 3.8.1 推送功能
支持服務(wù)器向客戶端推送數(shù)據(jù)的推送功能,服務(wù)器可直接發(fā)送數(shù)據(jù),不用等客戶端的請(qǐng)求。
##### 3.8.2 減少通信量
只要建立起WebSocket連接,就會(huì)一直保持連接狀態(tài),和HTTP相比,不但每次連接的總開銷少了,而且WebSocket的首部信息很小,通信量也相應(yīng)減少了。
```http
GET /chat HTTP/1.1
Host:Server.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:Dhgersaferasftgsdsgswe==
Origin:http://example.com
Sec-WebSocket-Protocol:chat,superchat
Sec-WebSocket-Version:13
```
為了實(shí)現(xiàn)WebSocket通信,在HTTP連接建立之后,需要完成一次握手;要用到HTTP的Upgrade首部字段,告知服務(wù)器通信協(xié)議發(fā)生改變,以達(dá)到握手目的。成功握手確立WebSocket連接之后,通信不再使用HTTP的數(shù)據(jù),而采用WebSocket獨(dú)立的數(shù)據(jù)幀。