Runtime

作者: 一只长毛猫 | 来源:发表于2018-04-17 19:59 被阅读13次

主要内容:
1 Runtime数据结构
2 对象,类,元类之间关系
3 消息传递
4 消息转发
5 常见问题

数据结构

objc_object定义

truct objc_object {
    private:
        isa_t isa;
    public:
        //1 关联对象
        //2 弱引用 
        //3 内存管理retain
        提供一些对isa进行操作的方法
}

id 就是 objc_object

typedef struct objc_object *id;

isa概览

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};

isa分为两种
指针型isa : 值代表Class地址
非指针型isa:值的部分代表Class地址,就是上面的shiftcls代表Class地址,indexed占用1bit表示是否使用优化的存储引用计数,has_assoc表示对象是否有关联对象,has_sidetable_rc是否使用了引用计数表,extra_rc存储引用计数减一后的结果。

objc_class定义

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

NSObject 都有一个Class类型的isa ,Class 就是 objc_class

@interface NSObject <NSObject> {
    Class isa;
}
typedef struct objc_class *Class;

cache_t 结构
作用:用于快速查找方法执行函数
是一个哈希表结构,通过hash(key)来定位到bucket_t,提取imp.

列表结构
struct cache_t {
    struct bucket_t *_buckets;
    ……
}
节点结构
struct bucket_t {
    private:
        cache_key_t _key;
        IMP _imp;
        ……
}

class_data_bits_t
包含了属性、方法、协议,主要对class_rw_t封装,代表读和写。

struct class_rw_t {
    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ……
}

class_rw_t中的methods、properties、protocols都是二维数组。第一个维度存的是分类如Person(China)、Person(America)。第二个维度存的才是具体的方法列表,成员列表,协议列表。还有一个比较重要的成员class_ro_t代表只读,存的是Person类的相关信息。
class_ro_t 结构

struct class_ro_t {
    const char * name;   //类名
    method_list_t * baseMethodList;  //一维数组
    protocol_list_t * baseProtocols;   //一维数组
    property_list_t *baseProperties;  //一维数组
    const ivar_list_t * ivars;  //一维数组
    ……
}

method_t

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
    ……
}
method_t
Type Encodings

通过对objc_object和objc_class结构了解,我们可以串联出一幅整体构图


runtime数据结构

对象、类对象、元类对象

三者关系:类对象存储实例方法列表等信息,元类对象存储类方法列表等信息


经典图

实例方法查找:根据对象的isa找到类对象,逐级在类对象,父类对象,根类对象中查找。
类方法查找:根据类对象的isa找到元类对象,逐级在元类对象,父元类对象,根元类对象,根类对象中查找。
类方法没有实现,但根类中有同名的实例方法,此时会掉用根类的实例方法吗?

@interface UserInfo : NSObject
+ (id)copy;
+ (instancetype)init; 
+ (void)finalize;
@end

@implementation UserInfo

@end

[UserInfo copy];   //正常运行
[UserInfo finalize]; //正常运行
[UserInfo init]; ///crash cannot init a class object.
self 实例对象
[self class] 类对象
object_getClass([self class]); 元类

self 类对象
[self class] 返回类自身
object_getClass([self class]);  元类

如何判断是否是元类
if(class_isMetaClass(obj)){
        NSLog(@"obj 是元类");
}

消息传递

缓存查找:给定SEL,通过hash查找定位到bucket_t, 取出IMP。
当前类查找:已排序的,二分法。未排序的,遍历查找。
逐级父类查找:父类缓存查找,父类方法查找。

@interface Phone : Mobile
@end
__________________________________
@implementation Phone
-(id)init
{
    self = [super init];
    if(self){
        NSLog(@"%@",NSStringFromClass([self class]));
        NSLog(@"%@",NSStringFromClass([super class]));
    }
}
@end
objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class class;
    __unsafe_unretained _Nonnull Class super_class;
};
[self class] =  objc_msgSend(self,@selector(class));
[super class]  = objc_msgSendSuper(super,@selector(class));

从super结构中可以看出,receiver还是当前类的对象self
class方法只在根类NSObject中才有,二者接收对象都是self.
所以[self class] 和[super class] 都会逐级向上查找,在根类中找到class的IMP,并进行调用,得到的结果都是Phone

消息转发

第一步: 动态添加

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if(sel == @selector(test))
    {
        class_addMethod(self, @selector(test), goo, "v@:");
        NSLog(@"第一次: resolveInstanceMethod");
        return YES;
    }else{
        return [super resolveInstanceMethod:sel];
    }
}
void goo(){
    NSLog(@"goo");
}

第二步: 重定向

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"第二次: forwardingTargetForSelector");
    return nil;
}

第三步:消息转发

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    if(aSelector == @selector(test))
    {
        NSLog(@"第三次 上: methodSignatureForSelector");
        //v :void
        //@:代表第一个参数是self
        //: 代表第二个参数是SEL
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }else{
        return [super methodSignatureForSelector:aSelector];
    }
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"第三次 下:forwardInvocation:");
}

Method-Swlzzling

+(void)load
{
    Method hello = class_getInstanceMethod(self, @selector(sayHello));
    Method good = class_getInstanceMethod(self, @selector(sayGood));
    method_exchangeImplementations(hello, good);
}

-(void)sayHello
{
    NSLog(@"sayHello");
}

-(void)sayGood
{
    NSLog(@"sayGood");
}

动态方法解析

@dynamic 运行时为方法添加get set

常见问题

1 编译时语言,动态语言区别

动态运行时语言将函数决议推迟到运行时
编译时语言在编译期进行函数决议

2 [obj foo]和objc_msgSend()关系

在编译后,转成objc_msgSend(self,@selector(foo));

3 runtime如何通过Selector找到对应的IMP地址?

当前类的缓存是否命中: hash查找
当前类的方法列表:已排序,二分法。未排序:遍历
父类逐级查找

4能否向编译后的类中添加实例变量?

编译后的类, 不能为它增加实例变量。class_ro_t 只读,不可写。

相关文章

网友评论

      本文标题:Runtime

      本文链接:https://www.haomeiwen.com/subject/qzgxkftx.html