(一)运行时(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];
-
self
叫做接收者(receiver)
-
testDemo
叫做选择子(selector)
-
选择子
与参数
合起来称为消息(message)
- 编译器看到此消息后,将其转换为一条标准的C语言函数调用
- 所调用的函数是消息传递机制中的核心函数,叫做
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;
类型编码参考地址://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;