runtime笔记

小喇叭: 个人笔记 个人笔记 个人笔记啦

当在浏览器输入runtime,经过0.几秒,哇,这绝对是iOS界的热搜啊。

这里作为个人笔记,没有太多关于runtime的概念解释,毕竟有官方圣经可以看

runtime笔记分为:
1. 消息转发机制 (OC && swift)
2. 通过分类扩展属性 (OC && swift)
3. 拦截系统方法 (OC && swift)
4. 自动化归档 (OC && swift)
5. swift反射 

对象(object),类(class),方法(method)的结构体

在obje.h runtime.h 里边找到对应的结构体定义
/**
对象
*/
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
/**
类
*/
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/**
方法列表
*/
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
/**
方法
*/
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

看过这几个结构体定义,来看一下消息传递的实现

eg: [someone doSomething]

编译器转成消息发送objc_msgSend(someone, doSomething)

1.系统首先找到消息的接收对象 someone ,然后通过someone的isa找到它的类。
2.在它的类中查找method_list,是否有doSomething方法。
3.没有则查找父类的method_list。
4.找到对应的method,执行它的IMP。
5.转发IMP的return值。

再来看几个关键词

- 类对象(objc_class)
- 实例(objc_object)
- 元类(Meta Class)
- Method(objc_method)
- SEL(objc_selector)
- IMP
- 类缓存(objc_cache)
- Category(objc_category)
 

类对象(objc_class)

typedef struct objc_class *Class

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
通过上边的结构体,可以看到结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等。这个结构体存放的数据称为元数据(metadata)。
该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例。

实例(objc_object)

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

类对象中的元数据存储的都是如何创建一个实例的相关信息。

元类(Meta Class)

既然类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?
就是类对象的isa指针指向的我们称之为元类(metaclass)
image
元类(Meta Class)是一个类对象的类。
所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。
任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

Method(objc_method)

Method和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码。

eg: 
- (void)doSomething {
    NSLog(@"doSomething");
}

通过开始我们看的objc_method结构体,我们已经看到了==SEL==和==IMP==,说明==SEL==和==IMP==其实都是Method的属性。

SEL(objc_selector)

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

objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以看到objc_msgSend函数第二个参数类型为SEL,它是selector在Objective-C中的表示类型(Swift中是Selector类)。
selector是方法选择器,就是映射到方法的C字符串。

IMP

/// A pointer to the function of a method implementation. 
typedef id (*IMP)(id, SEL, ...); 
#endif

就是指向最终实现程序的内存地址的指针。

在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

类缓存

对于我们所写的诸多方法,可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义。所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存。所以当objc_msgSend查找一个类的选择器,它首先搜索类缓存。这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息。所以尽管OC是动态调用方法,但是runtime系统实际上非常快,接近直接执行内存地址的程序速度。
参考: http://www.cocoachina.com/ios/20150818/13075.html

Category(objc_category)

一个指向分类的结构体的指针

struct category_t { 
    const char *name; 
    classref_t cls; 
    struct method_list_t *instanceMethods; 
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};
这里说一下instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject扩展实例变量的原因,不过这个和一般的实例变量是不一样的。

消息转发

image

==这里说一下 swift 是没有签名的那两个步骤的==,也就是图中第3块。

进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。

  1. 动态方法解析
  2. 备用接收者
  3. 完整消息转发

下面看一下例子

OC

动态方法解析

我们创建一个 God 类,在.h中声明一个方法
-(void)dispatchOrder: (NSString *)message;
我们不在.m中做实现话,直接[[[God alloc] init] dispatchOrder: @"I am God"];肯定是会崩溃的。
在.m中

首先,Objective-C运行时会根据实例方法或者类方法调用 +resolveInstanceMethod: 或者 +resolveClassMethod: 让我们去提供一个函数实现。
如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    /**
     class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
     const char * _Nullable types)
     */
    
    if (sel == @selector(dispatchOrder:)) {
        return class_addMethod(self, sel, (IMP)dispatchOrder, "v@:@");
    }
    // 如果没有我们走继承树
    return [super resolveInstanceMethod: sel];
}

void dispatchOrder(id obj, SEL _cmd, NSString *message) {
    NSLog(@"%@", message);
}

备用接收者(快速转发)

如果resolve方法返回 NO ,运行时就会执行:forwardingTargetForSelector

创建一个Person类,实现

@implementation Person

-(void)dispatchOrder: (NSString *)message {
    NSLog(@"%@,person", message);
}

@end

在God中

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    /**
     class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
     const char * _Nullable types)
     */
    
//    if (sel == @selector(dispatchOrder:)) {
//        return class_addMethod(self, sel, (IMP)dispatchOrder, "v@:@");
//    }
    // 如果没有我们走继承树
    return [super resolveInstanceMethod: sel];
}

void dispatchOrder(id obj, SEL _cmd, NSString *message) {
    NSLog(@"%@", message);
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(dispatchOrder:)) {
        return [Person new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

完整消息转发 (swift不支持)

分为2步:

  1. 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(dispatchOrder:)) {
        return [NSMethodSignature signatureWithObjCTypes: "v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}
  1. 消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    Person *person = [[Person alloc] init];
    if ([person respondsToSelector:sel]) {
        [anInvocation invokeWithTarget: person];
        return;
    }
    [super forwardInvocation: anInvocation];
}

消息无法处理

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"消息无法处理");
}

==以上是OC的消息转发过程==

swift

  1. 动态方法解析
override class func resolveInstanceMethod(_ sel: Selector!) -> Bool {
        if sel == Selector(("dispatchOrder:")) {
            if let method = class_getInstanceMethod(self, #selector(God.dispatchMessage(_:))) {
                let imp = method_getImplementation(method)
                let type = method_getTypeEncoding(method)
                return class_addMethod(self, Selector(("dispatchOrder:")), imp, type)
            }
        }
        return false
    }
    
    @objc func dispatchMessage(_ message: String) {
        print("\(message)")
    }
  1. 备用接受者
class Person: NSObject {
    @objc func dispatchOrder(_ message: String) {
        print("\(message), person")
    }
}

override class func resolveInstanceMethod(_ sel: Selector!) -> Bool {
//        if sel == Selector(("dispatchOrder:")) {
//            if let method = class_getInstanceMethod(self, #selector(God.dispatchMessage(_:))) {
//                let imp = method_getImplementation(method)
//                let type = method_getTypeEncoding(method)
//                return class_addMethod(self, Selector(("dispatchOrder:")), imp, type)
//            }
//        }
        return false
    }
    
    @objc func dispatchMessage(_ message: String) {
        print("\(message)")
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return Person()
    }

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • OC是一门动态语言,它将很多静态语言在编译和链接时期做的事情推迟到了运行时来处理,这就意味着它不仅需要一个编译器,...
    zziazm阅读 209评论 0 0
  • 主要参考链接: http://yulingtianxia.com/blog/2014/11/05/objectiv...
    Kevin_Junbaozi阅读 3,311评论 0 10
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,192评论 0 7
  • 今天的一剎,突然閃過姑姑的畫面。對姑姑的印象已經很模糊了,只記得小時候姑姑對我和弟弟很好,每次姑姑來家裡都會給我們...
    奔跑的胖墩阅读 265评论 0 1