iOS之OC深入理解iOS开发iOS Developer

重新学习 "运行时"(RunTime)

2017-02-16  本文已影响71人  赤子追梦心

运行时:

Runtime, 通过字面理解, 是系统运行时候的一些机制. 它是OC中一套用C语言写的API

runtime机制在我们实际开发过程中经常体现出来, 比如:

1. 一个btn, 他的触发方法我们没有定义, 编译过程中, 只会提示警告, 但是在真正运行的时候, 就会crash

2. 无论MVC, MVVM框架, 我们都要创建Model, 在BaseModel中我们经常会会写这样一个方法, 只有在运行的时候, 才知道具体哪一个对象会调用这个方法

+ (instancetype)baseModelWithDic:(NSDictionary *)dic {

         id model = [[[self class] alloc] initWithDic:dic];

         return model;

}

runtime功能非常强大, 在很多优秀的第三方框架中都有用到, 首先来了解一下runtime有哪些功能

/*

runtime常用方法总结:

1.1 获得一个类所有的成员变量

      class_copyIvarList(Class cls , unsigned int *outCount)

1.2 获取类的成员变量的名字

      ivar_getName(Ivar v)

1.3 获取类的成员变量的类型

       ivar_getTypeEndcoding(Ivar v)

2.1 获取类的类方法

       class_getClassMethod((Class cls , SEL name)

2.2 获取类的实例变量方法

       class_getInstanceMethod(Class cls , SEL name)

2.3 交换两个方法

       method_exchangeImplementations(Method m1 , Method m2)

3.1 将值存储到对象的object中

       objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)

3.2 根据key 取出object中存储的值

       objc_getAssociatedObject(id object , const void *key)

*/


一. runtime实现了哪些Obj-C实现不了的功能

1. 获得某个类所有的成员变量, 成员方法;

2. 为一个类动态添加成员变量, 成员方法;

3. 动态交换两个方法

4. 发送消息

二. 利用runtime我们可以做哪些工作

1. 实现一个自定义的对象完成本地存储, 我们需要一一的对每一个属性进行decodeObjectForKey: 和 encodeObject:

我们可以利用class_copyIvarList方法获取当前类的所有成员变量, 然后遍历, 再利用ivar_getName将成员变量的名字当做key进行归档解档, 从而实现自动归解档, 无需考虑实际有多少个变量(获取当前类的所有成员变量)

这样如果属性多了, 或者后期经常添加或修改属性, 那就会非常的麻烦, 所以, 我们可以利用runtime的'能获取类所有的成员变量'这个方法, 动态的对该对象进行自动归档,解档

2. 给Category动态的添加属性(为一个类添加成员变量成员方法)

之前提过, Category只能添加方法, 不能添加属性, 但是我们可以用runtime给Category添加属性, 这里我们利用的是objc_setAssociatedObject方法将值关联到设置的属性上

例如: 给UIViewController添加一个副标题属性

1->首先我们需要在声明文件中声明一个属性:

#import@interface UIViewController (Subhead)

@property(nonatomic, copy) NSString *subHead;

@end

2->接着在实现文件中引runtime头文件

       set, get方法内部是根据键值来存取的, 我们需要给这个属性定义个key, 然后让set, get方法中根据这个key来存取属性的值

#import "UIViewController+Subhead.h"

#import <objc/runtime.h>

NSString *const kName = @"name";

@implementation UIViewController (Subhead)

// 重写set方法

- (void)setSubHead:(NSString *)subHead {

objc_setAssociatedObject(self, (__bridge const void *)(kName), subHead, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)subHead {

return objc_getAssociatedObject(self, (__bridge const void *)(kName));

}

@end

3. 开发中, 我们经常为了给运营统计数据, 会统计页面或按钮点击次数, 停留时间(方法的拦截, 用自己定义的方法与系统自带的方法交换)

首先我们要给UIViewController创建一个类目:Swizzling

#import "UIViewController+swizzling.h" 

#import@implementation UIViewController (swizzling)

+ (void)load {

[super load];

// 通过class_getInstanceMethod()函数获取当前类的方法

Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));

Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));

// 交换方法的实现

if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {

method_exchangeImplementations(fromMethod, toMethod);

}

}

// 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。

- (void)swizzlingViewDidLoad {

NSString *str = [NSString stringWithFormat:@"%@", self.class];

// 我们在这里加一个判断,将系统的UIViewController的对象剔除掉

if(![str containsString:@"UI"]){

NSLog(@"统计打点 : %@", self.class);

}

[self swizzlingViewDidLoad];   // 这里面实际调用的是原来的ViewDidLoad,因为此时已经被替换了

}

@end

结合runtime配合类目, 我们可以对归档解档进一步优化:

首先我们给NSObject写一个类目

声明文件中声明三个方法,忽略属性数组的get方法, 归档, 解档

#import@interface NSObject (Coder)

- (NSArray *)ignoreNames;

- (void)encode:(NSCoder *)enCode;

- (void)deCode:(NSCoder *)deCode;

@end

实现文件中, 只实现后两个方法

#import "NSObject+Coder.h"

#import <objc/runtime.h>

@implementation NSObject (Coder)

// 归档

- (void)encode:(NSCoder *)enCode {

// 从子类往父类进行查找,对父类的属性执行归解档方法

Class sClass = self.class;

while (sClass && sClass != [NSObject class]) {

unsigned int outCount = 0;

Ivar *ivars = class_copyIvarList([self class], &outCount);

for (int i = 0; i < outCount; i++) {

Ivar ivar = ivars[i];

NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 如果有需要忽略的Key 跳过归档

if ([self respondsToSelector:@selector(ignoredNames)]) {

if ([[self ignoreNames] containsObject:key]) continue;

}

id value = [self valueForKeyPath:key];

[enCode encodeObject:value forKey:key];

}

free(ivars);

sClass = [sClass superclass];

}

}

// 解档

- (void)deCode:(NSCoder *)deCode {

// 一层层父类往上查找,对父类的属性执行归解档方法

Class sClass = self.class;

while (sClass && sClass != [NSObject class]) {

unsigned int outCount = 0;

Ivar *ivars = class_copyIvarList(sClass, &outCount);

for (int i = 0; i < outCount; i++) {

Ivar ivar = ivars[i];

NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 如果有需要忽略的Key 跳过解档

if ([self respondsToSelector:@selector(ignoredNames)]) {

if ([[self ignoreNames] containsObject:key]) continue;

}

id value = [deCode decodeObjectForKey:key];

[self setValue:value forKey:key];

}

free(ivars);

sClass = [sClass superclass];

}

}

@end

上一篇下一篇

猜你喜欢

热点阅读