目錄
- 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函數指針調用。
所以、也不會經過消息轉發的過程(子類沒實現、並不會調用父類
)。
詳情可以參考《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” 。
最後
本文主要是自己的學習與總結。如果文內存在紕漏、萬望留言斧正。如果願意補充以及不吝賜教小弟會更加感激。
參考資料
官方文檔 - NSObject_Class
NSObject Class 淺析
iOS類方法load和initialize詳解
ios開發 之 NSObject詳解
iOS基礎(九) - load和initialize的實現原理
NSObject之一
深入理解Objective-C的Runtime機制