OC知识--RUNTIME

Runtime用处

  1. 获取属性列表 class_copyPropertylist
  2. 获取方法列表 class_copyMethodList
  3. 获取成员变量列表 class_copyIvarList
  4. 获取协议列表 class_copyProtocolList
  5. 方法重写、拦截
  6. 动态添加方法
  7. 关联对象
  8. 方法交换

一、面向对象的类 -> 面向过程的结构体

类对象(objc_class) & 实例对象(objc_object)

#import <objc/objc.h>
/// 类对象
typedef struct objc_class *Class;
/// 对象或者实例是一个struct objc_object结构体
struct objc_object { 
    Class isa OBJC_ISA_AVAILABILITY; 
}; 
/// id实例也是一个struct objc_object结构体
typedef struct objc_object *id;
#import <objc/runtime.h>
struct objc_class { 
    Class isa OBJC_ISA_AVAILABILITY; 元类(mateclass)
    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 协议列表
}

  1. 实例对象str(struct objc_object)的isa指针 —> NSString类对象(struct objc_class)
  2. NSString类对象(struct objc_class)的isa指针  —> NSString元类
  3. NSString类对象(struct objc_class)的super_class指针  —> 父类NSObject类对象
  4. 父类NSObject类对象(struct objc_object)的isa指针 —> NSObject元类
  5. NSString元类的super_class指针  —> NSObject元类

类到结构体映射:

    实例对象是一个结构体(struct objc_object),这个结构体只有一个成员变量(struct objc_class),指向构造它的那个类对象,这个类对象中存储了一切实例对象需要的信息包括实例变量(struct objc_ivar_list *ivars)、实例方法(struct objc_method_list **methodLists)等,而类对象是通过元类(isa->metaclass)创建的,元类中保存了类变量和类方法,这样就完美解释了整个类和实例是如何映射到结构体的。

二、OC消息转发

    OC的实例方法在转写为C语言后实际就是一个函数,但是OC并不是在编译期决定调用哪个函数,而是在运行期决定,因为编译期根本不能确定最终会调用哪个函数  

 Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
 p = objc_msgSend(p, sel_registerName("init"));
 objc_msgSend(p, sel_registerName("setName:"), name);
 objc_msgSend(p, sel_registerName("showMyself"));

struct objc_method_list **methodLists的定义如下:

static struct /*_method_list_t*/ {
unsigned int entsize;  // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[5];

} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
5,
        {{(struct objc_selector *)"showMyself", "v16@0:8", (void *)_I_Person_showMyself},
        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
        {(struct objc_selector *)"age", "Q16@0:8", (void *)_I_Person_age},
        {(struct objc_selector *)"setAge:", "v24@0:8Q16", (void *)_I_Person_setAge_}}
};
struct _objc_method {
    struct objc_selector * _cmd; 选择子,一个字符串类型的名称用于查找对于函数实现
    const char *method_type; 方法类型
    void  *_imp; 方法实现,函数指针
};

objc_msgSend工作原理:

    为了匹配消息的接收者和选择子,需要在消息的接收者所在的类中去搜索这个struct objc_method_list方法列表,如果能找到就可以直接跳转到相关的具体实现中去调用,如果找不到,那就会通过super_class指针沿着继承树向上去搜索,如果找到就跳转,如果到了继承树的根部(通常为NSObject)还没有找到,那就会

  1. +(BOOL)resolveInstanceMethod:(SEL)name  是否动态class_addMethod添加方法
  2. - (id)forwardingTargetForSelector:(SEL)aSelector;  是否消息转发,由其他对象处理
  3. - (void)forwardInvocation: (NSInvocation*)invocation;  函数签名,消息重定向,调用NSObjec的一个方法doesNotRecognizeSelector:,然后会报unrecognized selector错误

三、OC的属性property

unsigned int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([p class], &propertyCount);
typedef struct objc_property *objc_property_t;

一个@property属性在底层就是一个结构体描述

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
        {{"cjmName","T@\"NSString\",C,N,V_cjmName"},
        {"cjmAge","TQ,N,V_cjmAge"}}
};
struct _prop_t {
    const char *name; 通过property_getName获得
    const char *attributes; 通过property_getAttributes获得
};

四、weak的实现

    weak不论是用作property修饰符还是用来修饰一个变量的声明其作用是一样的,就是不增加新对象的引用计数,被释放时也不会减少新对象的引用计数,同时在新对象被销毁时,weak修饰的属性或变量均会被设置为nil,这样可以防止野指针错误,本文要讲解的也正是这个特性,runtime如何将weak修饰的变量的对象在销毁时自动置为nil。

    那么runtime是如何实现在weak修饰的变量的对象在被销毁时自动置为nil的呢?一个普遍的解释是:runtime对注册的类会进行布局,对于weak修饰的对象会放入一个hash表中。用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以a为键在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。

猜你喜欢

转载自blog.csdn.net/liqun3yue25/article/details/88256576