iOS开发-初识runtime
来源:互联网 发布:2017淘宝图片空间收费 编辑:程序博客网 时间:2024/04/27 12:06
一.runtime的基本属性
SEL
objc_msgSend
函数第二个参数类型为SEL,它是selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:
typedef struct objc_selector *SEL;
- 其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令
@selector()
或者 Runtime 系统的sel_registerName
函数来获得一个SEL
类型的方法选择器。
id
- objc_msgSend第一个参数类型为
id
,大家对它都不陌生,它是一个指向类实例的指针:
typedef struct objc_object *id;
- 那
objc_object
又是啥呢:
struct objc_object { Class isa; };
objc_object
结构体包含一个isa
指针,根据isa
指针就可以顺藤摸瓜找到对象所属的类。
PS:isa
指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class
方法来确定实例对象的类。因为KVO
的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling
的技术,详见官方文档
Class
- 之所以说
isa
是指针是因为Class
其实是一个指向objc_class
结构体的指针:
typedef struct objc_class *Class;
- 而
objc_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;
可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。
- PS:
在objc_class
结构体中:ivars
是objc_ivar_list
指针;methodLists
是指向objc_method_list
指针的指针。也就是说可以动态修改methodLists
的值来添加成员方法,这也是Category
实现的原理,同样解释了Category
不能添加属性的原因。任性的话可以在Category
中添加@dynamic的属性,并利用运行期动态提供存取方法或干脆动态转发;或者干脆使用关联度对象(AssociatedObject)
其中objc_ivar_list
和objc_method_list
分别是成员变量列表和方法列表:
struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE;#ifdef __LP64__ int space OBJC2_UNAVAILABLE;#endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;} 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;}
如果你C语言不是特别好,可以直接理解为objc_ivar_list
结构体存储着objc_ivar
数组列表,而objc_ivar
结构体存储了类的单个成员变量的信息;同理objc_method_list
结构体存储着objc_method
数组列表,而objc_method
结构体存储了类的某个方法的信息。
- 最后要提到的还有一个objc_cache
,顾名思义它是缓存,它在objc_class
的作用很重要,在后面会讲到。
- 不知道你是否注意到了objc_class
中也有一个isa
对象,这是因为一个ObjC
类本身同时也是一个对象,为了处理类和对象的关系,runtime
库创建了一种叫做元类 (Meta Class)
的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似[NSObject alloc]
的消息时,你事实上是把这个消息发给了一个类对象(Class Object)
,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class)
的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc]
这条消息发给类对象的时候,objc_msgSend()
会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。 有趣的是根元类的超类是NSObject
,而isa
指向了自己,而NSObject
的超类为nil
,也就是它没有超类。
Method
- Method是一种代表类中的某个方法的类型。
typedef struct objc_method *Method;
而objc_method
在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;} OBJC2_UNAVAILABLE;
方法名类型为SEL
,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型method_types
是个char
指针,其实存储着方法的参数类型和返回值类型。 method_imp
指向了方法的实现,本质上是一个函数指针,后面会详细讲到。
Ivar
Ivar
是一种代表类中实例变量的类型。
typedef struct objc_ivar *Ivar;
- 而
objc_ivar
在上面的成员变量列表中也提到过:
-(NSString *)nameWithInstance:(id)instance { unsigned int numIvars = 0; NSString *key=nil; Ivar * ivars = class_copyIvarList([self class], &numIvars); for(int i = 0; i < numIvars; i++) { Ivar thisIvar = ivars[i]; const char *type = ivar_getTypeEncoding(thisIvar); NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding]; if (![stringType hasPrefix:@"@"]) { continue; } if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌! key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; break; } } free(ivars); return key;}
class_copyIvarList
函数获取的不仅有实例变量,还有属性。但会在原本的属性名前加上一个下划线。
IMP
IMP
在objc.h
中的定义是:
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC
消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而IMP
这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。
你会发现IMP
指向的方法与objc_msgSend
函数类型相同,参数都包含id
和SEL
类型。每个方法名都对应一个SEL
类型的方法选择器,而每个实例对象中的SEL
对应的方法实现肯定是唯一的,通过一组id
和SEL
参数就能确定唯一的方法实现地址;反之亦然。
Cache
- 在
runtime.h
中Cache
的定义如下:
typedef struct objc_cache *Cache
- 还记得之前
objc_class
结构体中有一个struct objc_cache *cache
吧,它到底是缓存啥的呢,先看看objc_cache
的实现:
struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method buckets[1] OBJC2_UNAVAILABLE;};
Cache
为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa
指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在Cache
中查找。Runtime
系统会把被调用的方法存到Cache
中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。
Property
@property
标记了类中的属性,这个不必多说大家都很熟悉,它是一个指向objc_property
结构体的指针:
typedef struct objc_property *Property;typedef struct objc_property *objc_property_t;//这个更常用
可以通过class_copyPropertyList
和 protocol_copyPropertyList
方法来获取类和协议中的属性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
返回类型为指向指针的指针,因为属性列表是个数组,每个元素内容都是一个objc_property_t
指针,而这两个函数返回的值是指向这个数组的指针。
举个栗子,先声明一个类:
@interface Lender : NSObject { float alone;}@property float alone;@end
你可以用下面的代码获取属性列表:
id LenderClass = objc_getClass("Lender");unsigned int outCount;objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
你可以用property_getName
函数来查找属性名称:
const char *property_getName(objc_property_t property)
你可以用class_getProperty
和 protocol_getProperty
通过给出的名称来在类和协议中获取属性的引用:
objc_property_t class_getProperty(Class cls, const char *name)objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
你可以用property_getAttributes
函数来发掘属性的名称和@encode
类型字符串:
const char *property_getAttributes(objc_property_t property)
把上面的代码放一起,你就能从一个类中获取它的属性啦:
id LenderClass = objc_getClass("Lender");unsigned int outCount, i;objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));}
对比下 class_copyIvarList
函数,使用 class_copyPropertyList
函数只能获取类的属性,而不包含成员变量。但此时获取的属性名是不带下划线的。
二.runtime常用用法
1.获取类的成员变量
- (IBAction)getClassMember:(UIButton *)sender { // 定义一个变量接收属性数目 unsigned int outCount = 0; // 获得person类的成员变量列表 Ivar *ivarArray = class_copyIvarList([UIView class], &outCount); // 遍历列表 for (int i = 0; i < outCount; ++i) { Ivar ivar = ivarArray[i]; // 获得成员变量名 const char *ch = ivar_getName(ivar); // 获得成员变量类型 const char *type = ivar_getTypeEncoding(ivar); // C字符串转OC字符串 NSString *varName = [NSString stringWithUTF8String:ch]; NSString *varType = [NSString stringWithUTF8String:type]; NSLog(@"%@ %@",varType,varName); } // 释放 free(ivarArray);}
2.获得类中的所有属性
- (IBAction)getClassProperty:(UIButton *)sender { unsigned int count = 0; objc_property_t *propertyArray = class_copyPropertyList([Person class], &count); for (int i = 0; i < count; ++i) { objc_property_t property = propertyArray[i]; const char *name = property_getName(property); NSString *proName = [NSString stringWithUTF8String:name]; NSLog(@"%@",proName); } // 释放 free(propertyArray);}
3.获得类的全部方法
- (IBAction)getClassMethod:(UIButton *)sender { // 定义一个变量接收方法数量 unsigned int count = 0; // 获得类的方法 Method *methodArray = class_copyMethodList([Person class], &count); // 遍历类的方法 for (int i = 0; i < count; ++i) { Method method = methodArray[i]; // 获得方法名 SEL sel = method_getName(method); // 获得方法的实现// IMP imp = method_getImplementation(method); NSLog(@"%@",NSStringFromSelector(sel)); } // 释放 free(methodArray);}
4.获得类的全部协议
- (IBAction)getClassProtocal:(UIButton *)sender { unsigned int count = 0; // 获得指向该类遵循的所有协议的数组指针 __unsafe_unretained Protocol **protocolArray = class_copyProtocolList([self class], &count); for (int i = 0; i < count; ++i) { Protocol *protocol = protocolArray[i]; const char *name = protocol_getName(protocol); NSString *protocolName = [NSString stringWithUTF8String:name]; NSLog(@"%@",protocolName); } // 释放 free(protocolArray);}
5.动态改变成员变量
- (IBAction)changeClassMember:(UIButton *)sender {// self.student.name = @"willphonez"; // 定义一个变量保存成员变量数量 unsigned int count = 0; // 获得成员变量的数组的指针 Ivar *ivarArray = class_copyIvarList([self.student class], &count); // 遍历成员变量数组 for (int i = 0; i < count; ++i) { Ivar ivar = ivarArray[i]; // 获得成员变量名 NSString *ivarName =[NSString stringWithUTF8String:ivar_getName(ivar)]; // 根据成员变量名找到成员变量 if ([ivarName isEqualToString:@"_scret"]) { // 对成员变量重新赋值 object_setIvar(self.student, ivar, @"aibaozi"); break; } } // 释放 free(ivarArray); // 打印结果// NSLog(@"%@",self.student.name); [self.student run];}
6.动态交换类方法
- (IBAction)changeClassMethod:(UIButton *)sender { // 获得两个方法 Method run = class_getInstanceMethod([Person class], @selector(run)); Method eat = class_getInstanceMethod([Person class], @selector(eat)); // 交换对象方法实现 method_exchangeImplementations(run, eat); // 调用方法,或发现两个方法的实现交换了 [self.student run]; [self.student eat]; // runtime修改的是类,不是单一的对象,一次修改,在下次编译前一直有效 Person *p = [[Person alloc] init]; [p run]; [p eat]; // 也可以在category中添加自己的方法去替换 (包括系统类的方法) [Person sleep];}
7.动态添加方法
- (IBAction)addClassMethod:(UIButton *)sender { // 添加方法的实现到fromCity方法 class_addMethod([self.student class], @selector(fromCity:),(IMP)fromCityAnswer, nil); // runtime修改的是类,不是单一的对象,一次修改,在下次编译前一直有效 if ([self.student respondsToSelector:@selector(fromCity:)]) { [self.student performSelector:@selector(fromCity:) withObject:@"广州"]; } else { NSLog(@"无可奉告"); }}void fromCityAnswer(id self, SEL _cmd, NSString *string) { NSLog(@"我来自:%@",string);}
8.动态给category扩展属性
- (IBAction)addCategoryProperty:(UIButton *)sender { self.student.girlFriend = @"baozijun"; NSLog(@"%@",self.student.girlFriend);}
Person+ZWF.h
文件
#import "Person.h"@interface Person (ZWF)// 扩展属性@property (nonatomic, strong) NSString *girlFriend;@end
Person+ZWF.m
文件
#import "Person+ZWF.h"#import <objc/runtime.h>@implementation Person (ZWF)char friend;- (NSString *)girlFriend{ // 获取相关连得对象时使用Objc函数objc_getAssociatedObject return objc_getAssociatedObject(self, &friend);}- (void)setGirlFriend:(NSString *)girlFriend{ // 创建关联要使用objc_setAssociatedObject // 4个参数依次是(源对象, 关键字, 关联对象, 关联策略) objc_setAssociatedObject(self, &friend, girlFriend, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}+ (void)load{ // 获得两个类方法 Method sleep = class_getClassMethod(self, @selector(sleep)); Method noSleep = class_getClassMethod(self, @selector(noSleep)); // 交换两个类方法的实现 method_exchangeImplementations(sleep, noSleep);}+ (void)noSleep{ NSLog(@"%s",__func__);}@end
Demo源码已上传github:runtimeDemo,欢迎下载!
- 【iOS开发】初识runtime
- iOS开发-初识runtime
- IOS高级开发~Runtime
- IOS开发中的Runtime
- iOS开发-浅解runtime
- iOS开发 runloop&runtime
- 【iOS开发】runtime机制
- iOS开发:Runtime详解
- iOS开发-Runtime详解
- iOS开发-Runtime详解
- iOS开发-Runtime详解
- 【iOS开发】Runtime详解
- 初识 runtime
- iOS开发-Quartz2D初识
- IOS开发初识
- iOS开发:初识xib
- IOS开发-初识CoreData
- Runtime(一) 初识runtime
- [Array]Combination Sum
- 计算机的组成 —— VGA
- Java/Android 容易疏忽面试题整理
- 码神-day8-java
- windows刻录编程
- iOS开发-初识runtime
- Oracle Linux 6.7中使用service multipathd reload命令之后,Oracle 数据库crash掉.
- 欢迎使用CSDN-markdown编辑器
- [Array]Combination Sum II
- Java中BIO,NIO和AIO使用样例
- Python文件读写时的换行符与回车符
- Notification
- frame与bounds的含义与区别
- iOS 清除xcode缓存和生成文件