Objective-C中的类和对象

2018-07-19  本文已影响0人  lever_xu

id和Class的定义

runtime里面,声明了idClass的类型,简化一下如下:

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代表了一个对象。根据上面的声明,凡是首地址是*isastruct指针,都可以被认为是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里只记录了类名、类方法列表等。

objc-meta.png

类的继承

为了说明方便,这里把上面的例子稍微改一下:NyanCat : Cat : NSObject 这样一个继承树,画出图来就是这样子的:

image

可变与不可变

因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时给对象添加成员变量。
但同时在 objc_class的结构体中,我们可以发现方法的定义列表时一个名为methodLists的指针的指针,通过修改该指针所指向的指针的值,就可以动态地为某一个类增加成员方法。这也是Category实现的原理。同时也说明了为什么Category只可为对象增加成员方法,却不能增加成员变量。

需要特别说明一下,通过objc_setAssociatedObject 和 objc_getAssociatedObject方法可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构。

系统相关 API 及应用

Objective-C 提供了以下 API 来动态替换类方法或实例方法的实现:

使用场景:

使用举例:

// 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的实现原理

当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过isa 混写isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,如下所示:

KVC 支持实例变量,KVO 只能手动支持手动设定实例变量的KVO实现监听

上一篇 下一篇

猜你喜欢

热点阅读