iOS文件補完計劃--NSObject

目錄
- NSObject類
- 類的初始化
- load
- initialize
- 建立、複製和銷燬
- alloc
- allocWithZone
- init
- new
- copy
- mutableCopy
- copyWithZone:
- mutableCopyWithZone:
- dealloc
- 類/物件的識別與判等
- class
- superclass
- hash
- isEqual
- isProxy
- isKindOfClass
- isMemberOfClass
- isSubclassOfClass:
- 類/物件的測試
- instancesRespondToSelector:
- conformsToProtocol:
- 獲取方法資訊
- methodForSelector:
- instanceMethodForSelector:
- 類/物件的描述
- debugDescription
- description
- 傳送訊息
- performSelector:withObject:afterDelay:
- performSelector:withObject:afterDelay:inModes:
- performSelectorOnMainThread:withObject:waitUntilDone:
- performSelectorOnMainThread:withObject:waitUntilDone:modes:
- performSelector:onThread:withObject:waitUntilDone:
- performSelector:onThread:withObject:waitUntilDone:modes:
- performSelectorInBackground:withObject:
- cancelPreviousPerformRequestsWithTarget:
- cancelPreviousPerformRequestsWithTarget:selector:object:
- 動態解析(訊息轉發)
- 解決階段
- resolveClassMethod
- resolveInstanceMethod
- 重定向(Fast Forwarding)
- forwardingTargetForSelector
- 訊息轉發(Normal Forwarding)
- methodSignatureForSelector
- instanceMethodSignatureForSelector
- forwardInvocation
- 錯誤處理
- doesNotRecognizeSelector:
- 解決階段
- Weak相關
- allowsWeakReference
- retainWeakReference
NSObject類/NSObject協議
幾乎所有OC物件都可以使用NSObject的方法、因為絕大部分OC物件都繼承者他。
NSObject協議
方法很多與NSObject的方法相同、只是從類方法( 為了簡便
)變成了物件方法/屬性這種形式。
並且、他也被 NSProxy
遵循、這點正體現了OC的多繼承。比如 NSObject
與 NSProxy
物件都可以使用isKindOfClass方法。
而類物件、也可以使用物件方法(大概是因為類物件也是一種物件吧)。
所以、下文中:
- 對於"-"的標記、本身就是可以作用於類物件的。
- 而"+"、但並不代表只能用於類物件。(NSObject協議中可能聲明瞭"-"的版本)。
比如[NSObject hash]、[[NSObject class] hash]、[[NSObject new] hash]
。
類的初始化
-
+ (void)load
程式執行時載入( 新增到Runtime中
)一個 Class
、或者 Category
時呼叫。
並且只會呼叫一次。
1. +(void)load
整個類最先被呼叫的方法
所以、對於 method swizzle
這種從一開始就希望起作用的操作、需要放在這裡。
2. 父類先於子類、主類優先於分類
需要注意的是如果子類沒有使用 +(void)load
方法、父類並不會被優先呼叫(也就是依舊按照 Compile Sources
的順序)。
由於這個規則存在、我們也不需要主動實現 [super load]
方法。
3. 與 Compile Sources
的關係
只要加入 Compile Sources
中、即使專案中沒有人對其 #import
也一樣會呼叫( 畢竟是動態語言
)。
預設的呼叫的順序、也與 Compile Sources
中的順序相同。

4. 不主動實現、就不會被呼叫
+ load
會按照模組被儲存在 loadable_classes
/ loadable_categories
結構體中。而後取出、並且通過C函式指標呼叫。
所以、也不會經過訊息轉發的過程( 子類沒實現、並不會呼叫父類
)。
詳情可以參考 ofollow,noindex">《iOS基礎(九) - load和initialize的實現原理》
5. 在 load
方法被自動呼叫之前、一個類仍然可以被使用
In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.
也就是你可以這樣寫、但我不知道有什麼意義~
@implementation Test + (void)load { [[Test3 new]hahaha]; }
-
+ (void)initialize [ɪ'nɪʃəlaɪz]
向一個類傳送第一條訊息前被呼叫、對於父類實現( 注意不是父類
)的呼叫可能不止一次。
1. 父類呼叫在子類之前
在本類initialize( callInitialize(cls)
)呼叫之前、如果父類沒被呼叫過、會主動呼叫一次。並且父類中也如此實現、也就是會遞迴呼叫。
void _class_initialize(Class cls) { assert(!cls->isMetaClass()); Class supercls; bool reallyInitialize = NO; // Make sure super is done initializing BEFORE beginning to initialize cls. // See note about deadlock above. supercls = cls->superclass; if (supercls&&!supercls->isInitialized()) { _class_initialize(supercls); } ... if (reallyInitialize) { callInitialize(cls); } ... }
2. 如果子類未實現 + (void)initialize
、則會呼叫一次父類
所以、在官方文件以及xcode自動補全中採用以下寫法
+ (void)initialize { if (self == [ClassName self]) { // ... do the initialization ... } }
3. 每個類只會被呼叫一次
分類如果實現、則不會呼叫主類、這與 + (void)load
不同。
所以如果需要分別定製主類以及category、應該寫在 + (void)load
中。
3. 呼叫在 +(void)load
之前
畢竟 +(void)load
也是個訊息。
4. 如果一個類沒有被使用(即使被 #import
)、便不會被呼叫
但如果他自己實現了 +(void)load
方法、系統在呼叫 +(void)load
之前、會呼叫 + (void)initialize
進行初始化。
load
和 initialize
內部都實現了加鎖、是執行緒安全的。
建立、複製和銷燬
-
alloc
為該物件分配地址空間
物件建立後、isa以外的例項變數都預設初始化為0。
NSObject
而言、
alloc
其實已經初始化完畢了
比如UIView
)類、還需要init來進行進一步配置。
-
allocWithZone
作用於 alloc
相同。文件上上說是由於歷史原因。
-
init
對已經分配了記憶體空間的物件進行進一步配置。
在某些情況下、 init
可能會返回一個新的物件(詳見 《iOS架構補完計劃--設計模式》 中對於工廠模式的介紹)。
-
new
集alloc和init於一身
相當於呼叫 [[Class alloc] init];
、也是一種歷史遺留的產物、不過還挺方便。
-
copy
通過自己實現 <NSCopying>
協議的 copyWithZone:
方法返回一個 不可變的副本
。
如果沒有實現協議方法、則會崩潰。
-
mutableCopy
通過自己實現 < NSMutableCopying >
協議的 mutableCopyWithZone:
方法返回一個 可變的副本
。
-
+ (id)copyWithZone:
-
+ (id)mutableCopyWithZone:
需要注意這兩個方法並不是 <NSCopying/NSMutableCopying>
那個物件方法、而是系統為 類物件
實現的。
二者均被標記成 OBJC_ARC_UNAVAILABLE
、也就是ARC下不需要(
)。
但是官方文件中指出 This method exists so class objects can be used in situations where you need an object that conforms to the NSCopying protocol.
也就是說、類物件也可以被 copy
、並且系統幫我們進行了內部實現。
需要注意的是、類物件的 copy
只是單純的返回自身而已。
但是這個機制讓我們可以將類物件作為key使用。
id obj0 = [Test class]; id obj1 = [Test copy]; id obj2 = [Test mutableCopy]; id obj3 = [obj0 copyWithZone:nil]; id obj4 = [obj0 mutableCopyWithZone:nil]; NSDictionary * dic = @{obj0:@"0",obj1:@"1",obj2:@"2",obj3:@"3",obj4:@"4"}; NSLog(@"%p_obj0",obj0); NSLog(@"%p_obj1",obj1); NSLog(@"%p_obj2",obj2); NSLog(@"%p_obj3",obj3); NSLog(@"%p_obj4",obj4); NSLog(@"%@_dic",dic); //列印 NSObject[46855:3785930] category_test_initialize NSObject[46855:3785930] 0x10235a1d0_obj0 NSObject[46855:3785930] 0x10235a1d0_obj1 NSObject[46855:3785930] 0x10235a1d0_obj2 NSObject[46855:3785930] 0x10235a1d0_obj3 NSObject[46855:3785930] 0x10235a1d0_obj4 NSObject[46855:3785930] { Test = 0; }_dic
關於深拷貝和淺拷貝
深拷貝
產生新物件的情況
淺拷貝
是指未產生新物件的情況( 剛才對類物件的拷貝就是典型的淺拷貝
)
簡而言之
只有不可變物件的copy方式,是淺複製,其他都是深複製。
更多可以查閱 《iOS基礎深入補完計劃--帶你重識Property》
-
dealloc
當一個物件的引用計數為0時、系統就會將這個物件釋放。
我們不需要、也不應該主動呼叫該方法。只需要處置一些不會隨著例項生命週期而變化的事情即可(比如通知、C物件的free)。
類/物件的識別與判等
-
+ class
返回類物件
物件的 [someObj class]
方法、是 NSObject
的協議方法
-
+ superclass
返回父類物件
-
+ hash
通常來講、返回物件的地址(NSObject、UIView)。
對於字串/字典/陣列、根據內容不同可能對內容有不同的hash方式、可以看看 《解讀Objective-C中的[NSString hash]方法》
id obj0 = [NSObject new]; id obj1 = [NSObject class]; id obj2 = [NSObject new]; id obj3 = [NSObject class]; id obj4 = [UIView new]; id obj5 = [UIView class]; id obj6 = [NSString new]; id obj7 = [NSString class]; id obj8 = [NSDictionary new]; id obj9 = [NSDictionary class]; id obj10 = [NSArray new]; id obj11 = [NSArray class]; NSLog(@"obj0::%zd_%ld",[obj0 hash],(NSUInteger)obj0); NSLog(@"obj1::%zd_%ld",[obj1 hash],(NSUInteger)obj1); NSLog(@"obj2::%zd_%ld",[obj2 hash],(NSUInteger)obj2); NSLog(@"obj3::%zd_%ld",[obj3 hash],(NSUInteger)obj3); NSLog(@"obj4::%zd_%ld",[obj4 hash],(NSUInteger)obj4); NSLog(@"obj5::%zd_%ld",[obj5 hash],(NSUInteger)obj5); NSLog(@"obj6::%zd_%ld",[obj6 hash],(NSUInteger)obj6); NSLog(@"obj7::%zd_%ld",[obj7 hash],(NSUInteger)obj7); NSLog(@"obj8::%zd_%ld",[obj8 hash],(NSUInteger)obj8); NSLog(@"obj9::%zd_%ld",[obj9 hash],(NSUInteger)obj9); NSLog(@"obj10::%zd_%ld",[obj10 hash],(NSUInteger)obj10); NSLog(@"obj11::%zd_%ld",[obj11 hash],(NSUInteger)obj11); //列印結果 obj0::105827994210384_105827994210384 obj1::4533444264_4533444264 obj2::105827994210416_105827994210416 obj3::4533444264_4533444264 obj4::140577323666816_140577323666816 obj5::4563397296_4563397296 obj6::0_4523287328 obj7::4523970768_4523970768 obj8::0_105553116300640 obj9::4539240872_4539240872 obj10::0_105553116300656 obj11::4539240232_4539240232
hash方法只在物件被新增至NSSet和設定為NSDictionary的key時會呼叫
此時他會作為 key的查詢以及判等
依據避免重複新增
為了優化判等的效率, 基於hash的NSSet和NSDictionary在判斷成員是否相等時, 會這樣做
Step 1: 整合成員的hash值是否和目標hash值相等, 如果相同進入Step 2, 如果不等, 直接判斷不相等
Step 2: hash值相同(即Step 1)的情況下, 再進行物件判等, 作為判等的結果
也就是說。我們如果在插入物件之後手動修改了hash值、在進行查詢的時候是查詢不到滴。
自定義hash插入NSSet/NSDictionay
由於hash只返回物件地址、我們可以通過物件內容進行自定義hash。( 特指你希望相同名字和生日不想重複插入這種情況
)
- (NSUInteger)hash { return [self.name hash] ^ [self.birthday hash]; }
-
- isEqual
判斷兩個物件內容是否相等、並不只是單純判斷是否為同一個物件( 記憶體地址
)。
自定義物件需要自己實現判等邏輯。
-
- isProxy
判斷物件是否繼承NSProxy
需要注意我們絕大部分的類都繼承與 NSObject
而非 NSProxy
。
所以絕大部分都會返回No。你可以自己做一個繼承於 NSProxy
的類來測試。
-
- isKindOfClass
判斷物件是否是指定類或其子類
具體比較的、應該是物件的isa指標。可以看下面的例子:
BOOL a = [NSString isKindOfClass:[NSString class]]; BOOL b = [NSString isKindOfClass:object_getClass([NSString class])]; BOOL c = [UIView isKindOfClass:[UIView class]]; BOOL d = [UIView isKindOfClass:object_getClass([UIView class])]; NSLog(@"a::%d",a); NSLog(@"b::%d",b); NSLog(@"c::%d",c); NSLog(@"d::%d",d); //列印結果 test[4039:505795] a::0 test[4039:505795] b::1 test[4039:505795] c::0 test[4039:505795] d::1
-
- isMemberOfClass
判斷物件是否是給定類的例項(注意不包含子類)
所比較的、依舊是isa指標。可以自己照上面試試。
需要注意的是:
對於 NSString/NSDictionay/NSArray
這類類族物件來說。直接用抽象產品( NSString/NSDictionay/NSArray
)進行判等、是會失敗的。
物件的 [someObj superclass]
方法、是 NSObject
的協議方法
-
+ isSubclassOfClass:
檢視一個類物件是否是另一個類物件的子類或者本身
BOOL a = [Test isSubclassOfClass:[Test2 class]]; BOOL b = [Test2 isSubclassOfClass:[Test class]]; BOOL c = [Test isSubclassOfClass:[Test class]]; NSLog(@"a==%d,b==%d,c==%d",a,b,c); //列印 a==1,b==0,c==1
對物件而言、並沒有能直接比較從屬關係的方法。
類/物件的測試
-
- respondsToSelector:
判斷 物件
是否能夠呼叫給定的( 物件
)方法。(如果用類物件來測試、自然測試的就是類方法咯)
需要注意如果只做了宣告但沒有實現、也是會返回No的。
-
+ instancesRespondToSelector:
用 [類物件]
測試(類)方法是否被實現
需要注意如果只做了宣告但沒有實現、也是會返回No的。
所以說 respondsToSelector
既可以測類方法也可以測例項方法。
instancesRespondToSelector
則可以用類物件來測試類方法。
-
+ conformsToProtocol:
測試一個類是否 遵循
了某個協議
主要注意:1、遵循不代表實現。2、遵循不代表必須在.h中宣告。
獲取方法資訊
-
- methodForSelector:
-
+ instanceMethodForSelector:
分別返回類/物件的某個物件方法以及類方法的 實現(IMP)
類/物件的描述
-
+ debugDescription
控制檯中列印的資訊、就是通過這個方法輸出。
類方法只打印出了類名、例項方法( NSObject協議
)可能會打印出更多內容。
-
+ description
NSLog、就是通過這個方法輸出。
類方法只打印出了類名、例項方法( NSObject協議
)可能會打印出更多內容。
傳送訊息
-
- performSelector:withObject:afterDelay:
在延遲之後在 當前執行緒
上呼叫某物件的方法。
-
- performSelector:withObject:afterDelay:inModes:
在延遲之後使用指定的 Runloop模式
在 當前執行緒
上呼叫某物件的方法。
-
- performSelectorOnMainThread:withObject:waitUntilDone:
使用在 主執行緒
上呼叫某物件的方法
-
- performSelectorOnMainThread:withObject:waitUntilDone:modes:
使用指定的 Runloop模式
在 主執行緒上
呼叫某物件的方法。
-
- performSelector:onThread:withObject:waitUntilDone:
在 指定執行緒上
呼叫某物件的方法
-
- performSelector:onThread:withObject:waitUntilDone:modes:
使用指定的 Runloop模式
在指定的執行緒上呼叫某物件的方法
-
- performSelectorInBackground:withObject:
在新的 後臺執行緒上
呼叫某物件的方法
-
+ cancelPreviousPerformRequestsWithTarget:
取消執行某物件先前註冊的 所有
請求。
-
+ cancelPreviousPerformRequestsWithTarget:selector:object:
取消執行某物件先前註冊的 指定selector
請求。
object
引數必須與註冊時相同(並不需要是同一個、但內部會經過 isEqual
判斷)。
需要注意的是
1. 關於RunloopMode
如果沒有宣告指定的 Runloop模式
、那麼就會使用預設 NSDefaultRunLoopMode
。
如果(正式傳送訊息時)當前Runloop模式不匹配、則會等待直到Runloop切換到對應模式。
2. 關於wait引數:
一個布林值,指定當前執行緒是否阻塞,直到在主執行緒上的接收器上執行指定的選擇器之後。指定YES阻止此執行緒; 否則,指定NO立即返回此方法。
如果當前執行緒也是主執行緒,並且您YES為此引數指定,則會立即傳遞和處理訊息。
3. 關於取消
只有在使用 afterDelay
引數的方法上才可以工作
動態解析(訊息轉發)
如果runtime呼叫了一個未實現的方法、在崩潰( unrecognized selector
)之前會經過一下四步。
-
解決階段
+ resolveClassMethod:
+ resolveInstanceMethod:
允許嘗試解決這個問題、無論返回YES/NO。(這個我查了很久也沒能找到返回值到底有什麼用)
比如用runtime、為當前類新增這個方法實現。舉一個官方文件的例子:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } + (BOOL) resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSel]; }
官方文件中還有這樣一句話:
This method is called before the Objective-C forwarding mechanism is invoked. If respondsToSelector: or instancesRespondToSelector: is invoked, the dynamic method resolver is given the opportunity to provide an IMP for the given selector first.
也就是說、這個方法會在啟用訊息轉發前小呼叫。並且動態新增的方法可以被 respondsToSelector
/ instancesRespondToSelector
識別。
-
重定向(Fast Forwarding)
- forwardingTargetForSelector
允許我們為訊息指定一個新的物件進行響應。
如果在前一步你沒能對問題進行解決、runtime允許你將這個訊息轉發給一個特定的類。
從文件規範上來講、你需要這樣實現:
- 返回一個非nil以及非self的物件。
- 不知道返回啥應該返回super呼叫(或者乾脆別實現了)
- 如果你指向做單純的訊息轉發、用這個。反之如果想要做更高階的事(比如修改引數等等)、這個方法做不到。應該用下面的。
-
訊息轉發(Normal Forwarding)
- methodSignatureForSelector
+ instanceMethodSignatureForSelector
正常情況下:通過SEL獲取某個類/物件的對應方法簽名
在訊息轉發(決議)的階段: 如果返回一個函式簽名,系統就會建立一個NSInvocation物件並呼叫(下一步)-forwardInvocation:方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (!methodSignature) { methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"]; } return methodSignature; }
- forwardInvocation:
允許對方法簽名進行轉發(並由該物件嘗試執行)
- (void)forwardInvocation:(NSInvocation *)anInvocation { Test2 *test2 = [Test2 new]; if ([test2 respondsToSelector:anInvocation.selector]) { //NSString * str; //[anInvocation getArgument:&str atIndex:2]; NSString * str = @"5"; [anInvocation setArgument:&str atIndex:2]; [anInvocation invokeWithTarget:test2]; //需要注意的是這裡的invaction是不需要、也不能呼叫invoke執行的。否則會執行兩次 //[anInvocation invoke]; }else { [super forwardInvocation:anInvocation]; } }
- 這裡新的
Target
物件會嘗試響應該方法。 - 如果新的
Target
物件依舊未實現該方法、會由該物件繼續進行決議(也允許繼續轉發)。 - 引數在
methodSignatureForSelector
返回簽名之後已經自動設定好了。我們只需要指定新的Target
便可。 - 簽名的返回值將會發回給原呼叫方。
-
錯誤處理
- doesNotRecognizeSelector:
處理接收方無法識別的訊息
這個方法必須要呼叫父類實現、不推薦(允許)顛覆。否則將不會丟擲錯誤資訊。
- (void)doesNotRecognizeSelector:(SEL)aSelector { //彈窗啊、打點啊、等等等等 [super doesNotRecognizeSelector:aSelector]; }
官方提供了一寫應用舉例。當你不允許別人使用某個方法:
- (id)copy/init { [self doesNotRecognizeSelector:_cmd]; }
需要注意的是
除非你從 resolveInstanceMethod
/ resolveClassMethod
階段就用runtime添加了方法。不然每一次呼叫該方法都需要重新走一次訊息轉發的過程。
Weak相關
-
- allowsWeakReference:
允許弱引用標量、對於所有 allowsWeakReference
方法返回NO的類都絕對不能使用__weak修飾符。否則會崩潰。
-
- retainWeakReference
保留弱引用變數、在使用__weak修飾符的變數時、當被賦值物件的 retainWeakReference
方法返回NO的情況下、該變數將使用“nil” 。
最後
本文主要是自己的學習與總結。如果文記憶體在紕漏、萬望留言斧正。如果願意補充以及不吝賜教小弟會更加感激。