iOS Runtime 应用实践

美女镇楼图.png

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。学会使用Runtime api ,在iOS开发中实现更多的骚操作。

Runtime介绍

Runtime其实有两个版本: “modern” 和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行 (Modern) 版的 Runtime 系统,只能运行在 iOSmacOS 10.5 之后的 64 位程序中。而 macOS 较老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OCC语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。

阅读使用官方Api,解决项目各种骚需求。

Runtime应用

Runtime提供了很多api,功能强大,应用场景非常多,下面主要介绍3中应用场景。

  • 关联对象(Objective-C Associated Objects)给分类增加属性;
  • 方法魔法(Method Swizzling)方法替换;
  • 实现字典和模型的自动转换;

关联对象(Objective-C Associated Objects)给分类增加属性

我们都是知道分类(category)中是不能自定义属性和变量的。但通过Runtime的关联对象方法,就可以给分类添加属性。

关联对象Runtime提供饿了下面几个api:

//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)

参数解释

id object:被关联的对象
const void *key:关联的key,要求唯一
id value:关联的对象
objc_AssociationPolicy policy:内存管理的策略

内存管理策略枚举

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

想要使用 runtime 的 api 需要引入#import "objc/runtime.h"头文件。
下面实现一个 UIViewControllerCategory 添加自定义属性NSString * newTitle

ViewController+Property.h

@interface ViewController (Property)

@property (nonatomic , strong) NSString *newTitle;

@end
--------------------------------------------------------------------------
ViewController+Property.m

#import "ViewController+Property.h"
#import "objc/runtime.h"

static char kNewTitleKey;

@implementation ViewController (Property)
- (void)setNewTitle:(NSString *)newTitle {

   objc_setAssociatedObject(self, &kNewTitleKey, newTitle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


- (NSString*)newTitle {

   return objc_getAssociatedObject(self, &kNewTitleKey);
}

@end

viewController 中的操作

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self testCategoryProperty];
}

- (void)testCategoryProperty {
    
    self.newTitle = @"FK";
    NSLog(@"%@",self.newTitle);
    NSLog(@"运行成功");
}

如果不使用runtime中的关联api对newTitle进行关联,运行后会闪退,并显示如下报错:


Category 属性取值赋值报错.png

关联成功后,newTitle setter getter 方法使用正常。

Category 属性取值赋值正常.png

成功为分类设置自定义属性。

方法魔法(Method Swizzling)方法替换

曾经在了解 +(void)load 方法时,也有了解过一下 Method Swizzling ,今天针对方法魔法进行实践操作。

实现方法替换的核心:

//方法实现替换api
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

下面实现一个替换 ViewControllerviewDidLoad 方法的例子。

@implementation ViewController

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(FKViewdidLoad);

        Method originalMethod = class_getInstanceMethod(class,originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);

        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSLog(@"viewDidLoad");
}

- (void)FKViewdidLoad {
    
    NSLog(@"FKViewdidLoad");
}

@end

最后的打印结果


犯法替换结果.png

viewDidLoadFKViewdidLoad 两个方法替换成功。

此处值得注意的是 swizzling 应该只在+load中完成。 在 Objective-C 的运行时中,每个类有两个方法都会自动调用。+load 是在一个类被初始装载时调用,+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。
swizzling应该只在dispatch_once 中完成,由于swizzling 改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatchdispatch_once满足了所需要的需求,并且应该被当做使用swizzling的初始化单例方法的标准。

实现图解.png

实现字典和模型的自动转换

原理描述:用runtime提供的函数遍历Model自身所有属性,如果属性在json中有对应的值,则将其赋值。 核心api:

//遍历类型中所有属性
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

NSObject 中添加方法:

@implementation NSObject (RuntimeModel)

- (void)initWithDict:(NSDictionary *)dict {
    
    //(1)获取类的属性及属性对应的类型
    NSMutableArray * keys = [NSMutableArray array];
    NSMutableArray * attributes = [NSMutableArray array];
    /*
     * 例子
     * name = value3 attribute = T@"NSString",C,N,V_value3
     * name = value4 attribute = T^i,N,V_value4
     */
    unsigned int outCount;
    objc_property_t * properties = class_copyPropertyList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = properties[i];
        //通过property_getName函数获得属性的名字
        NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        [keys addObject:propertyName];
        //通过property_getAttributes函数可以获得属性的名字和@encode编码
        NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
        [attributes addObject:propertyAttribute];
    }
    //立即释放properties指向的内存
    free(properties);
    
    //(2)根据类型给属性赋值
    for (NSString * key in keys) {
        if ([dict valueForKey:key] == nil) continue;
        [self setValue:[dict valueForKey:key] forKey:key];
    }
}

@end

viewController 中的操作

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.runtimeM initWithDict:@{@"time" : @"5点" , @"name" : @"FK" , @"sex" : @"Man"}];
    NSLog(@"%@ - %@ - %@",self.runtimeM.time , self.runtimeM.name , self.runtimeM.sex);
}

打印结果如下:


屏幕快照 2018-09-09 下午5.52.33.png

成功将字典类型转换为模型类型。

拓展
使用 class_copyPropertyList 遍历方法,可以遍历对象中所有属性,并且可以把所有属性制空。

总结

到这里,本文想介绍的 runtime 3个应用场景就介绍完了。本文已贴出Demo中核心代码,如果想阅读完整代码,可以下载Demo查看。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容