runtime介紹:
runtime 叫運行時泽腮, 是一套底層C語言的API御蒲。我們平時編寫的OC代碼都是基于runtime實現(xiàn)的。
因為我們程序在編譯時期無法完成全部操作(方法的調用诊赊,類的創(chuàng)建)厚满,需要一個運行時的庫,所以出現(xiàn)了runtime碧磅。
OC在編譯時期不能決定真正調用那個函數(shù)碘箍,只有在運行的時候才會根據(jù)函數(shù)名找到對應的函數(shù)來調用
所以在編譯階段:OC可以調用任何函數(shù)遵馆,即使這個函數(shù)并未實現(xiàn),只要聲明過就不會報錯丰榴。
Runtime的作用:
作用一:runtime消息傳遞
可以先看一下 實例對象货邓、類對象、方法在runtime底層的表達形式:
//實例對象
struct objc_object {
Class isa;
};
//類對象
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
} ;
//方法列表
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];
};
//方法
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
}
實例對象有isa指針 類對象有isa指針和superClass指針
對象調用方法 [obj foo] 的流程是:
底層運行時會被編譯器轉化為:objc_msgSend(obj, foo)
如果obj是實例對象.
第一步:obj通過isa指針找到Class
第二步:在Class的實例方法列表objc_method_list中查找有沒有foo
第三步:如果沒有四濒,就在Class的父類superClass中找换况。
第四步:一直找到根類RootClass。
如果obj是類對象
第一步: obj通過isa指針找到自己的元類meta-class
第二步:在meta-class的類方法類別中查找有沒有foo
第三步:如果沒有盗蟆,就在meta-superClass中找
第四步:一直找到meta-RootClass复隆。
如果每一次調用方法 都這么查找的話,涉及到效率問題姆涩。
所以出現(xiàn)了 objc_cache挽拂。 記錄緩存。這樣可以先在緩存中找骨饿。
Category 在 Runtime 中是用結構體 category_t 來表示的亏栈,
typedef struct category_t {
const char *name;//主類名字
classref_t cls;//類
struct method_list_t *instanceMethods;//實例方法的列表
struct method_list_t *classMethods;//類方法的列表
struct protocol_list_t *protocols;//所有協(xié)議的列表
struct property_list_t *instanceProperties;//添加的所有屬性
} category_t;
也有很多人是這樣說的
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name;
char * _Nonnull class_name;
struct objc_method_list * _Nullable instance_methods;
struct objc_method_list * _Nullable class_methods;
struct objc_protocol_list * _Nullable;
} ;
對比objc_class 可以發(fā)現(xiàn)objc_category中少了
struct objc_ivar_list * _Nullable ivars
也就是說沒有ivars數(shù)組.
變相的解釋了,分類為什么不能添加成員宏赘。
可以添加屬性绒北,但是這個屬性需要動態(tài)綁定,并且沒有生成對應成員變量察署。
關于分類的問題:
為什么分類的方法優(yōu)先級高于主類的方法闷游?
因為category_t中的方法列表是插入到主類方法列表前面。所以先執(zhí)行分類中的方法贴汪, 找到了對應的方法脐往,就會停止查找,這里就造成了一種覆蓋主類方法的假象扳埂。
category實現(xiàn)原理:
我們都知道OC所有的對象在運行時都是用結構體表示的业簿。 category也是,用category_t表示阳懂。
1梅尤、在編譯時期,將分類中實現(xiàn)的方法生成一個結構體 method_list_t 岩调、將聲明的屬性生成一個結構體 property_list_t 巷燥。然后通過這些結構體生成一個結構體 category_t 。
2号枕、然后將結構體 category_t 保存下來缰揪。
3、在運行時期堕澄,Runtime 會拿到編譯時期我們保存下來的結構體 category_t邀跃。
4霉咨、然后將結構體 category_t 中的實例方法列表、協(xié)議列表拍屑、屬性列表添加到主類中途戒。
5、將結構體 category_t 中的類方法列表 添加到主類的 metaClass 中
引申:可以給協(xié)議protocol添加屬性
需要在遵守協(xié)議的類中僵驰,實現(xiàn)屬性的setter喷斋,getter方法。
作用二:Runtime消息轉發(fā)
http://www.reibang.com/p/6ebda3cd8052
如果消息傳遞蒜茴,在方法列表中找不到對應的方法星爪。那么就會進入消息轉發(fā)。
從消息轉發(fā)到程序報錯前有三個機會:
1粉私、動態(tài)方法解析
2顽腾、備用接收者(快速轉發(fā))
3、完整消息轉發(fā)
動態(tài)方法解析
實現(xiàn)這兩個方法+ (BOOL)resolveInstanceMethod:(SEL)sel诺核;和 + (BOOL)resolveClassMethod:(SEL)sel;
- (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo:)) {
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
if (sel == @selector(fuu)) {
// 獲取 class
Class predicateMetaClass = objc_getClass([NSStringFromClass(self) UTF8String]);
// 根據(jù) class 獲取方法的實現(xiàn)
IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(hahaha));
// 獲取實例方法
Method predicateMethod = class_getInstanceMethod(predicateMetaClass, @selector(hahaha));
const char *encoding = method_getTypeEncoding(predicateMethod);
class_addMethod(predicateMetaClass, sel, impletor, encoding);
}
return [super resolveInstanceMethod:sel];
}
這里第一字符v代表函數(shù)返回類型void抄肖,第二個字符@代表self的類型id,第三個字符:代表_cmd的類型SEL窖杀。
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函數(shù)
}
- (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(methodResolve)) {
// 獲取 MetaClass
Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
// 根據(jù) metaClass 獲取方法的實現(xiàn)
IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(proxyMethod));
// 獲取類方法
Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(proxyMethod));
const char *encoding = method_getTypeEncoding(predicateMethod);
// 動態(tài)添加類方法
class_addMethod(predicateMetaClass, sel, impletor, encoding);
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)proxyMethod {
NSLog(@"---veryitman--- +proxyMethod of class's method for OC.");
}
備用接收者(快速轉發(fā))
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [Person new];//返回Person對象漓摩,讓Person對象接收這個消息
}
return [super forwardingTargetForSelector:aSelector];
}
通過創(chuàng)建一個新的對象,讓這個新的對象去實現(xiàn)方法入客。
完整消息轉發(fā)
首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型管毙。
如果-methodSignatureForSelector:返回nil ,
Runtime則會發(fā)出 -doesNotRecognizeSelector: 消息桌硫,程序這時也就掛掉了夭咬。
如果返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation 對象
并發(fā)送-forwardInvocation:消息給目標對象鞍泉。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//簽名皱埠,進入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
第二步跟第三步的區(qū)別:
1、需要重載的API方法的用法不同
前者只需要重載一個API即可咖驮,后者需要重載兩個API。
前者只需在API方法里面返回一個新對象即可训枢,
后者需要對被轉發(fā)的消息進行重簽并手動轉發(fā)給新對象(利用 invokeWithTarget:)
2托修、轉發(fā)給新對象的個數(shù)不同
前者只能轉發(fā)一個對象,后者可以連續(xù)轉發(fā)給多個對象恒界。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector==@selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector =[anInvocation selector];
RunPerson *RP1=[RunPerson new];
RunPerson *RP2=[RunPerson new];
if ([RP1 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP1];
}
if ([RP2 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP2];
}
}
Runtime在實際項目中的應用
1睦刃、關聯(lián)對象 : 給分類添加屬性
2、添加方法十酣,交換方法 :KVO實現(xiàn)
3涩拙、消息轉發(fā):熱更新JSPatch
4际长、實現(xiàn)NSCoding的自動歸檔和自動解檔
5、實現(xiàn)字典和模型的自動轉換
KVO的”isa-swizzling”技術兴泥,就是將指針原來指向本類工育,改成了指向中間類。
就是通過這個方法搓彻。
object_setClass(self, [SimpleKVO_Dog class]);
將self 改成 SimpleKVO_Dog類如绸。這樣isa指針指向SimpleKVO_Dog。