Objective-C是基于C語言加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機制的動態(tài)語言,這意味著它不僅需要一個編譯器召廷,還需要Runtime系統(tǒng)來動態(tài)創(chuàng)建類和對象揖闸,進行消息發(fā)送和轉(zhuǎn)發(fā)。下面通過分析Apple開源的Runtime代碼(我使用的版本是objc4-646.tar)來深入理解Objective-C的Runtime機制夷陋。
Runtime數(shù)據(jù)結(jié)構
在Objective-C中欠拾,使用[receiver message]語法并不會馬上執(zhí)行receiver對象的message方法的代碼,而是向receiver發(fā)送一條message消息骗绕,這條消息可能由receiver來處理藐窄,也可能由轉(zhuǎn)發(fā)給其他對象來處理,也有可能假裝沒有接收到這條消息而沒有處理酬土。其實[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend ( id self, SEL op, ... );
下面從兩個數(shù)據(jù)結(jié)構id和SEL來逐步分析和理解Runtime有哪些重要的數(shù)據(jù)結(jié)構荆忍。
SEL
SEL是函數(shù)objc_msgSend第二個參數(shù)的數(shù)據(jù)類型,表示方法選擇器撤缴,按下面路徑打開objc.h文件
SEL Data Structure
查看到SEL數(shù)據(jù)結(jié)構如下:
typedef struct objc_selector *SEL;
其實它就是映射到方法的C字符串刹枉,你可以通過Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個SEL類型的方法選擇器。
如果你知道selector對應的方法名是什么腹泌,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串嘶卧,再用NSLog打印。
id
接下來看objc_msgSend第一個參數(shù)的數(shù)據(jù)類型id凉袱,id是通用類型指針芥吟,能夠表示任何對象侦铜。按下面路徑打開objc.h文件
id Data Structure.png
查看到id數(shù)據(jù)結(jié)構如下:
/// Represents an instance of a class.
struct objc_object {
Class isa? OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
id其實就是一個指向objc_object結(jié)構體指針,它包含一個Class isa成員钟鸵,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類钉稍。
注意:根據(jù)Apple的官方文檔Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技術實現(xiàn)的棺耍,isa指針在運行時被修改贡未,指向一個中間類而不是真正的類。所以蒙袍,你不應該使用isa指針來確定類的關系俊卤,而是使用class方法來確定實例對象的類。
Class
isa指針的數(shù)據(jù)類型是Class害幅,Class表示對象所屬的類消恍,按下面路徑打開objc.h文件
Class Data Structure
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
可以查看到Class其實就是一個objc_class結(jié)構體指針,但這個頭文件找不到它的定義以现,需要在runtime.h才能找到objc_class結(jié)構體的定義狠怨。
按下面路徑打開runtime.h文件
objc_class Data Structure
查看到objc_class結(jié)構體定義如下:
struct objc_class {
Class isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
const char *name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
long version? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
long info? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
long instance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
struct objc_cache *cache? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統(tǒng)運行版本進行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本邑遏,但我們?nèi)阅軓闹蝎@取一些有用信息佣赖。
讓我們分析一些重要的成員變量表示什么意思和對應使用哪些數(shù)據(jù)結(jié)構。
isa表示一個Class對象的Class记盒,也就是Meta Class憎蛤。在面向?qū)ο笤O計中,一切都是對象孽鸡,Class在設計中本身也是一個對象蹂午。我們會在objc-runtime-new.h文件找到證據(jù),發(fā)現(xiàn)objc_class有以下定義:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;? ? ? ? ? ? // formerly cache pointer and vtable
class_data_bits_t bits;? ? // class_rw_t * plus custom rr/alloc flags
......
}
由此可見彬碱,結(jié)構體objc_class也是繼承objc_object豆胸,說明Class在設計中本身也是一個對象。
其實Meta Class也是一個Class巷疼,那么它也跟其他Class一樣有自己的isa和super_class指針晚胡,關系如下:
Class isa and superclass relationship from Google
上圖實線是super_class指針,虛線是isa指針嚼沿。有幾個關鍵點需要解釋以下:
Root class (class)其實就是NSObject估盘,NSObject是沒有超類的,所以Root class(class)的superclass指向nil骡尽。
每個Class都有一個isa指針指向唯一的Meta class
Root class(meta)的superclass指向Root class(class)遣妥,也就是NSObject,形成一個回路攀细。
每個Meta class的isa指針都指向Root class (meta)箫踩。
super_class表示實例對象對應的父類
name表示類名
ivars表示多個成員變量爱态,它指向objc_ivar_list結(jié)構體。在runtime.h可以看到它的定義:
struct objc_ivar_list {
int ivar_count? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
}
objc_ivar_list其實就是一個鏈表境钟,存儲多個objc_ivar锦担,而objc_ivar結(jié)構體存儲類的單個成員變量信息。
methodLists表示方法列表慨削,它指向objc_method_list結(jié)構體的二級指針洞渔,可以動態(tài)修改*methodLists的值來添加成員方法,也是Category實現(xiàn)原理缚态,同樣也解釋Category不能添加實例變量的原因磁椒。在runtime.h可以看到它的定義:
struct objc_method_list {
struct objc_method_list *obsolete? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
int method_count? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1]? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
}
同理,objc_method_list也是一個鏈表猿规,存儲多個objc_method衷快,而objc_method結(jié)構體存儲類的某個方法的信息。
cache用來緩存經(jīng)常訪問的方法姨俩,它指向objc_cache結(jié)構體,后面會重點講到师郑。
protocols表示類遵循哪些協(xié)議
Method
Method表示類中的某個方法环葵,在runtime.h文件中找到它的定義:
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
char *method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
}
其實Method就是一個指向objc_method結(jié)構體指針,它存儲了方法名(method_name)宝冕、方法類型(method_types)和方法實現(xiàn)(method_imp)等信息张遭。而method_imp的數(shù)據(jù)類型是IMP,它是一個函數(shù)指針地梨,后面會重點提及菊卷。
Ivar
Ivar表示類中的實例變量,在runtime.h文件中找到它的定義:
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
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
}
Ivar其實就是一個指向objc_ivar結(jié)構體指針宝剖,它包含了變量名(ivar_name)洁闰、變量類型(ivar_type)等信息。
IMP
在上面講Method時就說過万细,IMP本質(zhì)上就是一個函數(shù)指針扑眉,指向方法的實現(xiàn),在objc.h找到它的定義:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
當你向某個對象發(fā)送一條信息赖钞,可以由這個函數(shù)指針來指定方法的實現(xiàn)腰素,它最終就會執(zhí)行那段代碼,這樣可以繞開消息傳遞階段而去執(zhí)行另一個方法實現(xiàn)雪营。
Cache
顧名思義弓千,Cache主要用來緩存,那它緩存什么呢献起?我們先在runtime.h文件看看它的定義:
typedef struct objc_cache *Cache? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
struct objc_cache {
unsigned int mask /* total = mask + 1 */? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
unsigned int occupied? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
Method buckets[1]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
};
Cache其實就是一個存儲Method的鏈表洋访,主要是為了優(yōu)化方法調(diào)用的性能砰诵。當對象receiver調(diào)用方法message時,首先根據(jù)對象receiver的isa指針查找到它對應的類捌显,然后在類的methodLists中搜索方法茁彭,如果沒有找到,就使用super_class指針到父類中的methodLists查找扶歪,一旦找到就調(diào)用方法理肺。如果沒有找到,有可能消息轉(zhuǎn)發(fā)善镰,也可能忽略它妹萨。但這樣查找方式效率太低,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用炫欺,占總調(diào)用次數(shù)的80%乎完。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當調(diào)用方法時品洛,優(yōu)先在Cache查找树姨,如果沒有找到,再到methodLists查找桥状。
消息發(fā)送
前面從objc_msgSend作為入口帽揪,逐步深入分析Runtime的數(shù)據(jù)結(jié)構,了解每個數(shù)據(jù)結(jié)構的作用和它們之間關系后辅斟,我們正式轉(zhuǎn)入消息發(fā)送這個正題转晰。
objc_msgSend函數(shù)
在前面已經(jīng)提過,當某個對象使用語法[receiver message]來調(diào)用某個方法時士飒,其實[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend ( id self, SEL op, ... );
現(xiàn)在讓我們看一下objc_msgSend它具體是如何發(fā)送消息:
首先根據(jù)receiver對象的isa指針獲取它對應的class
優(yōu)先在class的cache查找message方法查邢,如果找不到,再到methodLists查找
如果沒有在class找到酵幕,再到super_class查找
一旦找到message這個方法扰藕,就執(zhí)行它實現(xiàn)的IMP。
Objc Message.gif
self與super
為了讓大家更好地理解self和super裙盾,借用sunnyxx博客的ios程序員6級考試一道題目:下面的代碼分別輸出什么实胸?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
self表示當前這個類的對象,而super是一個編譯器標示符番官,和self指向同一個消息接受者庐完。在本例中,無論是[self class]還是[super class]徘熔,接受消息者都是Son對象门躯,但super與self不同的是,self調(diào)用class方法時酷师,是在子類Son中查找方法讶凉,而super調(diào)用class方法時染乌,是在父類Father中查找方法。
當調(diào)用[self class]方法時懂讯,會轉(zhuǎn)化為objc_msgSend函數(shù)荷憋,這個函數(shù)定義如下:
id objc_msgSend(id self, SEL op, ...)
這時會從當前Son類的方法列表中查找,如果沒有褐望,就到Father類查找勒庄,還是沒有,最后在NSObject類查找到瘫里。我們可以從NSObject.mm文件中看到- (Class)class的實現(xiàn):
- (Class)class {
return object_getClass(self);
}
所以NSLog(@"%@", NSStringFromClass([self class]));會輸出Son实蔽。
當調(diào)用[super class]方法時,會轉(zhuǎn)化為objc_msgSendSuper谨读,這個函數(shù)定義如下:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
objc_msgSendSuper函數(shù)第一個參數(shù)super的數(shù)據(jù)類型是一個指向objc_super的結(jié)構體局装,從message.h文件中查看它的定義:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus)? &&? !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
結(jié)構體包含兩個成員,第一個是receiver劳殖,表示某個類的實例铐尚。第二個是super_class表示當前類的父類。
這時首先會構造出objc_super結(jié)構體闷尿,這個結(jié)構體第一個成員是self塑径,第二個成員是(id)class_getSuperclass(objc_getClass("Son")),實際上該函數(shù)會輸出Father填具。然后在Father類查找class方法,查找不到匆骗,最后在NSObject查到劳景。此時,內(nèi)部使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用碉就,與[self class]調(diào)用相同盟广,所以結(jié)果還是Son。
隱藏參數(shù)self和_cmd
當[receiver message]調(diào)用方法時瓮钥,系統(tǒng)會在運行時偷偷地動態(tài)傳入兩個隱藏參數(shù)self和_cmd筋量,之所以稱它們?yōu)殡[藏參數(shù),是因為在源代碼中沒有聲明和定義這兩個參數(shù)碉熄。至于對于self的描述桨武,上面已經(jīng)解釋非常清楚了,下面我們重點講解_cmd锈津。
_cmd表示當前調(diào)用方法呀酸,其實它就是一個方法選擇器SEL。一般用于判斷方法名或在Associated Objects中唯一標識鍵名琼梆,后面在Associated Objects會講到性誉。
方法解析與消息轉(zhuǎn)發(fā)
[receiver message]調(diào)用方法時窿吩,如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎么辦错览?一般情況下纫雁,程序在運行時就會Crash掉,拋出 unrecognized selector sent to …類似這樣的異常信息倾哺。但在拋出異常之前轧邪,還有三次機會按以下順序讓你拯救程序。
Method Resolution
Fast Forwarding
Normal Forwarding
Message Forward from Google
Method Resolution
首先Objective-C在運行時調(diào)用+ resolveInstanceMethod:或+ resolveClassMethod:方法悼粮,讓你添加方法的實現(xiàn)闲勺。如果你添加方法并返回YES,那系統(tǒng)在運行時就會重新啟動一次消息發(fā)送的過程扣猫。
舉一個簡單例子菜循,定義一個類Message,它主要定義一個方法sendMessage申尤,下面就是它的設計與實現(xiàn):
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation Message
- (void)sendMessage:(NSString *)word
{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中創(chuàng)建Message對象并調(diào)用sendMessage方法:
- (void)viewDidLoad {
[super viewDidLoad];
Message *message = [Message new];
[message sendMessage:@"Sam Lau"];
}
控制臺會打印以下信息:
normal way : send message = Sam Lau
但現(xiàn)在我將原來sendMessage方法實現(xiàn)給注釋掉癌幕,覆蓋resolveInstanceMethod方法:
#pragma mark - Method Resolution
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sendMessage:)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
NSLog(@"method resolution way : send message = %@", word);
}), "v@*");
}
return YES;
}
控制臺就會打印以下信息:
method resolution way : send message = Sam Lau
注意到上面代碼有這樣一個字符串"v@*,它表示方法的參數(shù)和返回值昧穿,詳情請參考Type Encodings
如果resolveInstanceMethod方法返回NO勺远,運行時就跳轉(zhuǎn)到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)
Fast Forwarding
如果目標對象實現(xiàn)- forwardingTargetForSelector:方法,系統(tǒng)就會在運行時調(diào)用這個方法时鸵,只要這個方法返回的不是nil或self胶逢,也會重啟消息發(fā)送的過程,把這消息轉(zhuǎn)發(fā)給其他對象來處理饰潜。否則初坠,就會繼續(xù)Normal Fowarding。
繼續(xù)上面Message類的例子彭雾,將sendMessage和resolveInstanceMethod方法注釋掉碟刺,然后添加forwardingTargetForSelector方法的實現(xiàn):
#pragma mark - Fast Forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [MessageForwarding new];
}
return nil;
}
此時還缺一個轉(zhuǎn)發(fā)消息的類MessageForwarding,這個類的設計與實現(xiàn)如下:
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word
{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此時薯酝,控制臺會打印以下信息:
fast forwarding way : send message = Sam Lau
這里叫Fast半沽,是因為這一步不會創(chuàng)建NSInvocation對象,但Normal Forwarding會創(chuàng)建它吴菠,所以相對于更快點者填。
Normal Forwarding
如果沒有使用Fast Forwarding來消息轉(zhuǎn)發(fā),最后只有使用Normal Forwarding來進行消息轉(zhuǎn)發(fā)橄务。它首先調(diào)用methodSignatureForSelector:方法來獲取函數(shù)的參數(shù)和返回值幔托,如果返回為nil,程序會Crash掉,并拋出unrecognized selector sent to instance異常信息重挑。如果返回一個函數(shù)簽名嗓化,系統(tǒng)就會創(chuàng)建一個NSInvocation對象并調(diào)用-forwardInvocation:方法。
繼續(xù)前面的例子谬哀,將forwardingTargetForSelector方法注釋掉刺覆,添加methodSignatureForSelector和forwardInvocation方法的實現(xiàn):
#pragma mark - Normal Forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwarding new];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
關于這個例子的示例代碼請到github下載。
三種方法的選擇
Runtime提供三種方式來將原來的方法實現(xiàn)代替掉史煎,那該怎樣選擇它們呢谦屑?
Method Resolution:由于Method Resolution不能像消息轉(zhuǎn)發(fā)那樣可以交給其他對象來處理,所以只適用于在原來的類中代替掉篇梭。
Fast Forwarding:它可以將消息處理轉(zhuǎn)發(fā)給其他對象氢橙,使用范圍更廣,不只是限于原來的對象恬偷。
Normal Forwarding:它跟Fast Forwarding一樣可以消息轉(zhuǎn)發(fā)悍手,但它能通過NSInvocation對象獲取更多消息發(fā)送的信息,例如:target袍患、selector坦康、arguments和返回值等信息。
Associated Objects
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class. (Programming with Objective-C)
當想使用Category對已存在的類進行擴展時诡延,一般只能添加實例方法或類方法滞欠,而不適合添加額外的屬性。雖然可以在Category頭文件中聲明property屬性肆良,但在實現(xiàn)文件中編譯器是無法synthesize任何實例變量和屬性訪問方法筛璧。這時需要自定義屬性訪問方法并且使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個API來向?qū)ο筇砑尤鞘选@取和刪除關聯(lián)值:
void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
id objc_getAssociatedObject (id object, const void *key )
void objc_removeAssociatedObjects (id object )
其中objc_AssociationPolicy是個枚舉類型隧哮,它可以指定Objc內(nèi)存管理的引用計數(shù)機制。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,? ? ? ? ? /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
*? The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,? /**< Specifies that the associated object is copied.
*? The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,? ? ? /**< Specifies a strong reference to the associated object.
*? The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403? ? ? ? ? /**< Specifies that the associated object is copied.
*? The association is made atomically. */
};
下面有個關于NSObject+AssociatedObject Category添加屬性associatedObject的示例代碼:
NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (strong, nonatomic) id associatedObject;
@end
NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end
Associated Objects的key要求是唯一并且是常量座舍,而SEL是滿足這個要求的,所以上面的采用隱藏參數(shù)_cmd作為key陨帆。
Method Swizzling
Method Swizzling就是在運行時將一個方法的實現(xiàn)代替為另一個方法的實現(xiàn)曲秉。如果能夠利用好這個技巧,可以寫出簡潔疲牵、有效且維護性更好的代碼承二。可以參考兩篇關于Method Swizzling技巧的文章:
nshipster Method Swizzling
Method Swizzling 和 AOP 實踐
Aspect-Oriented Programming(AOP)
類似記錄日志纲爸、身份驗證亥鸠、緩存等事務非常瑣碎,與業(yè)務邏輯無關负蚊,很多地方都有神妹,又很難抽象出一個模塊,這種程序設計問題家妆,業(yè)界給它們起了一個名字叫橫向關注點(Cross-cutting concern)鸵荠,AOP作用就是分離橫向關注點(Cross-cutting concern)來提高模塊復用性,它可以在既有的代碼添加一些額外的行為(記錄日志伤极、身份驗證蛹找、緩存)而無需修改代碼。
危險性
Method Swizzling就像一把瑞士小刀哨坪,如果使用得當庸疾,它會有效地解決問題。但使用不當当编,將帶來很多麻煩届慈。在stackoverflow上有人已經(jīng)提出這樣一個問題:What are the Dangers of Method Swizzling in Objective C?,它的危險性主要體現(xiàn)以下幾個方面:
Method swizzling is not atomic
Changes behavior of un-owned code
Possible naming conflicts
Swizzling changes the method's arguments
The order of swizzles matters
Difficult to understand (looks recursive)
Difficult to debug
總結(jié)
雖然在平時項目不是經(jīng)常用到Objective-C的Runtime特性凌箕,但當你閱讀一些iOS開源項目時拧篮,你就會發(fā)現(xiàn)很多時候都會用到。所以深入理解Objective-C的Runtime數(shù)據(jù)結(jié)構牵舱、消息轉(zhuǎn)發(fā)機制有助于你更容易地閱讀和學習開源項目串绩。
擴展閱讀
What are the Dangers of Method Swizzling in Objective C?
Objective C類方法load和initialize的區(qū)別
著作權歸作者所有,轉(zhuǎn)載請聯(lián)系作者獲得授權芜壁,并標注“簡書作者”礁凡。