動(dòng)態(tài)語(yǔ)言
OC是一門不折不扣的動(dòng)態(tài)語(yǔ)言,所以它的很多機(jī)制都是動(dòng)態(tài)運(yùn)行時(shí)決定的。這點(diǎn)和C語(yǔ)言不一樣,C語(yǔ)言是靜態(tài)綁定,也就是編譯后所有的一切都已經(jīng)決定了。這一點(diǎn)和C語(yǔ)言的函數(shù)指針有些類似,很多時(shí)候函數(shù)指針在編譯的時(shí)候并不知道會(huì)指向哪個(gè)函數(shù),所以此時(shí)就是動(dòng)態(tài)綁定。
舉幾個(gè)OC動(dòng)態(tài)類型的例子,最為直接的就是id類型了、還有關(guān)聯(lián)對(duì)象、動(dòng)態(tài)綁定、消息轉(zhuǎn)發(fā)、方法調(diào)配、這些技術(shù)都是動(dòng)態(tài)類型很好的證明。
OC對(duì)象結(jié)構(gòu)
在介紹動(dòng)動(dòng)態(tài)性之前,我們先來看看OC對(duì)象的一些結(jié)構(gòu)。
#import<objc/runtime>這是OC運(yùn)行時(shí)函數(shù)庫(kù),里面定義了很多結(jié)構(gòu)體。
首先看對(duì)象的結(jié)構(gòu):
typdef struct objc_object { Class isa; } *id;
對(duì)象結(jié)構(gòu)中非常簡(jiǎn)單,只有一個(gè)isa指針,isa指針后面我們會(huì)介紹。
接下來我們看類的結(jié)構(gòu)體
struct objc_class { Class isa#if !__OBJC2__ Class super_class const char *name long version long info long instance_size struct objc_ivar_list *ivars struct objc_method_list **methodLists struct objc_cache *cache struct objc_PRotocol_list *protocols #endif}typedef struct objc_class *Class;
可以看到很多信息都在Class中定義著,里面信息如下
字段 | 含義 |
isa | isa指針 |
super_class | 父類指針 |
name | 類名 |
version | 類的版本信息,默認(rèn)為0 |
info | 供運(yùn)行期使用的一些位標(biāo)識(shí) |
instance_size | 實(shí)例的大小 |
ivars | 實(shí)例變量列表 |
methodLists | 方法列表 |
cache | 指向最近調(diào)用的方法,用于優(yōu)化調(diào)用方法的速度 |
protocols | 協(xié)議列表 |
接下來我們逐個(gè)介紹一下:
isa指針和super_class
在OC中,嚴(yán)格意義上講是沒有類這種概念的,每一個(gè)類都是一個(gè)對(duì)象,只不過類對(duì)象是一個(gè)單例。
isa指針存在于每一個(gè)對(duì)象中,類普通實(shí)例的isa指針指向類,類的isa指針指向它的元類(類方法全部都在元類中存放)。元類的isa指針指向根元類,也就是NSObject的isa所指向的元類。
super_class只有類和元類才有,它們分別指向自己的父類和父元類,而為了讓NSObject成為所有類的根類,讓NSObject的元類的父類指針也指向了NSObject。
這樣說可能也不是很好理解,看下面這張圖應(yīng)該就很快理解了。
等下我們說到消息傳遞的時(shí)候還會(huì)在說到isa和super_class。
ivars屬性列表
struct objc_ivar_list { int ivar_count #ifdef __LP64__ int space #endif /* variable length structure */ struct objc_ivar ivar_list[1] }
space作用還不太清楚...求指教啊。
下面是實(shí)例變量結(jié)構(gòu)
struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE;#ifdef __LP64__ int space OBJC2_UNAVAILABLE;#endif}
可以看到有一個(gè)ivar_offset,這個(gè)是實(shí)例變量在編譯時(shí)的偏移量,是由編譯時(shí)決定的。
methodLists方法列表
struct objc_method_list { struct objc_method_list *obsolete int method_count #ifdef __LP64__ int space #endif /* variable length structure */ struct objc_method method_list[1] }
該結(jié)構(gòu)有方法鏈表和方法總數(shù)。
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;}
里面有函數(shù)名,返回值類型和函數(shù)實(shí)現(xiàn),接下來看看SEL和IMP定義
typedef struct objc_selector *SEL;typedef id (*IMP)(id, SEL, ...);
可以看到SEL是objc_selector,(*IMP)(id,SEL,...)是id,我沒有找到objc_selector的結(jié)構(gòu)所以這里也沒法說什么...
cache緩存列表
typedef struct objc_cache *Cache #define CACHE_BUCKET_NAME(B) ((B)->method_name)#define CACHE_BUCKET_IMP(B) ((B)->method_imp)#define CACHE_BUCKET_VALID(B) (B)#ifndef __LP64__#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))#else#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))#endifstruct objc_cache { unsigned int mask /* total = mask + 1 */ unsigned int occupied Method buckets[1] };
緩存列表里面包含了已緩存的方法,用于快速的調(diào)用,不需要在去方法列表里面查詢了。
protocol協(xié)議列表
struct objc_protocol_list { struct objc_protocol_list *next; long count; Protocol *list[1];};
協(xié)議列表包含了協(xié)議數(shù)量和協(xié)議的指針。
id
id類型實(shí)際上就是一個(gè)objc_object的typedef,是一個(gè)對(duì)象實(shí)例,而且還是一個(gè)指針,所以用id來定義對(duì)象的時(shí)候就不需要加*號(hào)了。
id類型往往需要我們使用”自省“機(jī)制來保證使用安全,所謂自省其實(shí)就是看看這個(gè)對(duì)象是不是某個(gè)類的實(shí)例,或者是不是其子類。
自省用一下兩個(gè)方法:
isKindOfClass:(Class)class 判斷是不是其類族對(duì)象
isMemberOfClass:(Class)class 判斷是不是類本身對(duì)象
關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象在我之前的博客中已經(jīng)有介紹了,這里就不再說了。
OC消息機(jī)制和消息轉(zhuǎn)發(fā)
別的語(yǔ)言調(diào)用函數(shù),OC則叫做發(fā)送消息,這是因?yàn)樗蠴C的方法調(diào)用實(shí)際上底層都是通過
objc_msgSend(id self , SEL cmd,...)來發(fā)送的。
該函數(shù)的作用就是傳遞給一個(gè)對(duì)象某個(gè)方法,后面的不定參數(shù)列表是方法所需要的參數(shù)。
這里說一下OC的消息傳遞機(jī)制,首先對(duì)一個(gè)對(duì)象發(fā)送消息,它會(huì)先檢查自己的類中有沒有該方法,如果沒有就找他的父類中有沒有,如果還沒有則會(huì)進(jìn)行消息轉(zhuǎn)發(fā)。
在看例子之前先說一下,Xcode6貌似默認(rèn)行為不讓我們使用objc_msgSend了,所以需要先設(shè)置一下
把這一項(xiàng)設(shè)置為No就可以了。
這里看個(gè)例子,
EqualObject *object1 = [EqualObject new]; EqualObject *object2 = [EqualObject new]; object1.name = @"xiaoming"; object2.name = @"xiaoming"; BOOL isEqual = objc_msgSend(object1,@selector(isEqualToEqualObject:),object2); if(isEqual) { NSLog(@"equal"); }
EqualObject是我們自己實(shí)現(xiàn)的類,它有一個(gè)判斷是否相等的方法isEqualToEqualObject:,如果name相等就人為兩個(gè)對(duì)象相等。
這里我們直接傳遞消息,不通過OC語(yǔ)法,運(yùn)行程序可以看到equal被打印了出來。
然后我們?cè)倏纯聪⑥D(zhuǎn)發(fā)機(jī)制。
當(dāng)該對(duì)象包括其父類都沒有這個(gè)方法的時(shí)候會(huì)啟動(dòng),消息轉(zhuǎn)發(fā)機(jī)制分為兩大階段。
第一階段先看對(duì)象所屬類是否有能力動(dòng)態(tài)添加方法,已處理這個(gè)位置的選擇子,這叫做動(dòng)態(tài)解析(dynamic method resolution)。
第二階段設(shè)計(jì)“完整的消息轉(zhuǎn)發(fā)機(jī)制”。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接受者自己就沒法再以動(dòng)態(tài)新增方法的手段來處理與消息相關(guān)的方法調(diào)用。這又分為兩個(gè)小步。
首先,請(qǐng)接受者看看有沒有其他對(duì)象能處理這條消息。若有,則在運(yùn)行時(shí)轉(zhuǎn)給那個(gè)對(duì)象,于是消息轉(zhuǎn)發(fā)過程結(jié)束。若沒有“備用的接受者”,則啟動(dòng)完成的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行起系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給接受者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息。
動(dòng)態(tài)方法解析
遇到無(wú)法解析的信息后,首先將調(diào)用其所屬類的下列類方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
該方法參數(shù)就是未知的選擇子,返回BOOL類型那個(gè),表示這個(gè)類是否能新增一個(gè)實(shí)力方法已處理這個(gè)選擇子。假如是類方法,那么會(huì)調(diào)用
+(BOOL)resolveClassMethod:(SEL)selector
使用這種方法的前提是相關(guān)的實(shí)現(xiàn)已經(jīng)寫好了,只等運(yùn)行時(shí)動(dòng)態(tài)的插入就行,比如CoreData中NSManagedObjects對(duì)象的屬性時(shí)就可以這么做,因?yàn)閷?shí)現(xiàn)這些屬性所需的存取方法在編譯期就能確定。
下面我們看個(gè)例子
void showMessage(id self, SEL _cmd, id value){ if([value isKindOfClass:[NSString class]]) { NSLog(@"%@",(NSString *)value); }}+(BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selString = NSStringFromSelector(sel); if([selString isEqualToString:@"showMessage:"]) { class_addMethod(self, sel, (IMP)showMessage, "v@:@"); return YES; } else { return [super resolveInstanceMethod:sel]; }}
向剛才EqualObject添加以上實(shí)現(xiàn)帶代碼,然后在客戶端調(diào)用:
objc_msgSend(object1,@selector(showMessage:),@"Hello");
能夠看到程序并沒有報(bào)錯(cuò),而且還打印出了Hello!
備援接受者
當(dāng)沒有使用動(dòng)態(tài)方法解析后,還是出發(fā)備用接受者,該步驟會(huì)觸發(fā)該方法
-(id)forwardingTargetForSelector:(SEL)selector
方法參數(shù)是未知的選擇子,如果找到備用對(duì)象返回對(duì)象,否則返回nil。
可以利用該方法來模擬多重繼承機(jī)制(實(shí)際為組合),因?yàn)橥獠靠床坏剑愿杏X上就像是本身處理該消息。
下面看一個(gè)例子:
把剛才添加的代碼注釋掉,添加一個(gè)新類OtherObject,添加showMessage方法,然后在EqualObject中添加以下代碼
-(id)forwardingTargetForSelector:(SEL)aSelector{ NSString *selectString = NSStringFromSelector(aSelector); if([selectString isEqualToString:@"showMessage:"]) { return other; } return nil;}
會(huì)發(fā)現(xiàn)Hello依舊出現(xiàn)了!,而且如果你在OtherObject中的showMessage方法中打上斷點(diǎn),會(huì)發(fā)現(xiàn)方法執(zhí)行到了OtherObject中...
完整的消息轉(zhuǎn)發(fā)
如果消息沒有轉(zhuǎn)發(fā),那么回來到這一步,首先創(chuàng)建NSInvocation對(duì)象,然后把未處理的信息細(xì)節(jié)全部都封裝于其中。
此對(duì)象包含選擇子、目標(biāo)(target)和參數(shù)。在觸發(fā)NSInvocation對(duì)象時(shí),“消息派發(fā)系統(tǒng)”會(huì)把消息派給目標(biāo)。
此步驟會(huì)調(diào)用以下方法:
-(void)forwardInvocation:(NSInvocation *)invocation
這個(gè)方法實(shí)現(xiàn)簡(jiǎn)單,只要改變調(diào)用目標(biāo),使消息在新目標(biāo)上得以調(diào)用即可。但是這樣和備用接受者實(shí)現(xiàn)就一樣了,所以一般都不會(huì)這樣寫。
比較有用的實(shí)現(xiàn)是再出發(fā)消息前,先以某種方式改變消息內(nèi)容,比如追加另一個(gè)參數(shù),或是改裝選擇子。
如果發(fā)現(xiàn)不該由該類執(zhí)行,那么需要調(diào)用超類的該方法,繼承體系中每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求,如果到NSObject還不能處理會(huì)調(diào)用doesNotRecognizerSelector拋出異常。
實(shí)現(xiàn)上面那個(gè)方法的時(shí)候需要同時(shí)實(shí)現(xiàn)。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
需要先對(duì)NSInvocation簽名然后才能使用NSInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *sig; sig = [other methodSignatureForSelector:aSelector]; return sig;}-(void)forwardInvocation:(NSInvocation *)anInvocation{ [anInvocation invokeWithTarget:other];}
方法調(diào)配
在運(yùn)行時(shí),我們還可以使用方法調(diào)配技術(shù)來改變SEL指向的IMP,比如說目前方法名和對(duì)應(yīng)IMP如下
方法 | IMP |
methodA | IMPA |
methodB | IMPB |
但是當(dāng)我們使用方法調(diào)配后,就可以出現(xiàn)一下情況
方法 | IMP |
methodA | IMPB |
methodB | IMPA |
其實(shí)我們修改的本質(zhì)是方發(fā)表的映射,修改了選擇子的指向
該技術(shù)主要用到的方法如下
void method_exchangeImplementations(Method m1, Method m2)
該函數(shù)的兩個(gè)參數(shù)表示待交換的兩個(gè)方法實(shí)現(xiàn),而方法實(shí)現(xiàn)則可通過下列函數(shù)獲得:
Method class_getInstanceMethod(Class aClass , SEL aSelector)
現(xiàn)在我們向EqualObject中添加methodA和methodB
-(void)methodA{ NSLog(@"methodA");}-(void)methodB{ NSLog(@"methodB");}
然后客戶端這樣寫
EqualObject *object1 = [EqualObject new]; Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA)); Method methodB = class_getInstanceMethod([EqualObject class], @selector(methodB)); method_exchangeImplementations(methodA, methodB); [object1 methodA];
運(yùn)行后就會(huì)打印出methodB。
利用該技術(shù)進(jìn)行黑盒調(diào)試
要進(jìn)行黑盒調(diào)試,主要用到的就是該技術(shù)和category
下面來看看具體該如何編寫:
首先添加一個(gè)EqualObject的category,然后添加一個(gè)新方法plusMethodA
-(void)plusMethodA{ [self plusMethodA]; NSLog(@"this is plus version");}
這里看著像是會(huì)無(wú)限遞歸,但是實(shí)際上plusMethodA選擇子已經(jīng)指向了methodA的IMP,所以并不會(huì)出現(xiàn)無(wú)限調(diào)用的情況。
客戶端代碼
EqualObject *object1 = [EqualObject new]; Method methodA = class_getInstanceMethod([EqualObject class], @selector(methodA)); Method methodB = class_getInstanceMethod([EqualObject class], @selector(plusMethodA)); method_exchangeImplementations(methodA, methodB); [object1 methodA]
輸出結(jié)果為
2015-08-13 09:06:01.672 Equal[8282:5413229] methodA2015-08-13 09:06:01.673 Equal[8282:5413229] this is plus version
可以看到我們沒有繼承一個(gè)類就做到了擴(kuò)展某個(gè)方法,用于調(diào)試打印一些輸出信息會(huì)很有用!
常用Runtime總結(jié)
關(guān)聯(lián)對(duì)象:
設(shè)置一個(gè)關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, void *key ,id value, objc_AssociationPolicy policy)
獲取關(guān)聯(lián)對(duì)象
void objc_getAssociatedObject(id object, void *key)
刪除該對(duì)象所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
消息傳遞
向某個(gè)對(duì)象/父類 發(fā)送消息
objc_msgSend(Super)
方法調(diào)配
交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementation(Method m1, Method m2)
得到該法的指針
Method class_getInstanceMethod(Class aClass, SEL aSelector)
動(dòng)態(tài)創(chuàng)建對(duì)象
創(chuàng)建新的類
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
給類增加新的方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
注冊(cè)新的類
void objc_registerClassPair(Class cls)
獲得對(duì)象的isa指針?biāo)赶虻膶?duì)象
Class object_getClass(id obj)
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注