2021年最新 iOS面試高級(jí)知識(shí)
一、類(lèi)別
OC不像C++等高級(jí)語(yǔ)言能直接繼承多個(gè)類(lèi),不過(guò)OC可以使用類(lèi)別和協(xié)議來(lái)實(shí)現(xiàn)多繼承。
1、類(lèi)別加載時(shí)機(jī)
在App加載時(shí),Runtime會(huì)把Category的實(shí)例方法、協(xié)議以及屬性添加到類(lèi)上;把Category的類(lèi)方法添加到類(lèi)的metaclass上。
2、類(lèi)別添加屬性、方法
1)在類(lèi)別中不能直接以@property的方式定義屬性,OC不會(huì)主動(dòng)給類(lèi)別屬性生成setter和getter方法;需要通過(guò)objc_setAssociatedObject來(lái)實(shí)現(xiàn)。
@interface TestClass(ak) @property(nonatomic,copy) NSString *name; @end @implementation TestClass (ak) - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY); } - (NSString*)name{ NSString *nameObject = objc_getAssociatedObject(self, "name"); return nameObject; }
作為一個(gè)開(kāi)發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS交流群:834688868,不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家一起交流學(xué)習(xí)成長(zhǎng)!
2)類(lèi)別同名方法覆蓋問(wèn)題
如果類(lèi)別和主類(lèi)都有名叫funA的方法,那么在類(lèi)別加載完成之后,類(lèi)的方法列表里會(huì)有兩個(gè)funA;
類(lèi)別的方法被放到了新方法列表的前面,而主類(lèi)的方法被放到了新方法列表的后面,這就造成了類(lèi)別方法會(huì)“覆蓋”掉原來(lái)類(lèi)的同名方法,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的,它只要一找到對(duì)應(yīng)名字的方法,就會(huì)停止查找,殊不知后面可能還有一樣名字的方法;
如果多個(gè)類(lèi)別定義了同名方法funA,具體調(diào)用哪個(gè)類(lèi)別的實(shí)現(xiàn)由編譯順序決定,后編譯的類(lèi)別的實(shí)現(xiàn)將被調(diào)用。
在日常開(kāi)發(fā)過(guò)程中,類(lèi)別方法重名輕則造成調(diào)用不正確,重則造成crash,我們可以通過(guò)給類(lèi)別方法名加前綴避免方法重名。
關(guān)于類(lèi)別更深入的解析可以參見(jiàn)美團(tuán)的技術(shù)文章深入理解Objective-C:Category
二、協(xié)議
定義
iOS中的協(xié)議類(lèi)似于Java、C++中的接口類(lèi),協(xié)議在OC中可以用來(lái)實(shí)現(xiàn)多繼承和代理。
方法聲明
協(xié)議中的方法可以聲明為@required(要求實(shí)現(xiàn),如果沒(méi)有實(shí)現(xiàn),會(huì)發(fā)出警告,但編譯不報(bào)錯(cuò))或者@optional(不要求實(shí)現(xiàn),不實(shí)現(xiàn)也不會(huì)有警告)。 筆者經(jīng)常會(huì)問(wèn)面試者如下兩個(gè)問(wèn)題: -怎么判斷一個(gè)類(lèi)是否實(shí)現(xiàn)了某個(gè)協(xié)議?很多人不知道可以通過(guò)conformsToProtocol來(lái)判斷。 -假如你要求業(yè)務(wù)方實(shí)現(xiàn)一個(gè)delegate,你怎么判斷業(yè)務(wù)方有沒(méi)有實(shí)現(xiàn)dalegate的某個(gè)方法?很多人不知道可以通過(guò)respondsToSelector來(lái)判斷。
三、通知中心
iOS中的通知中心實(shí)際上是觀察者模式的一種實(shí)現(xiàn)。
postNotification是同步調(diào)用還是異步調(diào)用?
同步調(diào)用。當(dāng)調(diào)用addObserver方法監(jiān)聽(tīng)通知,然后調(diào)用postNotification拋通知,postNotification會(huì)在當(dāng)前線程遍歷所有的觀察者,然后依次調(diào)用觀察者的監(jiān)聽(tīng)方法,調(diào)用完成后才會(huì)去執(zhí)行postNotification后面的代碼。
如何實(shí)現(xiàn)異步監(jiān)聽(tīng)通知?
通過(guò)addObserverForName:object:queue:usingBlock來(lái)實(shí)現(xiàn)異步通知。
四、KVC
KVC查找順序
1)調(diào)用setValue:forKey時(shí)候,比如[obj setValue:@"akon" forKey:@"key"]時(shí)候,會(huì)按照_key,_iskey,key,iskey的順序搜索成員并進(jìn)行賦值操作。如果都沒(méi)找到,系統(tǒng)會(huì)調(diào)用該對(duì)象的setValue:forUndefinedKey方法,該方法默認(rèn)是拋出異常。 2)當(dāng)調(diào)用valueForKey:@"key"的代碼時(shí),KVC對(duì)key的搜索方式不同于setValue"akon" forKey:@"key",其搜索方式如下:
首先按get, is的順序查找getter方法,找到的話(huà)會(huì)直接調(diào)用。如果是BOOL或者Int等值類(lèi)型,會(huì)將其包裝成一個(gè)NSNumber對(duì)象。
如果沒(méi)有找到,KVC則會(huì)查找countOf、objectInAtIndex或AtIndexes格式的方法。如果countOf方法和另外兩個(gè)方法中的一個(gè)被找到,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類(lèi)),調(diào) 用這個(gè)代理集合的方法,就會(huì)以countOf,objectInAtIndex或AtIndexes這幾個(gè)方法組合的形式調(diào)用。還有一個(gè)可選的get:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法,包括方法簽名。 -如果上面的方法沒(méi)有找到,那么會(huì)同時(shí)查找countOf,enumeratorOf,memberOf格式的方法。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所的方法的代理集合,和上面一樣,給這個(gè)代理集合發(fā)NSSet的消息,就會(huì)以countOf,enumeratorOf,memberOf組合的形式調(diào)用。
如果還沒(méi)有找到,再檢查類(lèi)方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為),那么和先前的設(shè)值一樣,會(huì)按_,_is,,is的順序搜索成員變量名。
如果還沒(méi)找到,直接調(diào)用該對(duì)象的valueForUndefinedKey:方法,該方法默認(rèn)是拋出異常。
KVC防崩潰
我們經(jīng)常會(huì)使用KVC來(lái)設(shè)置屬性和獲取屬性,但是如果對(duì)象沒(méi)有按照KVC的規(guī)則聲明該屬性,則會(huì)造成crash,怎么全局通用地防止這類(lèi)崩潰呢? 可以通過(guò)寫(xiě)一個(gè)NSObject分類(lèi)來(lái)防崩潰。
@interface NSObject(AKPreventKVCCrash) @end @ implementation NSObject(AKPreventKVCCrash) - (void)setValue:(id)value forUndefinedKey:(NSString *)key{ } - (id)valueForUndefinedKey:(NSString *)key{ return nil; } @end 復(fù)制代碼
五、KVO
定義
KVO(Key-Value Observing),鍵值觀察。它是一種觀察者模式的衍生。其基本思想是,對(duì)目標(biāo)對(duì)象的某屬性添加觀察,當(dāng)該屬性發(fā)生變化時(shí),通過(guò)觸發(fā)觀察者對(duì)象實(shí)現(xiàn)的KVO接口方法,來(lái)自動(dòng)的通知觀察者。
注冊(cè)、移除KVO
通過(guò)如下兩個(gè)方案來(lái)注冊(cè)、移除KVO
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; 復(fù)制代碼
通過(guò)observeValueForKeyPath來(lái)獲取值的變化。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 復(fù)制代碼
我們可以通過(guò)facebook開(kāi)源庫(kù)KVOController方便地進(jìn)行KVO。
KVO實(shí)現(xiàn)
蘋(píng)果官方文檔對(duì)KVO實(shí)現(xiàn)介紹如下:
Key-Value Observing Implementation Details Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
即當(dāng)一個(gè)類(lèi)型為 ObjectA 的對(duì)象,被添加了觀察后,系統(tǒng)會(huì)生成一個(gè)派生類(lèi) NSKVONotifying_ObjectA 類(lèi),并將對(duì)象的isa指針指向新的類(lèi),也就是說(shuō)這個(gè)對(duì)象的類(lèi)型發(fā)生了變化。因此在向ObjectA對(duì)象發(fā)送消息時(shí)候,實(shí)際上是發(fā)送到了派生類(lèi)對(duì)象的方法。由于編譯器對(duì)派生類(lèi)的方法進(jìn)行了 override,并添加了通知代碼,因此會(huì)向注冊(cè)的對(duì)象發(fā)送通知。注意派生類(lèi)只重寫(xiě)注冊(cè)了觀察者的屬性方法。
關(guān)于kvc和kvo更深入的詳解參考iOS KVC和KVO詳解
六、autorelasepool
用處
在 ARC 下,我們不需要手動(dòng)管理內(nèi)存,可以完全不知道 autorelease 的存在,就可以正確管理好內(nèi)存,因?yàn)?Runloop 在每個(gè) Runloop Circle 中會(huì)自動(dòng)創(chuàng)建和釋放Autorelease Pool。 當(dāng)我們需要?jiǎng)?chuàng)建和銷(xiāo)毀大量的對(duì)象時(shí),使用手動(dòng)創(chuàng)建的 autoreleasepool 可以有效的避免內(nèi)存峰值的出現(xiàn)。因?yàn)槿绻皇謩?dòng)創(chuàng)建的話(huà),外層系統(tǒng)創(chuàng)建的 pool 會(huì)在整個(gè) Runloop Circle 結(jié)束之后才進(jìn)行 drain,手動(dòng)創(chuàng)建的話(huà),會(huì)在 block 結(jié)束之后就進(jìn)行 drain 操作,比如下面例子:
for (int i = 0; i < 100000; i++) { @autoreleasepool { NSString* string = @"akon"; NSArray* array = [string componentsSeparatedByString:string]; } } 復(fù)制代碼
比如SDWebImage中這段代碼,由于encodedDataWithImage會(huì)把image解碼成data,可能造成內(nèi)存暴漲,所以加autoreleasepool避免內(nèi)存暴漲
@autoreleasepool { NSData *data = imageData; if (!data && image) { // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format SDImageFormat format; if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) { format = SDImageFormatPNG; } else { format = SDImageFormatJPEG; } data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil]; } [self _storeImageDataToDisk:data forKey:key]; } 復(fù)制代碼
Runloop中自動(dòng)釋放池創(chuàng)建和釋放時(shí)機(jī)
系統(tǒng)在 Runloop 中創(chuàng)建的 autoreleaspool 會(huì)在 Runloop 一個(gè) event 結(jié)束時(shí)進(jìn)行釋放操作。
我們手動(dòng)創(chuàng)建的 autoreleasepool 會(huì)在 block 執(zhí)行完成之后進(jìn)行 drain 操作。需要注意的是: 當(dāng) block 以異常結(jié)束時(shí),pool 不會(huì)被 drain Pool 的 drain 操作會(huì)把所有標(biāo)記為 autorelease 的對(duì)象的引用計(jì)數(shù)減一,但是并不意味著這個(gè)對(duì)象一定會(huì)被釋放掉,我們可以在 autorelease pool 中手動(dòng) retain 對(duì)象,以延長(zhǎng)它的生命周期(在 MRC 中)。
iOS
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶(hù)投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。