C本身是一個(gè)靜態(tài)語(yǔ)言,數(shù)據(jù)類型和代碼運(yùn)行的結(jié)果都是在編譯的時(shí)候確定的郁惜。而Objective-C的runtime機(jī)制賦予了C一個(gè)新的活力堡距,即運(yùn)行時(shí)機(jī)制。這也就是說(shuō)扳炬,OC代碼或者C代碼在編譯過(guò)后的機(jī)器碼并不能得出運(yùn)行結(jié)果吏颖。而這個(gè)結(jié)果需要在運(yùn)行的時(shí)候才能獲得,這樣就給了我們一個(gè)新的操縱代碼的空間恨樟,也就是運(yùn)行時(shí)半醉。在OC中,運(yùn)行時(shí)是一段提前寫完的一個(gè)模塊的代碼劝术∷醵啵可以這么說(shuō),OC的運(yùn)行時(shí)就是這段代碼賦予的养晋。
前幾篇文章中衬吆,我提到了objc_msgSend的流程,是為了讓大家對(duì)runtime的過(guò)程有一個(gè)大致的了解绳泉。但是逊抡,對(duì)于大部分人來(lái)說(shuō),比起原理零酪,更關(guān)注的是怎么用冒嫡。所以本章的內(nèi)容就是我在runtime小序曲,從運(yùn)行時(shí)多態(tài)看這股神秘力量中提到的runtime的除了objc_msgSend的另外兩種應(yīng)用:NSObject的方法和runtime的函數(shù)四苇。
學(xué)習(xí)進(jìn)度:
- runtime小序曲孝凌,從運(yùn)行時(shí)多態(tài)看這股神秘力量
- runtime進(jìn)行曲,objc_msgSend的前世今生(一)
- runtime進(jìn)行曲月腋,objc_msgSend的前世今生(二)
- runtime變奏曲蟀架,那些藏在runtime中的接口(一)
- runtime變奏曲,那些藏在runtime中的接口(二)
一榆骚、NSObject的方法
前幾天片拍,和群里的一位騷年討論了runtime的問(wèn)題。他的看法是妓肢,runtime并沒(méi)有什么用穆碎,不用runtime照樣可以開發(fā)。其實(shí)职恳,前半句并沒(méi)有什么毛病所禀,runtime確實(shí)沒(méi)有什么用,因?yàn)榇蟛糠珠_發(fā)工作基本用不著(其實(shí)我們公司用的蠻多的)放钦。問(wèn)題出在第二句色徘,不用runtime就可以開發(fā)。OC作為一種高級(jí)語(yǔ)言操禀,能讓你方便的使用它的一些接口褂策。比如:
- (BOOL)respondsToSelector:(SEL)aSelector;
這個(gè)方法大家應(yīng)該經(jīng)常使用,尤其是在使用delegate的場(chǎng)合颓屑,基本是必用的斤寂。那么,我們從邏輯上看這個(gè)方法揪惦。從runtime小序曲遍搞,從運(yùn)行時(shí)多態(tài)看這股神秘力量中我就說(shuō)過(guò),OC沒(méi)法在編譯時(shí)刻確定一個(gè)對(duì)象的類型器腋。而這個(gè)方法是判斷一個(gè)繼承自NSObject的class有沒(méi)有實(shí)現(xiàn)一個(gè)SEL溪猿。很明顯,編譯時(shí)刻做不了這件事纫塌,它是在運(yùn)行時(shí)刻做的诊县。所以,不用runtime就可以開發(fā)是錯(cuò)誤的措左。其實(shí)依痊,這種事情并不罕見(jiàn),大部分人不想學(xué)習(xí)runtime的原因也是如此怎披。
- runtime是C和匯編寫的胸嘁,看不懂。
- runtime開發(fā)用不著钳枕。
換個(gè)方式再說(shuō)一下缴渊,runtime賦予C面向?qū)ο蟮哪芰Γ杂辛薕C鱼炒。那么說(shuō)實(shí)話衔沼,只要你用到class,其實(shí)都是和runtime相關(guān)的昔瞧,怎么可能避開指蚁。好像跑題了,現(xiàn)在我們回來(lái)自晰。其實(shí)凝化,和respondsToSelector:類似的OC方法還有很多,下述我會(huì)整理一些常用的酬荞。
// 在usr/include中的objc/runtime.h可以查看
// 獲取對(duì)象對(duì)應(yīng)的class
- (Class)class;
// 判斷一個(gè)對(duì)象或者類是不是某個(gè)class或者這個(gè)class的派生類
- (BOOL)isKindOfClass:(Class)aClass;
// 判斷一個(gè)對(duì)象或者類是不是某個(gè)class
- (BOOL)isMemberOfClass:(Class)aClass;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面是否實(shí)現(xiàn)了某個(gè)協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面有沒(méi)有某個(gè)方法
- (BOOL)respondsToSelector:(SEL)aSelector;
二搓劫、runtime中的數(shù)據(jù)結(jié)構(gòu)
一中舉出了一些OC的runtime方法瞧哟,很易懂,因?yàn)镺C就是我們的開發(fā)語(yǔ)言枪向,下面我會(huì)一個(gè)個(gè)解讀runtime中的C語(yǔ)言的接口勤揩。因?yàn)镃中沒(méi)有class的概念,只有struct秘蛔,所以在介紹C語(yǔ)言接口之前陨亡,這里我將挨個(gè)介紹在runtime中使用到的常見(jiàn)結(jié)構(gòu)體。
1深员、objc_class
類結(jié)構(gòu)體负蠕,對(duì)應(yīng)class。前幾篇文章提到最多的一個(gè)結(jié)構(gòu)倦畅。
objc_class
struct objc_class {
// 指向元類的的指針遮糖,如果本身是元類,則指向rootMeta
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
// 指向父類
Class super_class OBJC2_UNAVAILABLE;
// 類名
const char *name OBJC2_UNAVAILABLE;
// 版本號(hào)滔迈,可以用runtime方法set止吁,get
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
// 成員變量列表,存儲(chǔ)成員變量
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
// 方法列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
// 方法cache燎悍,msgSend遍歷繼承鏈的時(shí)候需要輔助使用
struct objc_cache *cache OBJC2_UNAVAILABLE;
// 協(xié)議列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
OBJC2_UNAVAILABLE宏定義是蘋果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的操作敬惦,為的是兼容非Objective-C 2.0的遺留邏輯,但我們?nèi)阅軓闹蝎@得一些有價(jià)值的信息谈山,有興趣的可以查看源代碼俄删,見(jiàn)底部文獻(xiàn)。
2奏路、與objc_class直接相關(guān)
這里面舉出了存儲(chǔ)于上述objc_class結(jié)構(gòu)體之中的一些相關(guān)結(jié)構(gòu)體畴椰。
objc_method
// 存在objc_method_list里面
struct objc_method {
// 方法名
SEL method_name OBJC2_UNAVAILABLE;
// 參數(shù),返回值編碼
char *method_types OBJC2_UNAVAILABLE;
// 方法地址指針
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method_description
// 方法描述
struct objc_method_description {
// 方法名
SEL name;
// 參數(shù)鸽粉,返回值編碼
char *types;
};
objc_protocol_list
struct objc_protocol_list {
// 指向下一個(gè)objc_protocol_list的指針
struct objc_protocol_list *next;
long count;
// 協(xié)議結(jié)構(gòu)
Protocol *list[1];
};
objc_ivar
struct objc_ivar {
// 成員變量名
char *ivar_name OBJC2_UNAVAILABLE;
// 成員變量的類型斜脂,比如@"NSString" 代表NSString
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
3、與分類相關(guān)
分類是不會(huì)在編譯時(shí)刻綁定到對(duì)應(yīng)的類里的触机,只能在運(yùn)行時(shí)動(dòng)態(tài)綁定帚戳。所以這里把它拿出來(lái)。
objc_category
struct objc_category {
// 分類名稱
char *category_name OBJC2_UNAVAILABLE;
// 綁定的類名
char *class_name OBJC2_UNAVAILABLE;
// 分類中的實(shí)例方法
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
// 分類中的類方法
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
// 分類實(shí)現(xiàn)的協(xié)議
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
}
分類和類擴(kuò)展區(qū)別:
extension看起來(lái)很像一個(gè)匿名的category儡首,但是extension和有名字的category幾乎完全是兩個(gè)東西片任。 extension在編譯期決議,它就是類的一部分蔬胯,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類对供,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡氛濒。但是category則完全不一樣产场,它是在運(yùn)行期決議的鹅髓。如想學(xué)習(xí)更多category相關(guān)底層,參見(jiàn):http://tech.meituan.com/DiveIntoCategory.html京景。
4迈勋、屬性相關(guān)
這里為什么把屬性拿出來(lái)呢?因?yàn)樯鲜龅募嫒軴C1.0的objc_class里面沒(méi)有property醋粟。看了runtime的源碼之后重归,發(fā)現(xiàn)property有一個(gè)具體的存儲(chǔ)地點(diǎn)米愿,也在objc_class(實(shí)質(zhì)是繼承了objc_object)中,至于上述objc_class沒(méi)有鼻吮,只是因?yàn)閍pple不想讓我們看見(jiàn)育苟。如想了解更多,參考:runtime源碼的objc-runtime-new.mm和objc-private.h椎木。
objc_property_t
// 屬性對(duì)應(yīng)的結(jié)構(gòu)體objc_property违柏,至于具體結(jié)構(gòu),一般看不著
typedef struct objc_property *objc_property_t;
// 在objc-runtime-old.h中的結(jié)構(gòu)體作為參考吧
struct old_property {
// 屬性名
const char *name;
// objc_property_attribute_t的char*表示
const char *attributes;
};
objc_property_attribute_t
// 這是用來(lái)修飾表示屬性的一些修飾詞香椎,比如nonatomic漱竖、copy等等
typedef struct {
const char *name;
const char *value;
} objc_property_attribute_t;
objc_property_attribute_t具體參考:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1。
三畜伐、runtime的函數(shù)
到這也就到了正菜了馍惹。本章我會(huì)把runtime庫(kù)中public部分所有和class相關(guān)的api都調(diào)用一遍,解釋一遍玛界。至于ivar相關(guān)万矾、method相關(guān)、protocol相關(guān)慎框、property相關(guān)和具體應(yīng)用良狈,且待下回分解。
前提
// 為獲取class的protocol準(zhǔn)備
@protocol AProtocol <NSObject>
- (void)aProtocolMethod;
@end
// 為獲取class的相關(guān)信息
@interface A : NSObject {
NSString *strA;
}
@property (nonatomic, assign) NSUInteger uintA;
@end
@implementation A
@end
// 為為class添加方法準(zhǔn)備
void aNewMethod() {
NSLog(@"aNewMethod");
}
void aReplaceMethod() {
NSLog(@"aReplaceMethod");
}
class相關(guān)使用場(chǎng)景1(class一些基礎(chǔ)信息獲缺靠荨):
// 代碼
// 獲取類名
const char *a = class_getName([A class]);
NSLog(@"%s", a); // a
// 獲取父類
Class aSuper = class_getSuperclass([A class]);
NSLog(@"%s", class_getName(aSuper)); // b
// 判斷是否是元類
BOOL aIfMeta = class_isMetaClass([A class]);
BOOL aMetaIfMeta = class_isMetaClass(objc_getMetaClass("A"));
NSLog(@"%i %i", aIfMeta, aMetaIfMeta); // c
// 類大小
size_t aSize = class_getInstanceSize([A class]);
NSLog(@"%zu", aSize); // d
// 獲取和設(shè)置類版本號(hào)
class_setVersion([A class], 1);
NSLog(@"%d", class_getVersion([A class])); // e
// 獲取工程中所有的class薪丁,包括系統(tǒng)class
unsigned int count3;
int classNum = objc_getClassList(NULL, count3);
NSLog(@"%d", classNum); // f
// 獲取工程中所有的class的數(shù)量
objc_copyClassList(&count3);
NSLog(@"%d", classNum); // g
Class aClass;
// 獲取name為"A"的class
aClass = objc_getClass("A");
NSLog(@"%s", class_getName(aClass)); // h
// 獲取name為"A"的class,比getClass少了一次檢查
aClass = objc_lookUpClass("A");
NSLog(@"%s", class_getName(aClass)); // i
// 獲取name為"A"的class猎醇,找不到會(huì)crash
aClass = objc_getRequiredClass("A");
NSLog(@"%s", class_getName(aClass)); // j
// 獲取name為"A"的class元類
Class aMetaClass = objc_getMetaClass("A");
NSLog(@"%d", class_isMetaClass(aMetaClass)); // k
// 輸出
2017-01-21 12:15:55.909 block[2493:1919841] A // a
2017-01-21 12:15:55.912 block[2493:1919841] NSObject // b
2017-01-21 12:15:55.913 block[2493:1919841] 0 1 // c
2017-01-21 12:15:55.913 block[2493:1919841] 24 // d
2017-01-21 12:15:55.914 block[2493:1919841] 1 // e
2017-01-21 12:44:32.401 block[5103:1948802] 4733 // f
2017-01-21 12:44:32.402 block[5103:1948802] 4733 // g
2017-01-21 12:44:32.402 block[5103:1948802] A // h
2017-01-21 12:44:32.403 block[5103:1948802] A // i
2017-01-21 12:44:32.403 block[5103:1948802] A // j
2017-01-21 12:44:32.403 block[5103:1948802] 1 // k
class相關(guān)使用場(chǎng)景2(class中的ivar和property)
// 代碼
// 獲取類實(shí)例成員變量窥突,只能取到本類的,父類的訪問(wèn)不到
Ivar aInstanceIvar = class_getInstanceVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aInstanceIvar)); // a
// 獲取類成員變量硫嘶,相當(dāng)于class_getInstanceVariable(cls->isa, name)阻问,感覺(jué)除非給metaClass添加成員,否則不會(huì)獲取到東西
Ivar aClassIvar = class_getClassVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aClassIvar)); // b
// 往A類添加成員變量不會(huì)成功的沦疾。因?yàn)閏lass_addIvar不能給現(xiàn)有的類添加成員變量称近,也不能給metaClass添加成員變量第队,那怎么添加,且往后看
if (class_addIvar([A class], "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
NSLog(@"綁定成員變量成功"); // c
}
// 獲取類中的ivar列表刨秆,count為ivar總數(shù)
unsigned int count;
Ivar *ivars = class_copyIvarList([A class], &count);
NSLog(@"%i", count); // d
// 獲取某個(gè)名為"uIntA"的屬性
objc_property_t aPro = class_getProperty([A class], "uintA");
NSLog(@"%s", property_getName(aPro)); // e
// 獲取類的全部屬性
class_copyPropertyList([A class], &count);
NSLog(@"%i", count); // f
// 創(chuàng)建objc_property_attribute_t凳谦,然后動(dòng)態(tài)添加屬性
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar = { "V", [[NSString stringWithFormat:@"_%@", @"aNewProperty"] UTF8String] }; //variable name
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar };
if(class_addProperty([A class], "aNewProperty", attrs, 4)) {
// 只會(huì)增加屬性,不會(huì)自動(dòng)生成set衡未,get方法
NSLog(@"綁定屬性成功"); // g
}
// 創(chuàng)建objc_property_attribute_t尸执,然后替換屬性
objc_property_attribute_t typeNew = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0New = { "C", "" }; // C = copy
objc_property_attribute_t ownershipNew = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivarNew = { "V", [[NSString stringWithFormat:@"_%@", @"uintA"] UTF8String] }; //variable name
objc_property_attribute_t attrsNew[] = { typeNew, ownership0New, ownershipNew, backingivarNew };
class_replaceProperty([A class], "uintA", attrsNew, 4);
// 這有個(gè)很大的坑。替換屬性指的是替換objc_property_attribute_t缓醋,而不是替換name如失。如果替換的屬性class里面不存在,則會(huì)動(dòng)態(tài)添加這個(gè)屬性
objc_property_t pro = class_getProperty([A class], "uintA");
NSLog(@"123456 %s", property_getAttributes(pro)); // h
// class_getIvarLayout送粱、class_setIvarLayout褪贵、class_getWeakIvarLayout、class_setWeakIvarLayout用來(lái)設(shè)定和獲取成員變量的weak抗俄、strong脆丁。參見(jiàn)http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
// 輸出
2017-01-21 12:44:32.377 block[5103:1948802] strA // a
2017-01-21 12:44:32.377 block[5103:1948802] (null) // b
2017-01-21 12:44:32.377 block[5103:1948802] 2 // d
2017-01-21 12:44:32.377 block[5103:1948802] uintA // e
2017-01-21 12:44:32.378 block[5103:1948802] 1 // f
2017-01-21 12:44:32.378 block[5103:1948802] 綁定屬性成功 // g
2017-01-21 12:44:32.379 block[5103:1948802] 123456 T@"NSString",C,N,V_uintA // h
class相關(guān)使用場(chǎng)景3(class中的method)
// 代碼
// 動(dòng)態(tài)添加方法
class_addMethod([A class], @selector(aNewMethod), (IMP)aNewMethod, "v");
// 向元類動(dòng)態(tài)添加類方法
class_addMethod(objc_getMetaClass("A"), @selector(aNewMethod), (IMP)aNewMethod, "v");
// 獲取類實(shí)例方法
Method aMethod = class_getInstanceMethod([A class], @selector(aNewMethod));
// 獲取元類中類方法
Method aClassMethod = class_getClassMethod([A class], @selector(aNewMethod));
NSLog(@"%s", method_getName(aMethod)); // a
NSLog(@"%s", method_getName(aClassMethod)); // b
// 獲取類中的method列表
unsigned int count1;
Method *method = class_copyMethodList([A class], &count1);
// 多了一個(gè)方法,打印看出.cxx_destruct动雹,只在arc下有槽卫,析構(gòu)函數(shù)
NSLog(@"%i", count1); // c
NSLog(@"%s", method_getName(method[2])); // d
// 替換方法,其實(shí)是替換IMP
class_replaceMethod([A class], @selector(aNewMethod), (IMP)aReplaceMethod, "v");
// 調(diào)用aNewMethod洽胶,其實(shí)是調(diào)用了aReplaceMethod
[[A new] performSelector:@selector(aNewMethod)]; // aReplaceMethod會(huì)輸出 e
// 獲取類中某個(gè)SEL的IMP
IMP aNewMethodIMP = class_getMethodImplementation([A class], @selector(aNewMethod));
aNewMethodIMP(); // 會(huì)調(diào)用aReplaceMethod的輸出 f
// 獲取類中某個(gè)SEL的IMP
IMP aNewMethodIMP_stret = class_getMethodImplementation_stret([A class], @selector(aNewMethod));
aNewMethodIMP_stret(); // 會(huì)調(diào)用aReplaceMethod的輸出 g
// 判斷A類中有沒(méi)有一個(gè)SEL
if(class_respondsToSelector([A class], @selector(aNewMethod))) {
NSLog(@"存在這個(gè)方法"); // h
}
// 輸出
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod // a
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod // b
2017-01-21 12:44:32.380 block[5103:1948802] 4 // c
2017-01-21 12:44:32.380 block[5103:1948802] setUintA: // d
2017-01-21 12:44:32.380 block[5103:1948802] aReplaceMethod // e
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod // f
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod // g
2017-01-21 12:44:32.381 block[5103:1948802] 存在這個(gè)方法 // h
class相關(guān)使用場(chǎng)景4(動(dòng)態(tài)創(chuàng)建類)
// 代碼
// 動(dòng)態(tài)創(chuàng)建一個(gè)類和其元類
Class aNewClass = objc_allocateClassPair([NSObject class], "aNewClass", 0);
// 添加成員變量
if (class_addIvar(aNewClass, "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
NSLog(@"綁定成員變量成功"); // a
}
// 注冊(cè)這個(gè)類晒夹,之后才能用
objc_registerClassPair(aNewClass);
// 銷毀這個(gè)類和元類
objc_disposeClassPair(aNewClass);
// 輸出
2017-01-21 12:44:32.382 block[5103:1948802] 綁定成員變量成功 // a
class相關(guān)使用場(chǎng)景5(class中的protocol)
// 代碼
// 添加protocol到class
if(class_addProtocol([A class], @protocol(AProtocol))) {
NSLog(@"綁定Protocol成功"); // a
}
// 查看類是不是遵循protocol
if(class_conformsToProtocol([A class], @protocol(AProtocol))) {
NSLog(@"A遵循AProtocol"); // b
}
// 獲取類中的protocol
unsigned int count2;
Protocol *__unsafe_unretained *aProtocol = class_copyProtocolList([A class], &count2);
NSLog(@"%s", protocol_getName(aProtocol[0])); // c
// 輸出
2017-01-21 12:44:32.381 block[5103:1948802] 綁定Protocol成功 // a
2017-01-21 12:44:32.381 block[5103:1948802] A遵循AProtocol // b
2017-01-21 12:44:32.382 block[5103:1948802] AProtocol // c
四、小結(jié)
本章介紹了runtime的一些OC方法和runtime的數(shù)據(jù)結(jié)構(gòu)姊氓,另外把runtime庫(kù)public部分中所有與class相關(guān)的C方法都介紹了丐怯。下一節(jié),將繼續(xù)介紹runtime庫(kù)public部分中中的其他的api翔横。(其實(shí)读跷,我覺(jué)得runtime學(xué)習(xí)不要太關(guān)注應(yīng)用,準(zhǔn)確的說(shuō)禾唁,每一個(gè)api都是一種應(yīng)用)
五效览、文獻(xiàn)
1、https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime?language=objc
2荡短、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
3丐枉、http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
4、http://tech.meituan.com/DiveIntoCategory.html
5掘托、https://github.com/opensource-apple/objc4