我所理解的RunTime

runTime 是ios底层运行时机制,他可以动态获取类的成员属性,变量,更可以改变这些东西!

要使用runTime ,必须先导入头文件:#import <objc/message.h>

字典转模型实例: 这是h文件,m文件无需写任何代码

@interface Lender : NSObject{

CGFloat height;

}

@property (nonatomic, copy) NSString*name;

@property (nonatomic, strong) NSNumber*age;

@property (nonatomic, assign)intno;@end

@end

下面就用RunTime来做些事情,比如获取这些变量或者改变这些东西

#import<objc/message.h>

unsignedintoutCount = 0;

Ivar*vars = class_copyIvarList([Lender class], &outCount);//获取到所有的变量列表

for(inti =0; i < outCount; i++) {//遍历所有的成员变量

Ivar ivar= vars[i];//取出第i个位置的成员变量

constchar*propertyName = ivar_getName(ivar);//通过变量获取变量名

constchar*propertyType = ivar_getTypeEncoding(ivar);//获取变量编码类型

printf("---%s--%s\n", propertyName, propertyType);

}

打印结果

---height--f

---_name--@"NSString"

---_age--@"NSNumber"

---_no--i

可见,通过这几句简单的代码就可以获取到某个类中所有变量的名称和类型,然后通过object_setIvar()方法为具体某个对象的某个成员变量赋值

其他常用情景(熟记)

1.动态(dynamic)调换(exchange)两个方法(method)的执行(implementation)。常用于类别中(catagory)。

2.动态遍历类所有的成员变量(property),实现数据字典(Dictionary)快速转实体类(模型)。常用于数据-模型处理。

3.动态遍历类所有的成员变量(property),实现归档(encode)与解档(decode)。

4.动态关联。可以给分类新增成员变量。(分类本是不允许添加成员变量的。)

一、动态调换方法:

NSMutableArray有个自带的方法addObject:(id)object,这个参数object是不能为nil的,如果这样调用[array addObject:nil],程序会无情crash。我要实现就算添加了nil,程序也不会crash。

新建类别(NSMutableArray+KXX),并在NSMutableArray+KXX.m文件中导入头文件。

在NSMutableArray+KXX.m文件中,实现方法- (void)newMethodToAddObject:(id)objdect。

- (void)newMethodToAddObject:(id)object {

if (!object) {

NSLog(@"object为空,已被处理!!!");

}

}

在NSMutableArray+KXX.m文件中,实现方法+ (void)load。这个方法会在对象内存被分配、初始化后就自动调用。

+ (void)load {

// NSMutableArray 真正的类名是 __NSArrayM!!!! NSMutableDictionary同理。

Method _originMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(addObject:));

Method _newMethod = class_getInstanceMethod([self class], @selector(newMethodToAddObject:));

// 调换两个方法的执行

method_exchangeImplementations(_originMethod, _newMethod);

}

在main.m文件测试。

#import "NSMutableArray+KXX.h"

#import <objc/message.h>

@implementation NSMutableArray (KXX)

- (void)newMethodToAddObject:(id)object {

if (!object) {NSLog(@"object为空,已被处理!!!");}}

+ (void)load {

// NSMutableArray 真正的类名是 __NSArrayM。NSMutableDictionary同理。

Method _originMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(addObject:));

Method _newMethod = class_getInstanceMethod([self class], @selector(newMethodToAddObject:));

// 调换两个方法的执行

method_exchangeImplementations(_originMethod, _newMethod);

}

@end

运行后虽然有警告,但是不会crash.输出为

2016-03-04 20:02:53.101 runtime[55523:513861] object为空,已被处理!!!

Program ended with exit code: 0

二、字典转实体(模型)

从网络上下载的json数据解析出字典后,可以利用runtime来快速转成实体类。而不用一个一个的给实体属性赋值。还有就是当数据字典的键(key)和实体的成员变量不一致时的处理,这个时候使用字典的映射(map)。

1.新建一个继承于NSObject的类ModelBaseClass,这个作为一会儿实体类的模型基类。

2.在基类构建setter方法。

// 构建setter方法

- (SEL)createSetterMethodWithPropertyName:(NSString *)name {

if (!name) {

return nil;

}

// 首字母大写。

name = [NSString stringWithFormat:@"set%@:", name.capitalizedString];

// 生成setter方法。

return NSSelectorFromString(name);

}

3.构建获取映射字典方法

// 获取字典映射,返回nil是为了子类重写该方法。

- (NSDictionary *)getMapDictionary {

return nil;

}

4.分情况如果是key不一样(需要映射字典)和key一样(其实也可以不用分,就按不一样处理)

// 字典转模型。这是没有字典映射的,数据字典的key必须和model的成员变量一致。

- (void)assignValueToModelWithNoMapDictionary:(NSDictionary *)dict {

if (!dict) {

return;

}

// 取出所有的key

NSArray *keys = [dict allKeys];

for (int i = 0; i < [keys count]; i ++) {

// 生成selector

SEL setSel = [self createSetterMethodWithPropertyName:keys[i]];

if ([self respondsToSelector:setSel]) {

NSString *value = [NSString stringWithFormat:@"%@", dict[keys[i]]];

[self performSelectorOnMainThread:setSel withObject:value waitUntilDone:YES];

}

}

}

// 这是有字典映射的。

- (void)assignValueToModelWithDictionary:(NSDictionary *)dict {

// 取得映射字典。

NSDictionary *mapDict = [self getMapDictionary];

// 转换成key和数据字典一样的字典。

NSArray *dataDictKeys = [dict allKeys];

NSMutableDictionary *tempDict = [NSMutableDictionary dictionary];

for (int i = 0; i < [dataDictKeys count]; i ++) {

NSString *key = dataDictKeys[i];

[tempDict setValue:dict[key] forKey:mapDict[key]];

}

[self assignValueToModelWithNoMapDictionary:tempDict];

}

-

5.新建实体类Model,继承于ModelBaseClass。并导入头文件

6.在Model.h文件创建两个成员变量。

#import "ModelBaseClass.h"

@interface Model : ModelBaseClass

@property (nonatomic, copy)NSString *name;

@property (nonatomic, copy)NSString *gender;

@end

7.完成构造方法。这里要分情况,是否需要字典映射。

- (instancetype)initWithDataDictionary:(NSDictionary *)dict{

self = [super init];

if (self) {

//

if (![self getMapDictionary]) {

[self assignValueToModelWithNoMapDictionary:dict];

}

else {

[self assignValueToModelWithDictionary:dict];

}

}

return self;

}

// 便利构造

+ (instancetype)modelWithDictionary:(NSDictionary *)dict{

return [[self alloc] initWithDataDictionary:dict];

}

8.创建生成getter方法

// 取得getter方法

- (SEL)createGetterMethodWithPropertyName:(NSString *)name{

return NSSelectorFromString(name);

}

9.获取类的所有的成员变量。

// 取得所有的成员变量

- (NSArray *)getAllProperties {

NSMutableArray *array = [NSMutableArray array];

unsigned int count = 0;

objc_property_t *properties = class_copyPropertyList([self class], &count);

for (unsigned int i = 0; i < count; i ++) {

objc_property_t property = properties[i];

const char *propertyString = property_getName(property);

NSString *string = [NSString stringWithUTF8String:propertyString];

[array addObject:string];

}

free(properties);

return array;

}

10.通过类和方法的签名来获取调用对象。

// 通过方法和类的签名来调用对象。实现取值

- (void)displayProperty {

// 取得所有成员变量

NSArray *properties = [self getAllProperties];

for (int i = 0; i < [properties count]; i ++) {

// 取得getter

SEL getSel = [self createGetterMethodWithPropertyName:properties[i]];

if ([self respondsToSelector:getSel]) {

// 获取类和方法签名

NSMethodSignature *signature = [self methodSignatureForSelector:getSel];

// 从签名获得调用对象

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

// 添加target

[invocation setTarget:self];

// 添加selector

[invocation setSelector:getSel];

// 设置接收返回的值

NSObject *__unsafe_unretained returnValue = nil;

// 调用

[invocation invoke];

// 接收返回值

[invocation setReturnValue:&returnValue];

}

}

}

11.重写父类的取得映射字典的方法。这个字典的键就是数据字典的key,值就是类的成员变量。

- (NSDictionary *)getMapDictionary {

return @{@"姓名":@"name", @"性别":@"gender", @"这个没有":@"(づ。◕‿‿◕。)づ"};

}

12.如果数据字典的成员个数多余类的成员变量数量,就会造成NSMutableDictionary的setObject:(id)object forKey:(NSString *)key方法的object参数为nil,程序就会crash.为了解决这个问题,新建一个NSMutableDictionary的分类NSMutableDictionary+KXX.

#import "NSMutableDictionary+KXX.h"

#import

@implementation NSMutableDictionary (KXX)

- (void)newMethodSetObject:(id)object forKey:(NSString *)key{

if (!key) {

NSLog(@"nil 已被处理!!!");

}

[self newMethodSetObject:object forKey:key];

}

+ (void)load {

Method _origin = class_getInstanceMethod(objc_getClass("__NSDictionaryM"), @selector(setObject:forKey:));

Method _new = class_getInstanceMethod([self class], @selector(newMethodSetObject:forKey:));

method_exchangeImplementations(_origin, _new);

}

@end

13.在main.m测试

#import

#import "Model.h"

#import "NSMutableDictionary+KXX.h"

int main(int argc, const char * argv[]) {

@autoreleasepool {

// insert code here...

NSDictionary *mockDict = @{@"姓名":@"邝大爷", @"性别":@"Male", @"这个没有":@"(づ。◕‿‿◕。)づ"};

Model *model = [Model modelWithDictionary:mockDict];

NSLog(@"%@\n------>\n%@\n", model.name, model.gender);

}

return 0;

}

成功!!!输出

2016-03-04 21:43:13.248 Model[63415:591984] Kxx.xxQ -----> Male

Program ended with exit code: 0

三、归档和解档

如果需要实现一些基本数据的数据持久化(data persistance)或者数据共享(data share)。我们可以选择归档和解档。如果用一般的方法:

- (void)encodeWithCoder:(NSCoder *)aCoder {

[aCoder encodeObject:self.name forKey:@"nameKey"];

[aCoder encodeObject:self.gender forKey:@"genderKey"];

[aCoder encodeObject:[NSNumber numberWithInteger:self.age] forKey:@"ageKey"];

}

也可以实现。但是如果实体类有很多的成员变量,这种方法很显然就无力了。

这个时候,我们就可以利用runtime来实现快速归档、解档:

1.让实体类遵循协议。并在.m文件导入头文件。

2.实现- (instancetype)initWithCoder:(NSCoder *)aDecoder和- (void)encodeWithCoder:(NSCoder *)aCoder方法。

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

self = [super init];

if (self) {

//

unsigned int count = 0;

objc_property_t *properties = class_copyPropertyList([self class], &count);

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

objc_property_t property = properties[i];

const char *propertyChar = property_getName(property);

NSString *propertyString = [NSString stringWithUTF8String:propertyChar];

id value = [aDecoder decodeObjectForKey:propertyString];

[self setValue:value forKey:propertyString];

}

free(properties);

}

return self;

}

- (void)encodeWithCoder:(NSCoder *)aCoder {

unsigned int count = 0;

objc_property_t *properties = class_copyPropertyList([self class], &count);

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

objc_property_t property = properties[i];

const char *propertyChar = property_getName(property);

NSString *propertyString = [NSString stringWithUTF8String:propertyChar];

id object = [self valueForKey:propertyString];

[aCoder encodeObject:object forKey:propertyString];

}

free(properties);

}

3.在main.m文件测试归档、解档。

#import

#import "Model.h"

int main(int argc, const char * argv[]) {

@autoreleasepool {

Model *model = [Model new];

model.name = @"kxx";

model.age = 24;

model.gender = @"male";

NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"modelData.data"];

// 归档

BOOL flag = [NSKeyedArchiver archiveRootObject:model toFile:path];

if (flag) {

NSLog(@"archive object successfully!!!");

}

// 解档

Model *unarchivedModel = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSLog(@"\n\n%@\n%@\n%ld", unarchivedModel.name, unarchivedModel.gender, unarchivedModel.age);

}

return 0;

}

4.控制台输出

2016-03-06 18:52:32.368 NSCoder[38672:320789] archive object successfully!!!

2016-03-06 18:52:32.369 NSCoder[38672:320789]

kxx

male

24

Program ended with exit code: 0

5.查看路径文件会有一个数据文件存在:

四、动态关联对象

比如Objective-C是不能向一个类的类别(catagory)添加新成员变量的。但是我们可以利用runtime来实现动态添加成员变量。先把关联策略列出来:

Behavior                              @propertyequivalent                                     Description

OBJC_ASSOCIATION_ASSIGN@property(assgin) or@property(unsafe_unretained)弱引用

OBJC_ASSOCIATION_RETAIN_NONATOMIC@property(nonatomic,strong)强引用,线程不安全的

OBJC_ASSOCIATION_COPY_NONATOMIC@property(nonatomic,copy)可拷贝,线程不安全的

OBJC_ASSOCIATION_RETAIN@property(atomic,strong)强引用,线程安全的

OBJC_ASSOCIATION_COPY@property(atomic,copy)属性是可拷贝的,线程安全的

模式:

添加私有变量(private variables)来让方法执行更加完善。

给类别(catagory)增加公共属性来配置它的行为。

给KVO模式创建一个相关联的观察者(observer)。

1. 给类别增加新的公共属性。

新建一个NSObject类的类别NSObject+AssociatedObject。

在.h文件新增一个成员变量:

@property (nonatomic, strong)id newProperty;

然后在.m文件的implementation下面加上关键字@dynamic,表示这个新增的成员变量是动态加载的。否则类别是不能使用新增成员变量的。并且导入头文件。

#import "NSObject+AssociatedObject.h"

@implementation NSObject (AssociatedObject)

@dynamic newProperty;

@end

设置全局的静态的一会儿关联对象要用的唯一的key值:

static const void *uniqueKey = "newPropertyKey";

然后就是设置新成员变量的setter方法。

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

参数说明:

id object:需要关联属性的对象。一般是self。

const void *key:这就是刚刚设置的唯一的key。

id value: 关联属性的值。

objc_AssociationgPolicy policy:关联策略。(详见上面表格)

// setter

- (void)setNewProperty:(id)newProperty {

objc_setAssociatedObject(self, uniqueKey, newProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

然后就是设置getter方法。

// getter

- (id)newProperty {

return objc_getAssociatedObject(self, uniqueKey);

}

在main.m文件测试:

#import

#import "NSObject+AssociatedObject.h"

int main(int argc, const char * argv[]) {

@autoreleasepool {

NSObject *object = [NSObject new];

[object setNewProperty:@"Hello World"];

NSString *result = object.newProperty;

NSLog(@"%@", result);

}

return 0;

}

ok,输出:

2016-03-07 10:52:41.097 AssociatedObject[6237:58203] Hello World

Program ended with exit code: 0

github: runtime demo

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,697评论 7 64
  • 今天的自己比昨天更好 自从关注写手圈之后,我仿佛一个流浪的人找到家一样欣喜,觉得找到了收留自己的地方,这里像是一个...
    时光里的流沙阅读 727评论 0 0
  • ❀本文摘自延参法师直播 风雨过耳寻常事,不为沉浮费寒暄。生命需要倾诉,需要朗读,更需要的是以一个积极的状态去倾诉,...
    海月中天阅读 445评论 0 0