id和Class的定義
runtime
里面灿巧,聲明了id
和Class
的類型蹦肴,簡化一下如下:
struct objc_class {
struct objc_class *isa;
};
struct objc_object {
struct objc_class *isa;
};
typedef struct objc_class *Class;
typedef struct objc_object *id;
在objc
中僚碎,id
代表了一個對象。根據(jù)上面的聲明阴幌,凡是首地址是*isa
的struct
指針勺阐,都可以被認為是objc
中的對象。運行時可以通過isa
指針矛双,查找到該對象是屬于什么類(Class
)渊抽。
運行時的實現(xiàn)方式
根據(jù)上面的說法,類對象(Class
)同樣也算是對象议忽,那它的isa
又是指向了什么呢懒闷?為了了解這些東西是怎么回事,這里寫一個簡單的類Cat
,并且用C
重寫一遍愤估,看看編譯器在底層到底是如何實現(xiàn)的帮辟。
//.h
@interface Cat : NSObject {
int age;
NSString *name;
}
- (void)nyan1;
+ (void)nyan2;
@end
//.m
@implementation Cat
- (void)nyan1 {
printf("instance nyan~");
}
+ (void)nyan2 {
printf("class nyan~");
}
@end
在終端執(zhí)行clang -rewrite-objc Cat.m
這一條語句,讓clang將該類重寫為cpp代碼玩焰,我們就能查看到大概底層的實現(xiàn)機制了由驹。
rewrite
后的代碼基本是純C
的,稍微整理一下昔园,可以提取出下面這些信息:
//class 的實際結(jié)構(gòu)
struct _class_t {
struct _class_t *isa; //isa指針
struct _class_t *superclass; //父類
void *cache;
void *vtable;
struct _class_ro_t *ro; //class包含的信息
};
//Class包含的信息
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name; //類名
const struct _method_list_t *baseMethods; //方法列表
const struct _objc_protocol_list *baseProtocols; //協(xié)議列表
const struct _ivar_list_t *ivars; //ivar列表
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties; //屬性列表
};
extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_Cat __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_Cat, //包含了類方法等
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_Cat __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_Cat, //此處isa指向meta-class
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Cat, //包含了實例方法 ivar信息等
};
typedef struct objc_object Cat;
1蔓榄、所有Cat
的實例的isa
都指向了Cat(Class)
;
2、Cat(Class)
是一個全局變量蒿赢,其中記錄了類名润樱、成員變量信息、property
信息羡棵、protocol
信息和實例方法列表等;
3壹若、Cat(Class)
的isa
指向了全局變量Cat(meta-class)
,meta-class
里只記錄了類名皂冰、類方法列表等店展。
類的繼承
為了說明方便,這里把上面的例子稍微改一下:NyanCat : Cat : NSObject
這樣一個繼承樹秃流,畫出圖來就是這樣子的:
可變與不可變
因為對象在內(nèi)存中的排布可以看成一個結(jié)構(gòu)體赂蕴,該結(jié)構(gòu)體的大小并不能動態(tài)變化。所以無法在運行時給對象添加成員變量舶胀。
但同時在 objc_class
的結(jié)構(gòu)體中概说,我們可以發(fā)現(xiàn)方法的定義列表時一個名為methodLists
的指針的指針,通過修改該指針所指向的指針的值,就可以動態(tài)地為某一個類增加成員方法嚣伐。這也是Category
實現(xiàn)的原理糖赔。同時也說明了為什么Category
只可為對象增加成員方法,卻不能增加成員變量轩端。
需要特別說明一下放典,通過objc_setAssociatedObject 和 objc_getAssociatedObject方法可以變相地給對象增加成員變量,但由于實現(xiàn)機制不一樣基茵,所以并不是真正改變了對象的內(nèi)存結(jié)構(gòu)奋构。
系統(tǒng)相關(guān) API 及應(yīng)用
Objective-C
提供了以下 API 來動態(tài)替換類方法或?qū)嵗椒ǖ膶崿F(xiàn):
-
class_replaceMethod
替換類方法的定義 -
method_exchangeImplementations
交換 2 個方法的實現(xiàn) -
method_setImplementation
設(shè)置 1 個方法的實現(xiàn)
使用場景:
-
class_replaceMethod
, 當需要替換的方法可能有不存在的情況時,可以考慮使用該方法拱层。 -
method_exchangeImplementations
弥臼,當需要交換 2 個方法的實現(xiàn)時使用。 -
method_setImplementation
最簡單的用法舱呻,當僅僅需要為一個方法設(shè)置其實現(xiàn)方式時使用醋火。
使用舉例:
// ImagePickerReplaceMethodsHolder.h
@interface ImagePickerReplaceMethodsHolder : NSObject
- (BOOL)shouldAutorotate;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
@end
// ImagePickerReplaceMethodsHolder.m
@implementation ImagePickerReplaceMethodsHolder
- (BOOL)shouldAutorotate {
return NO;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
@end
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self hackForImagePicker];
});
}
+ (void)hackForImagePicker {
// fix bug of image picker under iOS 6.0
// http://stackoverflow.com/questions/12522491/crash-on-presenting-uiimagepickercontroller-under-ios-6-0
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")
&& SYSTEM_VERSION_LESS_THAN(@"6.1")) {
Method oldMethod1 = class_getInstanceMethod([UIImagePickerController class], @selector(shouldAutorotate));
Method newMethod1 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(shouldAutorotate));
method_setImplementation(oldMethod1, method_getImplementation(newMethod1));
Method oldMethod2 = class_getInstanceMethod([UIImagePickerController class], @selector(preferredInterfaceOrientationForPresentation));
Method newMethod2 = class_getInstanceMethod([ImagePickerReplaceMethodsHolder class], @selector(preferredInterfaceOrientationForPresentation));
method_setImplementation(oldMethod2, method_getImplementation(newMethod2));
}
}
KVO和KVC的實現(xiàn)原理
- KVO
當你觀察一個對象時悠汽,一個新的類會被動態(tài)創(chuàng)建。這個類繼承自該對象的原本的類芥驳,并重寫了被觀察屬性的
setter
方法柿冲。重寫的setter
方法會負責在調(diào)用原setter
方法之前和之后,通知所有觀察對象:值的更改兆旬。最后通過isa 混寫
(isa-swizzling
) 把這個對象的isa
指針 (isa
指針告訴Runtime
系統(tǒng)這個對象的類是什么 ) 指向這個新創(chuàng)建的子類假抄,對象就神奇的變成了新創(chuàng)建的子類的實例。我畫了一張示意圖丽猬,如下所示:
KVC 支持實例變量宿饱,KVO 只能手動支持手動設(shè)定實例變量的KVO實現(xiàn)監(jiān)聽