通过runtime进行数据分析

(一)运行时(runtime)

  • 运行时(runtime)是一种面向对象的编程语言的运行环境.
  • 运行时(runtime)Objective-C的核心, Objective-C就是基于运行时(runtime)的.
  • Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言.
  • Objective-C需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发。
    • Objective-C最主要的特点就是在程序运行时, 以发送消息的方式调用方法.
  • C语言的函数调用方式是使用静态绑定(static binding).在编译期就能决定运行时所应调用的函数.
  • Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数。
  • 头文件 #import <objc/runtime.h> #import <objc/message.h> ...

1.OC方法调用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self testDemo:nil];
}

- (void)testDemo:(id)param {
    NSLog(@"%s",__func__);
}

2.分析 [self testDemo];

  1. self 叫做 接收者(receiver)
  2. testDemo 叫做 选择子(selector)
  3. 选择子参数合起来称为 消息(message)
  4. 编译器看到此消息后,将其转换为一条标准的C语言函数调用
  5. 所调用的函数是消息传递机制中的核心函数,叫做objc_msgSend()

3.运行时(runtime)消息发送 == OC方法调用底层实现

  • 运行时(runtime)消息发送函数
    • 提示 : OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 消息发送 : iOS8以后的特殊写法
    ((void(*)(id,SEL,id))objc_msgSend)(self,@selector(testDemo:),nil);
}

- (void)testDemo:(id)param {
    NSLog(@"%s",__func__);
}

(二)数据类型分析

1.SEL : 方法选择器(指向objc_selector结构体的指针)

typedef struct objc_selector *SEL;
  • 可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName()函数来获取一个SEL类型的方法选择器.
  • 如果知道selector对应的方法名是什么,可以通过NSString* NSStringFromSelector(SEL aSelector)方法将SEL转化为OC字符串.

2.id : 对象(指向objc_object结构体的指针)

// objc_object结构体
struct objc_object {
    // id的成员 : isa
    Class isa  OBJC_ISA_AVAILABILITY;
};
// 指向objc_object结构体指针
typedef struct objc_object *id;
  • 包含一个Class isa成员.
  • 根据isa指针就可以找到对象所属的类.

3.Class : 对象所属的类(指向objc_class结构体的指针)

typedef struct objc_class *Class;
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;

4.Method : 方法(指向objc_method结构体的指针)

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
  • objc_method存储了方法名(method_name)方法类型(method_types)方法实现(method_imp)等信息.
  • method_imp的数据类型是IMP,它是一个函数指针.

5.IMP : 方法实现(指向方法实现的指针)

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
  • IMP本质上就是一个函数指针,指向方法的实现.
  • 当向某个对象发送一条信息时,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码.

6.Ivar : 实例变量(指向objc_ivar结构体的指针)

typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

7.Cache : 缓存(指向结构体objc_cache的指针)

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
}
  • 其实就是一个存储Method的链表,主要是为了优化方法调用的性能.

8.类关系图

  • 实例对象在运行时被表示成 objc_object 类型结构体,结构体内部有个isa指针指向 objc_class 结构体。
  • objc_class 内部保存了类的变量和方法列表以及其他一些信息,并且还有一个isa指针。这个isa指针会指向 metaClass(元类),元类里保存了这个类的类方法列表。
  • 元类里也有一个isa指针,这个isa指针,指向的是根元类,根元类的isa指针指向自己

(三)objc_class中信息查看

1.Class : 对象所属的类(指向objc_class结构体的指针)

typedef struct objc_class *Class;
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;

参考官方地址:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1

类型编码参考地址://www.greatytc.com/p/f4129b5194c0

2.代码演练

(1)获取类名
/**
 获取类名

 @param cls 要获取类名的类
 @return 类名
 */
+ (NSString *)getClassName:(Class)cls {
    // 获取类名(C语言类型)
    const char *cName = class_getName(cls);
    // OC类型类名
    NSString *className = [NSString stringWithUTF8String:cName];

    return className;
}
(2)获取成员变量列表
/**
 获取成员变量列表(带下划线的都获取)

 @param cls 要获取成员变量列表的类
 @return 成员变量数组(成员变量名字和类型组合的字典数组)
 */
+ (NSArray *)getIvarList:(Class)cls {
    // 成员变量个数
    unsigned int count;
    // 获取所有的成员变量
    Ivar *ivarList = class_copyIvarList(cls, &count);

    // 准备数组容器
    NSMutableArray *ivarArrM = [NSMutableArray arrayWithCapacity:count];

    // 遍历成员变量
    for (NSInteger i = 0; i < count; i++) {
        // 获取成员变量名字
        const char *ivarName = ivar_getName(ivarList[i]);
        // 获取成员变量类型
        const char *ivarType = ivar_getTypeEncoding(ivarList[i]);

        // 成员变量名字和类型组合的字典容器
        NSMutableDictionary *ivarDictM = [NSMutableDictionary dictionary];
        ivarDictM[@"name"] = [NSString stringWithUTF8String:ivarName];
        ivarDictM[@"type"] = [NSString stringWithUTF8String:ivarType];

        // 添加到数组容器
        [ivarArrM addObject:ivarDictM];
    }
    free(ivarList);
    return ivarArrM.copy;
}
(3)获取属性列表
/**
 获取属性列表(有setter和getter方法的属性)

 @param cls 要获取属性列表的类
 @return 属性数组(属性名字和属性描述的字典数组)
 */
+ (NSArray *)getPropertyList:(Class)cls {
    // 成员属性个数
    unsigned int count;
    // 获取所有成员变量
    objc_property_t *propertyList = class_copyPropertyList(cls, &count);

    // 成员属性容器
    NSMutableArray *propertyArrM = [NSMutableArray arrayWithCapacity:count];

    // 遍历成员属性
    for (NSInteger i = 0; i < count; i++) {
        // 获取属性名字和属性的属性描述
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        NSString *attr = [NSString stringWithUTF8String:property_getAttributes(property)];
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[@"name"] = name;
        dict[@"attr"] = attr;
        // 添加到数组容器
        [propertyArrM addObject:[dict copy]];
    }
    free(propertyList);
    return propertyArrM.copy;
}
(4)类属性中的属性示例
@property (nonatomic,strong, setter=setPublicProperty:) NSArray *publicProperty01;
  • property_getAttributes输出为:
attr = "T@\"NSArray\",&,N,SsetPublicProperty:,V_publicProperty01";
name = publicProperty01;

其中 attr 的解释为:

  • T 代表类型标识
  • @ 代表为对象类型
  • NSArray 表示其实际类型
  • & 代表 retain 强引用(copy 用 C, weak 用 W)
  • N 代表 nonatomic (代表 natomic)
  • SsetPublicProperty: 前面大写S代表指定了 setter,后面跟着代表具体方法
  • V_publicProperty01 V代表其对应的成员,后面为成员的名字
(5)获取方法列表
/**
 获取类的实例方法 : 属性的setter和getter方法,对象方法,不包括类方法

 @param cls 要获取类的实例方法的类
 @return 方法数组
 */
+ (NSArray *)getMethodList:(Class)cls {
    // 实例方法个数
    unsigned int count;
    // 获取所有方法(不包括类方法)
    Method *methodList = class_copyMethodList(cls, &count);

    // 方法容器
    NSMutableArray *methodArrM = [NSMutableArray arrayWithCapacity:count];

    // 遍历所有方法
    for (NSInteger i = 0; i < count; i++) {
        // 获取数据
        NSString *name = [NSString stringWithString:NSStringFromSelector(method_getName(method))];
        NSString *type = [NSString stringWithUTF8String:method_getTypeEncoding(method)];
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[@"name"] = name;
        dict[@"type"] = type;
        // 添加到数组
        [methodArrM addObject:[dict copy]];
    }

    // 通过元类获取类方法
    Class metaCls = objc_getMetaClass(class_getName(cls));
    methods = class_copyMethodList(metaCls, &count);
    for (NSInteger i = 0; i < count; i++) {
        Method method = methods[i];
        // 获取数据
        NSString *name = [NSString stringWithString:NSStringFromSelector(method_getName(method))];
        NSString *type = [NSString stringWithUTF8String:method_getTypeEncoding(method)];
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[@"name"] = name;
        dict[@"type"] = type;
        // 添加到数组
        [arrayM addObject:[dict copy]];
    }

    free(methodList);
    return methodArrM.copy;
}
(6)方法类型编码示例
/// 公有方法2
- (void)publicMethodd02:(NSString *)str append:(int)age;
  • method_getTypeEncoding 的输出为:
name = "publicMethodd02:append:";
type = "v28@0:8@16i24";

其中type解释为:

  • v 代表返回值为void
  • 28 代表整个方法参数占位的总长度
  • @0 @代表对象,objc_msgSend 函数传入的第1个参数(self), 后面的0代表位置0开始
  • :8 :代表SEL,objc_msgSend 函数传入的第2个参数(self),后面的8代表位置8开始
  • @16 @代表第1个参数的类型为对象类型,后面的16代表位置8开始
  • i24 i代表第2个参数的类型为int类型,后面的24代表位置24开始
(7)获取协议列表
/**
 获取类的协议列表

 @param cls 获取协议列表的类
 @return 协议数组
 */
+ (NSArray *)getProtocolList:(Class)cls {

    // 协议个数
    unsigned int count;
    // 获取协议列表
    Protocol * __unsafe_unretained *protocolList = class_copyProtocolList(cls, &count);

    // 协议容器
    NSMutableArray *protocolArrM = [NSMutableArray arrayWithCapacity:count];

    // 遍历协议列表
    for (NSInteger i = 0; i < count; i++) {
        // 获取协议名字
        const char *protocolName = protocol_getName(protocolList[i]);
        // 添加到协议容器
        [protocolArrM addObject:[NSString stringWithUTF8String:protocolName]];
    }
    free(protocolList);
    return protocolArrM.copy;
}

(四)问答

问答1 : 为什么id可以指向任何对象?

  • 类是用 objc_class 结构体表示的,对象是用objc_object 结构体表示的,objc_class继承自objc_object,而id就是objc_object类型的,
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
}

问答2 : 分类中是否可以定义属性?

  • 可以定义属性,但是系统不会实现setter和getter
  • 定义的属性无法存值,因为没有成员变量链表struct objc_ivar_list *ivars
  • 但是,可以使用运行时的关联给分类添加属性
typedef 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;
} category_t;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容