聊聊Objective-C的Runtime

栏目: Objective-C · 发布时间: 8年前

内容简介:聊聊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,在运行时动态创建一个对象。

实现原理:

  1. 创建 NSKVONotifying_XXX:XXX 类(XXX为被监听者)。
  2. 重写属性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)];

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Artificial Intelligence

Artificial Intelligence

Stuart Russell、Peter Norvig / Pearson / 2009-12-11 / USD 195.00

The long-anticipated revision of this #1 selling book offers the most comprehensive, state of the art introduction to the theory and practice of artificial intelligence for modern applications. Intell......一起来看看 《Artificial Intelligence》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具