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
代表了一个对象。根据上面的声明,凡是首地址是*isa
的struct
指针,都可以被认为是objc
中的对象。运行时可以通过isa
指针,查找到该对象是属于什么类(Class
)。
运行时的实现方式
根据上面的说法,类对象(Class
)同样也算是对象,那它的isa
又是指向了什么呢?为了了解这些东西是怎么回事,这里写一个简单的类Cat
,并且用C
重写一遍,看看编译器在底层到底是如何实现的。
//.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
在终端执行clang -rewrite-objc Cat.m
这一条语句,让clang将该类重写为cpp代码,我们就能查看到大概底层的实现机制了。
rewrite
后的代码基本是纯C
的,稍微整理一下,可以提取出下面这些信息:
//class 的实际结构
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; //协议列表
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
这样一个继承树,画出图来就是这样子的:
可变与不可变
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时给对象添加成员变量。
但同时在 objc_class
的结构体中,我们可以发现方法的定义列表时一个名为methodLists
的指针的指针,通过修改该指针所指向的指针的值,就可以动态地为某一个类增加成员方法。这也是Category
实现的原理。同时也说明了为什么Category
只可为对象增加成员方法,却不能增加成员变量。
需要特别说明一下,通过objc_setAssociatedObject 和 objc_getAssociatedObject方法可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构。
系统相关 API 及应用
Objective-C
提供了以下 API 来动态替换类方法或实例方法的实现:
-
class_replaceMethod
替换类方法的定义 -
method_exchangeImplementations
交换 2 个方法的实现 -
method_setImplementation
设置 1 个方法的实现
使用场景:
-
class_replaceMethod
, 当需要替换的方法可能有不存在的情况时,可以考虑使用该方法。 -
method_exchangeImplementations
,当需要交换 2 个方法的实现时使用。 -
method_setImplementation
最简单的用法,当仅仅需要为一个方法设置其实现方式时使用。
使用举例:
// 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的实现原理
- KVO
当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的
setter
方法。重写的setter
方法会负责在调用原setter
方法之前和之后,通知所有观察对象:值的更改。最后通过isa 混写
(isa-swizzling
) 把这个对象的isa
指针 (isa
指针告诉Runtime
系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:
KVC 支持实例变量,KVO 只能手动支持手动设定实例变量的KVO实现监听