什么是runtime?
- 从字面上理解为 运行时,简单来说 runtime是一个实现OC语言的C库。
- 我们编写的OC代码会在程序运行过程中会转换成runtime的c语言代码,当然我们也可以直接运用runtime提供的APi进行魔法操作。
- OC是运行时动态语言在运行时将对象类型确定、方法调用、代码和资源的装载等。
Runtime 概念 及术语
1. Object(objc_object) 实例
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
- 这里我们看到 objc_object 结构体 里面只包含一个 class类型 的isa 指针。
这里也就说明 一个Object (实例)唯一保存的就是他所属Class(类)的地址,当我们对一个 实例进行方法调用时候。
例如 [object message] ,会通过objc_object结构体的 isa指针 去找到到对应的 objec_class 结构体。
2. Class(objc_class) 类
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa; // objc_class 结构体的实例指针
#if !__OBJC2__
Class _Nullable super_class; // 指向父类的指针
const char * _Nonnull name; // 类的名字
long version; // 类的版本信息,默认为 0
long info; // 类的信息,供运行期使用的一些位标识
long instance_size; // 该类的实例变量大小;
struct objc_ivar_list * _Nullable ivars; // 该类的实例变量列表
struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
struct objc_cache * _Nonnull cache; // 方法缓存
struct objc_protocol_list * _Nullable protocols; // 遵守的协议列表
#endif
这里我们看到 objc_object 结构体里面定义了很多变量 通过命名不难发现 结构体里面保存了 指向父类的 指针、类的名字、版本、实例大小、实例变量 list 方法 list 缓存 协议列表 等。一个类包含的信息不就正式这些吗?
objc_class结构体 的第一个成员变量是 isa指针,isa指针 保存的是所属类的结构体的实例指针也就是对象。
所以Class(类)的本质就是一个对象, 称之为 类对象 。类对象就是一个结构体 struct objc_class ,这个结构体存放的数据 称之为 元数据 (metadata)
3. Meta Class(元类)
从上面可以看出,对象(objc_object)结构体的 isa指针 指向的是对应 类对象(objc_class)结构体 ,那么类对象 (objc_class)的isa指向什么?答案指向 元类
元类 是类对象(objc_class) 的类 听起来绕口 下面看它的作用就会豁然开朗
在OC中,每当我们创建一个类。在编译时就会创建一个元类,而这个元类的对象 就是我们创建的这个类。(我们创建的类本质也是一个对象objc_class)
那么为什么要有元类?我们看类对象( objc_class结构体) ivars 用来存放属性变量,objc_method_list 用来存放 实例方法 (-方法),那一些静态变量 和 类(+)方法 哪里去了? 没错,他们就是存放在 元类的methodLists和ivars里面。
4. Method(objc_method)
/// An opaque type that represents a method in a class definition.
/// 代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name; /// 方法名
char * _Nullable method_types; /// 方法类型
IMP _Nonnull method_imp; ///方法实现
};
- SEL _method_name (方法名)释义
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL: 是一个指向 objc_selecter 结构体的指针,但是在runtime相关头文件中 并没有找到明确的定义,经过测试打印 得出 猜测 结论 SEL 只是一个保存方法名的字符串。
- char *_Nullable method_types(方法类型)释义
方法类型 method_types 是个字符串,用来存储方法的参数类型和返回值类型。
- IMP method_imp(方法实现)释义
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。
objc_class (类对象)结构体中的 methodLists(方法列表)中存放的元素就是Method(方法)
由此看出Method将 SEL(方法名)和IMP(函数指针)相关联。当对一个对象 发送消息的时候 会通过SEL(方法名)去找到IMP(函数指针)进行执行。
消息机制的基本原理
根据上面分析,大家脑海中应该会有一个方法调用,也就是消息发送的全过程。下面总结一下
///实例方法调用
Person * p = [[Person alloc]init];
[p eat];
///类方法调用
[Person sleep];
#import "Person.h"
@implementation Person
-(void)eat;
{
NSLog(@"吃饭");
}
+(void)sleep;
{
NSLog(@"睡觉");
}
@end
对象方法调用过程
通过 实例(p) 的isa指针 找到 实例( p)的 Class(objc_class类对象);
在Class(objc_class类对象)的结构体中找到 cache(方法缓存)散列表 中根据method_name 看里面有没有对应的IMP(方法实现);
如果没有找到 就继续在 Class(objc_class类对象)的methodLists(方法列表中)寻找对应的selector ,如果找到,填充到cache(方法缓存)中,并返回 selector;
如果Class(类)中没有找到这个selector ,就继续在他的superClass中寻找;
一旦找到对应的selector,就直接执行对应selector方法实现的IMP(方法实现);
若找不到对应的selector,即将进入 runtime的 消息转发机制。消息转发不做处理 程序发生崩溃。
类方法的调用
上面我们有说过 我们创建的类 在编译时会创建一个元类,而这个元类的对象 就是我们创建的这个类
通过Class(objc_class类对象) 的isa指针找到所属元类;
在进行上述后续操作。
runtime可以干什么?
- 获取类中所有的实例变量
class_copyIvarList()返回一个指向类的成员变量和属性数组的指针
class_copyPropertyList()返回一个指向类的属性数组的指针
///我是.m
#import "Person.h"
@interface Person ()
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *age;
@property (nonatomic,copy) NSString *sex;
@end
@implementation Person
{
NSNumber * phone;
}
@end
#import
#import "Person.h"
#import // 包含对类、成员变量、属性、方法的操作
#import // 包含消息机制
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
// 返回所有的属性和实例变量 class_copyIvarList
unsigned int methodCount = 0;
Ivar * ivars = class_copyIvarList([Person class], &methodCount);
for (unsigned int i = 0; i< methodCount; i++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
const char * type = ivar_getTypeEncoding(ivar);
NSLog(@" Persons成员变量属性为%s==类型%s",name,type);
}
free(ivars);
// 只返回 属性方法 class_copyPropertyList
unsigned int method_Count = 0;
objc_property_t * properties = class_copyPropertyList([Person class], &method_Count);
for (unsigned int i = 0; i< method_Count; i++) {
objc_property_t property = properties[i];
const char * properName = property_getName(property);
NSLog(@" Person 属性为%s",properName);
}
free(properties);
}
return 0;
}
2019-10-29 14:18:17.595647+0800 runtime[7816:359804] Hello, World!
2019-10-29 14:18:17.596917+0800 runtime[7816:359804] Persons成员变量属性为phone==类型@"NSNumber"
2019-10-29 14:18:17.597031+0800 runtime[7816:359804] Persons成员变量属性为_name==类型@"NSString"
2019-10-29 14:18:17.597093+0800 runtime[7816:359804] Persons成员变量属性为_age==类型@"NSString"
2019-10-29 14:18:17.597145+0800 runtime[7816:359804] Persons成员变量属性为_sex==类型@"NSString"
2019-10-29 14:18:17.597213+0800 runtime[7816:359804] Person 属性为name
2019-10-29 14:18:17.597608+0800 runtime[7816:359804] Person 属性为age
2019-10-29 14:18:17.597708+0800 runtime[7816:359804] Person 属性为sex
Program ended with exit code: 0
- 使用runtime动态添加一个类
objc_allocateClassPair:注册一个新类或者元类 如想让这个类成为基类那么参数superclass指针定为nil.参数extraByte是分配给类和元类对象尾部的索引ivars的字节数,通常指为0
objc_registerClassPair:当创建完新类后,需要调用这个方法注册这个类 之后这个类才可以在程序中使用
objc_disposeClassPair:用于销毁一个类及其元类,需要注意的是,如果程序运行中还存在类或者其子类的实例,那么就不能调用此方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
//创建一个新类
Class myClass = objc_allocateClassPair([NSObject class], "myClass", 0);
//添加ivar
//@encode(aType):返回该类型的字符串
class_addIvar(myClass, "_address", sizeof(NSString*), log2(sizeof(NSString*)),@encode(NSString*));
class_addIvar(myClass, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));
//注册类
objc_registerClassPair(myClass);
//创建实例
id object = [[myClass alloc] init];
//为ivar赋值
[object setValue:@"china" forKey:@"address"];
[object setValue:@20 forKey:@"age"];
NSLog(@"address=%@,age=%@",[object valueForKey:@"address"],[object valueForKey:@"age"]);
//当类或者它的子类的实例还存在 则不能调用 objc_disposeClassPair
object = nil;
//销毁
objc_disposeClassPair(myClass);
}
return 0;
}
2019-10-29 15:06:02.931562+0800 runtime[8254:384872] Hello, World!
2019-10-29 15:06:02.936912+0800 runtime[8254:384872] address=china,age=20
Program ended with exit code: 0
- 在category中增加属性(众所周知正常来说不可以添加但是用runtime完全可以实现)
给Person类添加个Category
#import
#import"Person.h"
@interface Person (Category)
//不会生成添加属性的getter和setter方法,必须我们手动生成
@property (nonatomic, copy) NSString *phone;
@end
#import "Person+Category.h"
#import
#import
@implementation Person (Category)
// 定义关联的key
static const char *key = "phone";
/**
phone的getter方法
*/
-(NSString *)phone
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
/**
phone的setter方法
*/
-(void)setPhone:(NSString *)phone
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, phone, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#import
#import "Person.h"
#import "Person+Category.h"
#import // 包含对类、成员变量、属性、方法的操作
#import // 包含消息机制
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
Person * per = [[Person alloc]init];
per.phone = @"111000001011";
NSLog(@"Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=%@",per.phone);
}
return 0;
}
2019-10-29 15:26:13.400545+0800 runtime[8485:392320] Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=111000001011
Program ended with exit code: 0
- runtime进行消息传递
#import "Person.h"
@interface Person ()
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *age;
@property (nonatomic,copy) NSString *sex;
@end
@implementation Person
{
NSNumber * phone;
}
-(void)callPhone;
{
NSLog(@"电话正在拨打中。。。");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
Person * person = [[Person alloc]init];
// OC中,当调用某个对象的方法时,其实质上就是向该对象发送了一条消息,比如:
//
// [person callPhone]; 本质为以下代码
objc_msgSend(person, @selector(callPhone));
}
return 0;
2019-10-29 15:53:47.954497+0800 runtime[8667:403765] Hello, World!
2019-10-29 15:53:47.955521+0800 runtime[8667:403765] 电话正在拨打中。。。
Program ended with exit code: 0
- 使用runtime动态添加方法[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
//Person类中并不存在 sendMassage方法
Person * person = [[Person alloc]init];
SEL aSel = NSSelectorFromString(@"sendMassage");
[person performSelector:aSel];
}
return 0;
}
运行
-[Person sendMassage]: unrecognized selector sent to instance 0x100626da0
运行崩溃此异常为没有找到sendMassage ,消息转发过程以程序崩溃结束 但是可以利用runtime补救 程序消息转发详解请看这里[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)
//person.m 没有找到sendMassage方法调用callPhone方法吧
-(void)callPhone;
{
NSLog(@"电话正在拨打中。。。");
}
//动态补加方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSString * str = NSStringFromSelector(sel);
if ([str isEqualToString:@"sendMassage"]) {
// 得到实例方法
Method _Nonnull m = class_getInstanceMethod(self, @selector(callPhone));
// 返回方法的调用地址
IMP imp = method_getImplementation(m);
// 给类添加一个新的方法和该方法的具体实现
class_addMethod(self, @selector(sendMassage), imp, "");
}
return [super resolveInstanceMethod:sel];
}
- 利用kvc和runtime暴力访问私有属性和变量
Person * person = [[Person alloc]init];
//kvo 暴力访问 私有属性和私有变量
[person setValue:@"小明" forKey:@"name"];
NSString * pname = [person valueForKey:@"name"];
[person setValue:@"19" forKey:@"age"];
NSString * page = [person valueForKey:@"age"];
[person setValue:@"男" forKey:@"sex"];
NSString * psex = [person valueForKey:@"sex"];
[person setValue:@1111111111 forKey:@"phone"];
NSString * pphone = [person valueForKey:@"phone"];
NSLog(@"姓名%@,年龄%@,性别%@,电话%@",pname,page,psex,pphone);
// runtime如何来做?
unsigned int count = 0;
//获取所有属性变量名字
Ivar * members= class_copyIvarList([Person class], &count);
for (unsigned int i = 0; i
- 使用runtime进行方法交换
为什么在load里面交换请看 [load方法和initialize方法异同] (https://www.jianshu.com/p/d4ce4530246e)
///我是Person.m
+ (void)load
{
//获取实例的方法
Method M1 = class_getInstanceMethod(self, @selector(callBB机));
Method M2 = class_getInstanceMethod(self, @selector(callPhone));
//交换方法
method_exchangeImplementations(M1, M2);
//获取类方法
Method cl1 = class_getClassMethod(self, @selector(callQQ));
Method cl2 = class_getClassMethod(self, @selector(callWX));
//交换方法
method_exchangeImplementations(cl1, cl2);
}
-(void)callBB机;
{
NSLog(@"打bb机中..");
}
-(void)callPhone;
{
NSLog(@"电话正在拨打中。。。");
}
+(void)callQQ
{
NSLog(@"打QQ中..");
}
+(void)callWX
{
NSLog(@"打微信中..");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person * person = [[Person alloc]init];
NSLog(@"打电话------------");
[person callPhone];
NSLog(@"打BB机------------");
[person callBB机];
NSLog(@"打QQ------------");
[Person callQQ];
NSLog(@"打WX------------");
[Person callWX];
}
return 0;
运行
2019-10-29 17:56:41.131014+0800 runtime[9536:457820] 打电话------------
2019-10-29 17:56:41.131757+0800 runtime[9536:457820] 打bb机中..
2019-10-29 17:56:41.131928+0800 runtime[9536:457820] 打BB机------------
2019-10-29 17:56:41.131989+0800 runtime[9536:457820] 电话正在拨打中。。。
2019-10-29 17:56:41.132038+0800 runtime[9536:457820] 打QQ------------
2019-10-29 17:56:41.132083+0800 runtime[9536:457820] 打微信中..
2019-10-29 17:56:41.132127+0800 runtime[9536:457820] 打WX------------
2019-10-29 17:56:41.132173+0800 runtime[9536:457820] 打QQ中..
- 使用runtime为model赋值
建PersonModel类
///我是.h
#import
@interface PersonModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *age;
@property (nonatomic,copy) NSString *sex;
@property (nonatomic,copy) NSString *phone;
+(instancetype)modelWithDict:(NSDictionary*)dict;
@end
///我是.m
#import "PersonModel.h"
#import
@implementation PersonModel
+(instancetype)modelWithDict:(NSDictionary*)dict;
{
id object = [[self alloc]init];
unsigned int count = 0;
Ivar * ivarList = class_copyIvarList(self, &count);
for (int i = 0; i
main函数赋值
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSDictionary * data = @{@"name":@"小花花",
@"age":@"10",
@"sex":@"女",
@"phone":@"1111111111111"};
PersonModel * model = [PersonModel modelWithDict:data];
NSLog(@"name:%@.age:%@.sex:%@.phone:%@",model.name,model.age,model.sex,model.phone);
}
return 0;
}
运行 模型赋值成功
2019-10-30 11:21:59.319949+0800 runtime[18571:872011] name:小花.age:10.sex:女.phone:1111111111111
Program ended with exit code: 0
- 自动归档解档
当我们需要将一个对象进行归档时,都要让该对象的类遵守NSCoding协议,再实现归档和接档方法。以上面的PersonModel为例
/**
* 将对象写入某个文件时需要调用,在该方法中说明哪些属性需要存储
*/
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:self.name forKey:@"name"];
[coder encodeObject:self.age forKey:@"age"];
[coder encodeObject:self.sex forKey:@"sex"];
[coder encodeObject:self.phone forKey:@"phone"];
}
/**
* 从文件中解析对象时会调用,在该方法中解析对象的属性
*/
- (nullable instancetype)initWithCoder:(NSCoder *)coder;
{
if (self = [super init]) {
// 解析之后要赋值给属性
_name = [coder decodeObjectForKey:@"name"];
_age = [coder decodeObjectForKey:@"age"];
_sex = [coder decodeObjectForKey:@"sex"];
_phone = [coder decodeObjectForKey:@"phone"];
}
return self;
}//
如果这个类属性上百个那一个一个的写会累死 而且还很low
应该使用runtime来进行
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
id value = [coder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
// 获得c的父类
c = [c superclass];
free(ivar);
}
}
return self;
}
/**
* 从文件中解析对象时会调用,在该方法中解析对象的属性
*/
- (void)encodeWithCoder:(NSCoder *)coder;
{
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[coder encodeObject:value forKey:key];
}
c = [c superclass];
// 释放内存
free(ivar);
}
}
参考资料
博文:『Runtime』详解(一)基础知识
博文: iOS Runtime详解