一、Runtime簡介
object-c是基于C語言加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機制的動態(tài)語言炮沐,除編譯器之外难咕,還需用Runtime系統(tǒng)來動態(tài)創(chuàng)建類和對象,進行消息發(fā)送和轉(zhuǎn)發(fā)理卑。
二翘紊、Runtime數(shù)據(jù)結(jié)構(gòu)
在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é)構(gòu)id和SEL來逐步分析和理解Runtime有哪些重要的數(shù)據(jù)結(jié)構(gòu)。
SEL
SEL是函數(shù)objc_msgSend第二個參數(shù)的數(shù)據(jù)類型妈嘹,表示方法選擇器柳琢,按下面路徑打開objc.h文件
typedef struct objc_selector *SEL;
其實它就是映射到方法的C字符串,你可以通過Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個SEL類型的方法選擇器润脸。如果你知道selector對應(yīng)的方法名是什么柬脸,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串,再用NSLog打印毙驯。
id
接下來看objc_msgSend第一個參數(shù)的數(shù)據(jù)類型id倒堕,id是通用類型指針,能夠表示任何對象爆价。按下面路徑打開objc.h文件:
/// 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é)構(gòu)體指針垦巴,它包含一個Class isa成員媳搪,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。
Class
isa指針的數(shù)據(jù)類型是Class魂那,Class表示對象所屬的類蛾号,按下面路徑打開objc.h文件:
可以查看到Class其實就是一個objc_class結(jié)構(gòu)體指針,但這個頭文件找不到它的定義涯雅,需要在runtime.h才能找到objc_class結(jié)構(gòu)體的定義鲜结。
isa表示一個Class對象的Class,也就是Meta Class活逆。在面向?qū)ο笤O(shè)計中精刷,一切都是對象,Class在設(shè)計中本身也是一個對象蔗候。我們會在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é)構(gòu)體objc_class也是繼承objc_object锈遥,說明Class在設(shè)計中本身也是一個對象纫事。
其實Meta Class也是一個Class,那么它也跟其他Class一樣有自己的isa和super_class指針所灸,關(guān)系如下:
super_class表示實例對象對應(yīng)的父類丽惶;
name表示類名;
ivars表示多個成員變量爬立,它指向objc_ivar_list結(jié)構(gòu)體钾唬。在runtime.h可以看到它的定義:
objc_ivar_list
其實就是一個鏈表,存儲多個objc_ivar侠驯,而objc_ivar結(jié)構(gòu)體存儲類的單個成員變量信息抡秆。
methodLists
表示方法列表,它指向objc_method_list結(jié)構(gòu)體的二級指針吟策,可以動態(tài)修改*methodLists的值來添加成員方法儒士,也是Category實現(xiàn)原理,同樣也解釋Category不能添加屬性的原因檩坚。在runtime.h可以看到它的定義:
同理着撩,objc_method_list也是一個鏈表,存儲多個objc_method效床,而objc_method結(jié)構(gòu)體存儲類的某個方法的信息。
cache用來緩存經(jīng)常訪問的方法权谁,它指向objc_cache結(jié)構(gòu)體剩檀,后面會重點講到。
protocols表示類遵循哪些協(xié)議旺芽。
Method表示類中的某個方法沪猴,在runtime.h文件中找到它的定義:
其實Method就是一個指向objc_method結(jié)構(gòu)體指針辐啄,它存儲了方法名(method_name)、方法類型(method_types)和方法實現(xiàn)(method_imp)等信息运嗜。而method_imp的數(shù)據(jù)類型是IMP壶辜,它是一個函數(shù)指針,后面會重點提及担租。
Ivar
Ivar表示類中的實例變量砸民,在runtime.h文件中找到它的定義:
Ivar其實就是一個指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)奋救、變量類型(ivar_type)等信息
IMP
在上面講Method時就說過岭参,IMP本質(zhì)上就是一個函數(shù)指針,指向方法的實現(xiàn)尝艘,在objc.h找到它的定義:
當(dāng)你向某個對象發(fā)送一條信息演侯,可以由這個函數(shù)指針來指定方法的實現(xiàn)背亥,它最終就會執(zhí)行那段代碼狡汉,這樣可以繞開消息傳遞階段而去執(zhí)行另一個方法實現(xiàn)轴猎。
Cache
顧名思義锐峭,Cache主要用來緩存,那它緩存什么呢椎扬?我們先在runtime.h文件看看它的定義:
Cache其實就是一個存儲Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能铣猩。當(dāng)對象receiver調(diào)用方法message時贿肩,首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類汰规,然后在類的methodLists中搜索方法,如果沒有找到茬射,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒有找到朴读,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低惩琉,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用瞒渠,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時仔燕,優(yōu)先在Cache查找晰搀,如果沒有找到乡翅,再到methodLists查找蠕蚜。
消息發(fā)送
前面從objc_msgSend作為入口腺毫,逐步深入分析Runtime的數(shù)據(jù)結(jié)構(gòu)邪蛔,了解每個數(shù)據(jù)結(jié)構(gòu)的作用和它們之間關(guān)系后侧到,我們正式轉(zhuǎn)入消息發(fā)送這個正題。
objc_msgSend函數(shù)
在前面已經(jīng)提過心软,當(dāng)某個對象使用語法[receiver message]來調(diào)用某個方法時踏堡,其實[receiver message]被編譯器轉(zhuǎn)化為:
id objc_msgSend ( id self, SEL op, ... );
首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class腐魂;
優(yōu)先在class的cache查找message方法,如果找不到甸箱,再到methodLists查找豪嗽;
如果沒有在class找到,再到super_class查找豌骏;
一旦找到message這個方法昵骤,就執(zhí)行它實現(xiàn)的IMP。
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表示當(dāng)前這個類的對象,而super是一個編譯器標(biāo)示符框舔,和self指向同一個消息接受者蹦玫。在本例中,無論是[self class]還是[super class]刘绣,接受消息者都是Son對象樱溉,但super與self不同的是,self調(diào)用class方法時纬凤,是在子類Son中查找方法福贞,而super調(diào)用class方法時,是在父類Father中查找方法停士。
當(dāng)調(diào)用[self class]方法時挖帘,會轉(zhuǎn)化為objc_msgSend函數(shù),這個函數(shù)定義如下:
id objc_msgSend(id self, SEL op, ...)
這時會從當(dāng)前Son類的方法列表中查找恋技,如果沒有拇舀,就到Father類查找,還是沒有蜻底,最后在NSObject類查找到骄崩。我們可以從NSObject.mm文件中看到- (Class)class的實現(xiàn):
- (Class)class {
? ? return object_getClass(self);
}
所以NSLog(@"%@", NSStringFromClass([self class]));會輸出Son。
當(dāng)調(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é)構(gòu)體抠璃,從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é)構(gòu)體包含兩個成員,第一個是receiver脱惰,表示某個類的實例鸡典。第二個是super_class表示當(dāng)前類的父類。
這時首先會構(gòu)造出objc_super結(jié)構(gòu)體枪芒,這個結(jié)構(gòu)體第一個成員是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。
三皮胡、使用示例
1痴颊、方法繼承
@interface CFURL : NSURL
+(instancetype)CFURLWithString:(NSString *)string;
@end
#import "CFURL.h"
@implementation CFURL
+(instancetype)CFURLWithString:(NSString *)string{
? ? CFURL *url = [super URLWithString:string];
? ? if (url == nil) {
? ? ? ? NSLog(@"url為空");
? ? }
? ? return url;
}
@end
//擴展分類
@interface NSURL (url)
+(instancetype)CF_URLWithStr:(NSString *)URLString;
@end
2、方法交換
Git下載:https://github.com/gaoguangxiao/RuntimeMCDemo
#import "NSURL+url.h"
#import@implementation NSURL (url)
+(void)load{
? ? //最早的方法屡贺,比main還早
? ? NSLog(@"load");
? ? //1.拿到兩個Method
? ? //2.進行方法交換
? ? Method m1 = class_getClassMethod([NSURL class], @selector(URLWithString:));
? ? Method m2 = class_getClassMethod([NSURL class], @selector(CF_URLWithStr:));
? ? //利用runtime進行方法的交換
? ? method_exchangeImplementations(m1, m2);
}
+(instancetype)CF_URLWithStr:(NSString *)URLString{
? ? //交換了兩個方法
? ? NSURL *url = [NSURL CF_URLWithStr:URLString];//注意這里不能再調(diào)用系統(tǒng)的方法
? ? if (!url) {
? ? ? ? NSLog(@"url為空");
? ? }
? ? return url;
}
@end
3蠢棱、分類擴展屬性
//.h文件#import"NSObject.h"
@interfaceNSObject(Property)//
@property在分類中只會生成set、get方法的聲明 不會生成實現(xiàn)甩栈,也不會生成_成員屬性@property(nonatomic,copy)NSStringname;
@end-----------------------------------------------
//.m文件
// 定義關(guān)聯(lián)的key
static const char*key ="name";
@implementation NSObject
(Property)
- (NSString*)name{
// 根據(jù)關(guān)聯(lián)的key泻仙,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString*)name{/*
? ? 第一個參數(shù):給哪個對象添加關(guān)聯(lián)
? ? 第二個參數(shù):關(guān)聯(lián)的key量没,通過這個key獲取
? ? 第三個參數(shù):關(guān)聯(lián)的value
? ? 第四個參數(shù):關(guān)聯(lián)的策略
? */objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}@end
4玉转、修改私有類屬性
//按鈕標(biāo)題的字體顏色
-(void)setTextColor:(UIColor?*)textColor
{
????_textColor?=?textColor;
?? ?unsignedintcount?=?0;
????Ivar?*ivars?=?class_copyIvarList([UIAlertAction?class],?&count);
????for(inti?=0;i?<?count;i?++){
?? ? ? ?Ivar?ivar?=?ivars[i];
????????NSString?*ivarName?=?[NSString?stringWithCString:ivar_getName(ivar)?encoding:NSUTF8StringEncoding];
?if([ivarName?isEqualToString:@"_titleTextColor"])?{
??[self?setValue:textColor?forKey:@"titleTextColor"];
????????}
?? ?}
}
四、參考網(wǎng)址