关于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>

OC方法调用

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

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

分析[self testDemo];

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

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

运行时(runtime)消息发送函数:OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.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__);
}

数据类型分析

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

typedef struct objc_selector *SEL;

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

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

// objc_object结构体
struct objc_object {
    // id的成员 : isa
    Class isa  OBJC_ISA_AVAILABILITY;
};
// 指向objc_object结构体指针
typedef struct objc_object *id;

1.包含一个Class isa成员.
2.根据isa指针就可以找到对象所属的类.

Class : 对象所属的类(指向objc_class结构体的指针)<if见面自动加井号>

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;

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,它是一个函数指针.

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

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif

IMP本质上就是一个函数指针,指向方法的实现.
当向某个对象发送一条信息时,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码.

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
}

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的链表,主要是为了优化方法调用的性能.

image.png

类关系图

image.png

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

objc_class中信息查看

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;

代码示例

获取类名
/**
 获取类名

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

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

 @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;
}
获取属性列表
/**
 获取属性列表(有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;
}

类属性中的属性示例

@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代表其对应的成员,后面为成员的名字
获取方法列表
/**
 获取类的实例方法 : 属性的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;
}
方法类型编码示例
/// 公有方法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开始
获取协议列表
/**
 获取类的协议列表

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

推荐阅读更多精彩内容