一、類別
OC不像C++等高級語言能直接繼承多個類,不過OC可以使用類別和協(xié)議來實現(xiàn)多繼承。
1、類別加載時機
在App加載時,Runtime會把Category的實例方法、協(xié)議以及屬性添加到類上;把Category的類方法添加到類的metaclass上。
2、類別添加屬性、方法
1)在類別中不能直接以@property的方式定義屬性,OC不會主動給類別屬性生成setter和getter方法;需要通過objc_setAssociatedObject來實現(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;
}
2)類別同名方法覆蓋問題
- 如果類別和主類都有名叫funA的方法,那么在類別加載完成之后,類的方法列表里會有兩個funA;
- 類別的方法被放到了新方法列表的前面,而主類的方法被放到了新方法列表的后面,這就造成了類別方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法,就會停止查找,殊不知后面可能還有一樣名字的方法;
- 如果多個類別定義了同名方法funA,具體調(diào)用哪個類別的實現(xiàn)由編譯順序決定,后編譯的類別的實現(xiàn)將被調(diào)用。
- 在日常開發(fā)過程中,類別方法重名輕則造成調(diào)用不正確,重則造成crash,我們可以通過給類別方法名加前綴避免方法重名。
關于類別更深入的解析可以參見美團的技術文章深入理解Objective-C:Category
二、協(xié)議
定義
iOS中的協(xié)議類似于Java、C++中的接口類,協(xié)議在OC中可以用來實現(xiàn)多繼承和代理。
方法聲明
協(xié)議中的方法可以聲明為@required(要求實現(xiàn),如果沒有實現(xiàn),會發(fā)出警告,但編譯不報錯)或者@optional(不要求實現(xiàn),不實現(xiàn)也不會有警告)。
筆者經(jīng)常會問面試者如下兩個問題:
-怎么判斷一個類是否實現(xiàn)了某個協(xié)議?很多人不知道可以通過conformsToProtocol來判斷。
-假如你要求業(yè)務方實現(xiàn)一個delegate,你怎么判斷業(yè)務方有沒有實現(xiàn)dalegate的某個方法?很多人不知道可以通過respondsToSelector來判斷。
三、通知中心
iOS中的通知中心實際上是觀察者模式的一種實現(xiàn)。
postNotification是同步調(diào)用還是異步調(diào)用?
同步調(diào)用。當調(diào)用addObserver方法監(jiān)聽通知,然后調(diào)用postNotification拋通知,postNotification會在當前線程遍歷所有的觀察者,然后依次調(diào)用觀察者的監(jiān)聽方法,調(diào)用完成后才會去執(zhí)行postNotification后面的代碼。
如何實現(xiàn)異步監(jiān)聽通知?
通過addObserverForName:object:queue:usingBlock來實現(xiàn)異步通知。
四、KVC
KVC查找順序
1)調(diào)用setValue:forKey時候,比如[obj setValue:@"akon" forKey:@"key"]時候,會按照_key,_iskey,key,iskey的順序搜索成員并進行賦值操作。如果都沒找到,系統(tǒng)會調(diào)用該對象的setValue:forUndefinedKey方法,該方法默認是拋出異常。
2)當調(diào)用valueForKey:@"key"的代碼時,KVC對key的搜索方式不同于setValue"akon" forKey:@"key",其搜索方式如下:
- 首先按get, is的順序查找getter方法,找到的話會直接調(diào)用。如果是BOOL或者Int等值類型,會將其包裝成一個NSNumber對象。
- 如果沒有找到,KVC則會查找countOf、objectInAtIndex或AtIndexes格式的方法。如果countOf方法和另外兩個方法中的一個被找到,那么就會返回一個可以響應NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調(diào)
用這個代理集合的方法,就會以countOf,objectInAtIndex或AtIndexes這幾個方法組合的形式調(diào)用。還有一個可選的get:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標準命名方法,包括方法簽名。
-如果上面的方法沒有找到,那么會同時查找countOf,enumeratorOf,memberOf格式的方法。如果這三個方法都找到,那么就返回一個可以響應NSSet所的方法的代理集合,和上面一樣,給這個代理集合發(fā)NSSet的消息,就會以countOf,enumeratorOf,memberOf組合的形式調(diào)用。 - 如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為),那么和先前的設值一樣,會按_,_is,,is的順序搜索成員變量名。
- 如果還沒找到,直接調(diào)用該對象的valueForUndefinedKey:方法,該方法默認是拋出異常。
KVC防崩潰
我們經(jīng)常會使用KVC來設置屬性和獲取屬性,但是如果對象沒有按照KVC的規(guī)則聲明該屬性,則會造成crash,怎么全局通用地防止這類崩潰呢?
可以通過寫一個NSObject分類來防崩潰。
@interface NSObject(AKPreventKVCCrash)
@end
@ implementation NSObject(AKPreventKVCCrash)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
}
- (id)valueForUndefinedKey:(NSString *)key{
return nil;
}
@end
五、KVO
定義
KVO(Key-Value Observing),鍵值觀察。它是一種觀察者模式的衍生。其基本思想是,對目標對象的某屬性添加觀察,當該屬性發(fā)生變化時,通過觸發(fā)觀察者對象實現(xiàn)的KVO接口方法,來自動的通知觀察者。
注冊、移除KVO
通過如下兩個方案來注冊、移除KVO
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
通過observeValueForKeyPath來獲取值的變化。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
我們可以通過facebook開源庫KVOController方便地進行KVO。
KVO實現(xiàn)
蘋果官方文檔對KVO實現(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.
即當一個類型為 ObjectA 的對象,被添加了觀察后,系統(tǒng)會生成一個派生類 NSKVONotifying_ObjectA 類,并將對象的isa指針指向新的類,也就是說這個對象的類型發(fā)生了變化。因此在向ObjectA對象發(fā)送消息時候,實際上是發(fā)送到了派生類對象的方法。由于編譯器對派生類的方法進行了 override,并添加了通知代碼,因此會向注冊的對象發(fā)送通知。注意派生類只重寫注冊了觀察者的屬性方法。
關于kvc和kvo更深入的詳解參考iOS KVC和KVO詳解
六、autorelasepool
用處
在 ARC 下,我們不需要手動管理內(nèi)存,可以完全不知道 autorelease 的存在,就可以正確管理好內(nèi)存,因為 Runloop 在每個 Runloop Circle 中會自動創(chuàng)建和釋放Autorelease Pool。
當我們需要創(chuàng)建和銷毀大量的對象時,使用手動創(chuàng)建的 autoreleasepool 可以有效的避免內(nèi)存峰值的出現(xiàn)。因為如果不手動創(chuàng)建的話,外層系統(tǒng)創(chuàng)建的 pool 會在整個 Runloop Circle 結束之后才進行 drain,手動創(chuàng)建的話,會在 block 結束之后就進行 drain 操作,比如下面例子:
for (int i = 0; i < 100000; i++)
{
@autoreleasepool
{
NSString* string = @"akon";
NSArray* array = [string componentsSeparatedByString:string];
}
}
比如SDWebImage中這段代碼,由于encodedDataWithImage會把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];
}
Runloop中自動釋放池創(chuàng)建和釋放時機
- 系統(tǒng)在 Runloop 中創(chuàng)建的 autoreleaspool 會在 Runloop 一個 event 結束時進行釋放操作。
- 我們手動創(chuàng)建的 autoreleasepool 會在 block 執(zhí)行完成之后進行 drain 操作。需要注意的是:
當 block 以異常結束時,pool 不會被 drain
Pool 的 drain 操作會把所有標記為 autorelease 的對象的引用計數(shù)減一,但是并不意味著這個對象一定會被釋放掉,我們可以在 autorelease pool 中手動 retain 對象,以延長它的生命周期(在 MRC 中)。
資料推薦
如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流群931542608來獲取一份詳細的大廠面試資料為你的跳槽多添一份保障。
