聊聊Objective-C的Runtime
栏目: Objective-C · 发布时间: 7年前
内容简介:聊聊Objective-C的Runtime
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。
对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。Runtime基本上是用C和汇编写的,这个库使得 C语言 有了面向对象的能力。
在Runtime中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,让Objective-C的面向对象编程变为可能。
找出方法的最终执行代码:当程序执行 [object doSomething]
时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
1 消息机制
与古老的C语言不同,Objective-C虽然源自C语言,但是它却是面向对象的,在这之中,消息机制发挥着重大作用。
C语言和Objective-C编译时的区别:
C语言在编译的时候,已经知道调用哪一个函数。
Objective-C不一样,只有在运行时才知道需要调用的方法和函数。
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
使用这个方法要 #import <objc/message.h>
,另外,Apple在Xcode5开始,不建议使用底层方法,而恰巧以上方法就是底层方法。此时,Xcode就会报错,那么,如何解决呢?
解决方案如下:
-
1)打开Project的
Build Settings
,搜索“msg”。 -
2)将
Enable Strict Checking of objc_msgSend Calls
的值设置为NO。
发送无参消息
@interface Sample : NSObject + (void)run; - (void)run; - (void)eatWithFood:(NSString *)food; @end @implementation Sample + (void)run { NSLog(@"类方法 run"); } - (void)run { NSLog(@"实例方法 run"); } - (void)eatWithFood:(NSString *)food { NSLog(@"实例方法 eat:%@", food); } @end - (void)test { Sample *t = [[Sample alloc] init]; objc_msgSend(t, @selector(run)); objc_msgSend([Sample class], @selector(run)); }
发送带参消息
- (void)test { Sample *t = [[Sample alloc] init]; objc_msgSend(t, @selector(eatWithFood:), @"apple"); }
附加:将Objective-C转换出Runtime代码方法
clang -rewrite-objc xxxx.m
交换方法的实现
class_getInstanceMethod(__unsafe_unretained Class cls, SEL name)
获取对象方法。
class_getClassMethod(__unsafe_unretained Class cls, SEL name)
获取类方法。
method_exchangeImplementations(Method m1, Method m2)
交换方法的实现方式。
常见示例代码:
NSURL *url = [NSURL URLWithString:@"https://charsdavy.github.io"]; [NSURLRequest requestWithURL:url];
但 url
可能返回nil,而此段代码未能对返回值 url
进行合法判断。但每次使用以上类似代码都需要进行合法性判断,那么有什么更好的方法使 URLWithString:
能够做合法性判断呢?这个时候就需要使用Runtime的特有方法了。
@interface NSURL (DD) + (instancetype)dd_URLWithString:(NSString *)URLString; @end @implementation NSURL (DD) // 加载此分类时调用 + (void)load { //获取方法名称 Method urlMethod = class_getClassMethod([NSURL class], @selector(URLWithString:)); Method ddUrlMethod = class_getClassMethod([NSURL class], @selector(dd_URLWithString:)); //交换方法的实现 method_exchangeImplementations(urlMethod, ddUrlMethod); } //此方法与URLWithString:交换了实现方式 + (instancetype)dd_URLWithString:(NSString *)URLString { // NSURL *url = [NSURL URLWithString:URLString]; 此处不能再调用此方法,否则会死循环 NSURL *url = [NSURL dd_URLWithString:URLString]; if (!url) { NSLog(@"url is nil"); } return url; } @end
归档和解档
先来理解几个Objective-C中的概念:
序列化:将自定义的Objective-C的对象转化成二进制文件数据。
反序列化:将二进制文件数据转化成自定义的Objective-C的对象。
归档:将自定义的Objective-C的对象存储到本地磁盘。
解档:将存储在本地磁盘的数据转换成自定义的Objective-C的对象。
Ivar类型:成员属性。
Method类型:成员方法。
通常我们使用归档和解档的方式如下:
@interface Sample : NSObject<NSCoding> @property (nonatomic) NSString *name; @property (nonatomic) NSString *age; @end @implementation Sample - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.name forKey:@"name"]; [aCoder encodeObject:self.age forKey:@"age"]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { self.name = [aDecoder decodeObjectForKey:@"name"]; self.age = [aDecoder decodeObjectForKey:@"age"]; } return self; } @end - (void)saveObject { Sample *p = [[Sample alloc] init]; p.name = @"Chars"; p.age = @"18"; NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"chars.dat"]; BOOL flag = [NSKeyedArchiver archiveRootObject:p toFile:path]; if (flag) { NSLog(@"success"); } else { NSLog(@"falied"); } NSLog(@"%@", path); } - (void)readObject { NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"chars.dat"]; Sample *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; if (p) { NSLog(@"name:%@,age:%@", p.name, p.age); } else { NSLog(@"falied"); } NSLog(@"%@", path); }
Objective-C中归档底层实现方式:将对象拆分为字典(键值对),然后变成二进制存入磁盘。
但是,当model中成员属性数量很多的时候,就沦为了体力劳动。那么,此时我们又能使用Runtime来简化工作。
class_copyIvarList(__unsafe_unretained Class cls, unsigned int *outCount)
获取Class中成员变量的个数。
以下就是使用Runtime消息机制编写的归档与解档方法:
- (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Sample class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; [aCoder encodeObject:[self valueForKey:key] forKey:key]; } free(ivars); } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Sample class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } free(ivars); } return self; }
2 KVO
利用Runtime,在运行时动态创建一个对象。
实现原理:
-
创建
NSKVONotifying_XXX:XXX
类(XXX为被监听者)。 -
重写属性set方法,调用
willChangeValueForKey:
和didChangeValueForKey:
方法,进而触发调用观察者的observeValueForKeyPath:ofObject:change:context:
示例代码:
@interface Viewer : NSObject @property (nonatomic) NSString *name; @property (nonatomic) NSString *age; @end @interface Observer : NSObject @property (nonatomic) NSString *name; @property (nonatomic) NSString *age; @end @implementation Observer - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"object:%@, keyPath:%@, change:%@", object, keyPath, change); } @end Observer *observer = [[Observer alloc] init]; Viewr *viewer = [[Viewer alloc] init]; //注册监听,viewer为被监听者,observer为观察者 [viewr addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; //触发KVO viewer.age = @"99";
3 动态添加方法
当方法被调用时,才被加载。
+ (BOOL)resolveClassMethod:(SEL)sel;
当一个类被调用了一个没有实现的方法时,则会调用此方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
当一个类被调用了一个没有实现的实例方法时,则会调用此方法。
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
动态添加方法。
参数 cls
:类类型。参数 name
:方法编号。参数 imp
:方法实现,就是一个函数的指针。参数 * types
:方法类型
@implementation Sample //一个类被调用了一个没有实现的实例方法时,则会调用此方法。 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { //添加一个实例方法 class_addMethod([Sample class], sel, (IMP)eat, "v@:"); } return [super resolveInstanceMethod:sel]; } //隐式参数,self和_cmd是系统传过来的参数 void eat(id self, SEL _cmd) { NSLog(@"调用了%@的%@方法", self, NSStringFromSelector(_cmd)); } @end [[[Sample alloc] init] performSelector:@selector(eat)];
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。