本文创作是为了知识温习和巩固,并希望对大家能够有所帮助。如果发现有任何错误,肯请大家留言指正,谢谢🙏。
一、Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一。
运行时:是一种面向对象的编程语言(面向对象编程)的运行环境。运行时表明了在某个时间段内,哪个程序正在运行的时刻。运行时是计算机程序运行生命周期内的一个阶段。runtime可以为我们提供了在程序在运行时动态创建和检查对象,修改类和对象的方法。
二、运行时的开发中的使用:
- 创建分类的时候属性添加
- 动态方法交换(全埋点)
- 获取对象属性、私有属性
- 模型转换
等等....
二、消息转发机制
1、方法正常调用流程
- obj -> isa -> obj的Class对象 -> method_array_t methods -> 对该表进行遍历查找,找到就调用,没找到继续往下走
- obj -> superclass -> obj的父类 -> isa -> method_array_t methods -> 对父类的方法列表进行遍历查找,找到就调用,没找到就重复本步骤
- 直到NSObject -> isa -> NSObject的Class对象 -> method_array_t,如果还是没有找到就会crash
2、缓存机制
我们每次在执行方法的时候每次按照上面的流程这样效率就会太慢。而运行时底层的逻辑加入了缓存机制,在我们第一次执行方法的时候找到方法之后我们就存入了缓存列表即哈希表(也叫散列表,根据健值(key,value)查询的数据结构)这个时候我们会先从缓存里面取,没有的话再走上面的流程
- 当一个对象接收到消息时[obj message];首先根据obj的isa指针进入它的类对象class里面。
- 在对象当前的Class里面,首先到缓存列表(根据cache_t)里面查询方法message的函数实现,如果找到,就直接调用该函数。
- 如果上一步没有找到对应函数,在对该class的方法列表进行二分/遍历查找,如果找到了对应函数,首先会将该方法缓存到当前类对象class的cache_t里面,然后对函数进行调用。
- 在每次进行缓存操作之前,首先需要检查缓存容量,如果缓存内的方法数量超过规定的临界值(设定容量的3/4),需要先对缓存进行2倍扩容,原先缓存过的方法全部丢弃,然后将当前方法存入扩容后的新缓存内。
- 如果当前对象的class对象里面,发现缓存和方法列表都找不到mssage方法,则通过class的superclass指针进入它的父类对象father_class里面
- 进入father_class后,首先在它的cache_t里面查找mssage,如果找到了该方法,那么会首先将方法缓存到消息接受者obj的类对象class的cache_t里面,然后调用方法对应的函数。
- 如果上一步没有找到方法则继续走到下一层,直到了基类NSObject,仍然没有找到message的时候,crash
大家可以看下底层对象 类 方法列表 方法所包含的变量
//对象
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;
}
-
类对象(objc_class)
-
实例(objc_object)
-
元类(Meta Class)
-
Method(objc_method)
-
SEL(objc_selector)
-
IMP
-
类缓存(objc_cache)
-
Category(objc_category)
注意:其实最开始的时候先进行动态方法解析,解析之后然后根据相应方法查找比如:
objc_msgSend
+(BOOL)resolveInstanceMethod:(SEL)sel;
根据上述的逻辑执行 如果大家想看方法的执行可以自己尝试下都有说明给大家推荐一篇:消息转发流程
三、常用方法
1、获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count) {
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property ----->%@",[NSString stringWithUTF8String:propertyName]);
}
}
2、获取方法列表
Method *methodList = class_copyMethodList([slef class], &count) ;
for (unsigned int i = 0; i < count; i++ ) {
Method method = methodList[i];
NSLog(@"method ----->%@",NSStringFromSelector(method_getName(method)));
}
3、获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++ ) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar -----> %@",[NSString stringWithUFT8String:ivarName]);
}
4、获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocol = protocolList[i];
const char *protocolName = protocol_getName(myProtocol);
NSLog(@"protocol -----> %@", [NSString stringWithUTF8String:protocolName]);
}
5、获取类方法
// 类 Animal 对象 animal
Class AnimalClass = object_getClass([Animal class]);
SEL oriSEL = @selectot(test1);
Method oriMethod = class_getInstanceMethod([animal Class], oriSEL);
6、获得实例方法
Class AnimalClass = object_getClass([animal class]);
SEL oriSEL = @selectot(test2);
Method cusMethod = class_getInstanceMethod([animal class], oriSEL);
7、动态添加方法
/*
* (IMP)guessAnswer 意思是guessAnswer的地址指针
* "v@:" v:代表返回值void,如果是i代表int,@:代表id sel,“:”代表SEL_cmd
* “v@:@@” 意思是,两个参数的没有返回值。****
*/
class_addMethod([animal class], @selector(guess), (IMP)guess, "v@:");
8、动态交换方法
/*
* 需要交换的方法
*/
method_exchangeImplementations(Method1, Method2);
9、属性设置方法(一般在分类里添加的时候需要)
/*
* 清楚关联属性
*/
- (void)clearAssociatedObjcet{
objc_removeAssociatedObjects(self);
}
/*
* 添加变量的set方法
*/
- (void)setUrlString:(NSString *)urlString {
objc_setAssociatedObject(self, @selector(urlString), urlString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
/*
* 添加变量的get方法
*/
- (NSString *)urlString {
return objc_getAssociatedObject(self, @selector(urlString));
}
总结:runtime 我们在开发的时候用到很多比如 全埋点的点击事件。当我们经历好久了迭代后,如果公司突然要求添加埋点,这个时候runtime是最省时省力的方法其实是这个时候我们可以创建一个plist文件做匹配 这里就不多介绍了,这方面的文章很多大家可以尝试下
写runtime的很多,有很多写的很细,大家同样可以去参考下,让我们有更大的提升。
网友评论