iOS runtime 理解

苹果开源代码下载地址
苹果开源代码地址

源码基于objc4-750

runtime是什么?

iOS对象在运行时动态创建对象和类、实现消息传递和消息转发的机制.objc_msgSend(objc,SEL);

runtime原理?

了解runtime原理前需要了解iOS一个Class结构。

Class结构

NSObject.h的定义可以知道NSObject类对象是一个Class isa。是一个objc_class结构体。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
} 

objc-runtime-new.hClass的定义是一个objc_class结构体的指针。objc_class是继承自objc_object的结构体(Objective-C 2.0).
idobjc_object结构体的指针。

typedef struct objc_class *Class;
typedef struct objc_object *id;

objc_class结构体在objc-runtime-new.h声明:

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

    class_rw_t *data() { 
        return bits.data();
    }
    ... // 省略了其他代码。
    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {   // 元类
        if (isMetaClass()) return (Class)this;
        else return this->ISA(); 
    }
    
    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
};

cache是一个cache_t的结构体。结构体包含函数的IMP等一些信息,优化方法调用。

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    ...
};

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};

class_data_bits_t通过bits & FAST_DATA_MASK用于class_rw_t* data的运算。

class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

dataclass_rw_t的结构体。

class_rw_t结构体包含class_ro_t结构体的指针(一维数组,只读)。包含类的初始内容。方法列表method_array_t、属性列表property_array_t、协议protocol_array_t等一些属性(method_array_t是一个二维数组)。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;  // 类名
    method_list_t * baseMethodList;  //方法列表
    protocol_list_t * baseProtocols;  //代理协议列表
    const ivar_list_t * ivars; // 属性列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;   // 方法列表
    property_array_t properties; // 属性列表。属性的getter、setter方法在methods中
    protocol_array_t protocols; // 协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

objc_objectobjc_private.h中的声明:

struct objc_object {
private:
    isa_t isa;  

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    ...
}
runtime.jpg

结合图片和代码分析可知:

objc_object有一个isa指针,objc_class继承自objc_object也拥有一个isa指针。类对象的isa指针指向为元类metaclass.每个类对象有个superclassisa指针。而实例对象是一个通过[[obj alloc] init]或者[obj new]出来的对象。

实例对象(instance)的指针指向类对象(class)。类对象的指针指向根元类(metacalss)。

根类对象rootclasssuperclassnil.根类对象的指针指向根元类root metaclass.而根元类的指针指向自己,并且根元类的superclass为根类对象。

Method

Method是一个method_t结构体指针。

struct method_t {
    SEL name;   //函数名
    const char *types;  // 函数参数类型
    MethodListIMP imp;  // 指向函数实现的指针

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

SEL是一个objc_selector结构体的指针。保存了函数名的C字符串。所以在函数声明和实现时,方法名相同和参数列表一样,参数类型不一样的情况下会报错。

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

IMP是一个函数指针。IMP函数指针指向了方法实现的首地址。当OC发起消息后,最终执行代码是由IMP决定。利用这个特性,当需要重复多次调用函数时,可以绕开消息绑定,直接用IMP指针掉起方法。

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

Method建立了SELIMP的关联,当一个对象发送消息后会通过SEL找到对应的IMP执行。

runtime消息传递

objc_msgSend是用汇编实现。[obj send]在编译时会转换成objc_msgSend(obj, send)函数。

runtime执行流程:
1.通过objisa指针找到Class,如果是类方法则会在metaClass中查找。
2.在class的缓存列表中查找是否有对应的方法。没有从methodList中查找。如果methodList找不到会沿着继承链一直往根部找,如果找到执行并添加到缓存中。
3.如果找不到会触发消息转发。

消息转发

runtime执行流程到第3步时,触发消息转发。消息转发流程如下:

method.png

1.程序会调用动态解析函数resolveClassMethod:resolveInstanceMethod:(类方法和实例方法)。在当前函数中可以添加一个对应的函数实现。该函数返回已BOOL是否可以处理对应的函数,如果return NO;则执行第2步。
2.调用forwardingTargetForSelector:备用接收者函数。这个函数可以指定其他接收者。该函数返回一个id类型的参数,如果返回nil则执行第3步。
3.执行methodSignatureForSelector:函数。该函数需要返回一个方法签名。如果返回nil,程序异常。如果返回对应的方法签名,则调用forwardInvocation:函数响应。可以在该函数中指定函数响应的对象。

runtime应用场景?

1.category属性关联(通过hash表)。

- (void)setRh_isHidden:(BOOL)rh_isHidden {
    objc_setAssociatedObject(self, rh_isShowKey, @(rh_isHidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (BOOL)rh_isHidden {
    NSNumber *num = objc_getAssociatedObject(self, rh_isShowKey);
    return [num boolValue];
}

2.方法交换和添加。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL systemViewWill = @selector(viewWillAppear:);
        SEL swizzViewWill = @selector(rh_viewWillAppear:);
        Method systemMethod = class_getInstanceMethod([self class], systemViewWill);
        Method swizzMethod = class_getInstanceMethod([self class], swizzViewWill);
        
        BOOL add = class_addMethod(self, systemViewWill, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (add) {
            class_replaceMethod(self, swizzViewWill, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        }else {
            method_exchangeImplementations(systemMethod, swizzMethod);
        }
    });
}
- (void)rh_viewWillAppear:(BOOL)animated {
    [self rh_viewWillAppear:animated];
}

3.消息转发。

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

4.模型和字典转换。
获取到类的属性列表。进行转换
5.KVO。
运行时创建一个监听类的子类。重写监听类的setter方法在setter方法调用willChangeValueForKey:didChangeValueForKey:方法通知监听类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351