0、简介:

OC方法不同于C语言函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

  至于其他理论上的东西不必讲太多,编程讲的就是实用性,只记录一下各种使用场景。

使用运行时:(1)导入<objc/message.h> (2)Build Setting -> 搜索msg -> 设置属性为No(取消消息机制的检查)

一般见人写runtime第一个必讲消息机制,发消息是怎么回事

比如:新建一个dog类,头文件定义三个外部调用方法,内部实现这里就不写了,随意

 - (void)run; //对象方法

 + (void)run; //类方法

 - (void)eat:(NSString *)food; //带参数的实例方法

来到使用它的地方

     Dog *d = [[Dog alloc] init];

     [d run];

     // 消息机制:任何方法调用,本质都是发送消息
// SEL:方法编号,根据方法编号就可以找到对应方法实现
[d performSelector:@selector(run)]; // 让d发送消息
objc_msgSend(d, @selector(run));

基本可以理解为第3行代码底层调用第7行,第7行底层调用第10行

顺便也写一下OC方法的大概调用流程吧:

简单理解下就行了,看了下网上大神写的资料,感觉没必要记那么多繁复的东西

大概我理解就是:方法调用就是个发消息的过程,消息名即方法名,接收消息的对象即我们普遍认为的调用那个方法的对象。

SEL即方法编号,第3行调用方法之后去接收者那里找到对应的方法编号,通过方法编号找到方法映射表中的对应方法,最后根据方法映射表找到对应的方法实现。

来个图更明显点:

为避免误读,对上面的图做个补充,以免为初学者产生一个错误的映射表内存模型:

类与对象相比只是多了实例变量和方法列表等,类和对象都是对象,分别是类对象和实例对象。

在class中的isa指针指向的是metaClass,metaClass中存放的是静态成员变量和类方法(+开头)。在object中的isa指针指向的是对应的类结构:Class,Class其中存放的是普通成员变量和实例方法(-开头)。

所有的metaclass中isa指针都是指向根metaclass,而根metaclass则指向自身。根metaclass是通过继承根类产生的,与根class结构体成员一致,不同的是根metaclass的isa指针指向自身。

实例对象存放对象方法的映射表,类对象存放类方法的映射表,因此上面的两个方法其实是在不同的映射表中的。

上面的代码是对象无参调用的方式,补充一下类方法和含参方法:

 //类名调用类方法的本质就是类名转换成类对象
[Dog run]; // 获取类对象
Class dogClass = [Dog class]; //上面方法会调用这个
[dogClass performSelector:@selector(run)]; //最终干的事还是发消息
objc_msgSend(dogClass, @selector(eat));

最后说含参方法:

 //含参方法
[d eat:@""]; [d performSelector:@selector(eat:) withObject:@""]; objc_msgSend(d, @selector(eat:),@"");

顺便补充一下:含参方法performSelector最多只能传入两个参数,参数更多的时候多余的参数可以放到字典、数组里,这个不存在问题,demo里面有写。

1、当你希望给系统方法扩展一些功能,并且保持原有的功能时;

直接上代码:

分类:

#import <UIKit/UIKit.h>

@interface UIColor (extension)
+ (__kindof UIColor *)at_redColor;
@end #import "UIColor+extension.h"
#import <objc/message.h> @implementation UIColor (extension) // 加载这个分类的时候就会调用load方法
+ (void)load
{
// class:获取这个类
// SEL:获取方法编号,根据SEL就能去对应的类找方法
Method redColorMethod = class_getClassMethod([UIColor class], @selector(redColor)); // 获取类方法
Method at_redColorMethod = class_getClassMethod([UIColor class], @selector(at_redColor)); // 交换方法实现
method_exchangeImplementations(redColorMethod, at_redColorMethod); } //自己的方法
+ (__kindof UIColor *)at_redColor {
UIColor *color = [UIColor at_redColor];
//添加一个打印功能
NSLog(@"");
return color;
}
@end

控制器:

 self.view.backgroundColor = [UIColor redColor];

会打印出"123"。

红线表示方法交换;

2、动态给某个类添加方法(如果一个类方法非常多,加载类到内存的时候也比较耗费资源(感觉也耗不了什么资源,非要说的话跟懒加载的思想差不多吧,工作中基本没这么玩过),需要给每个方法生成映射表)

 #import "Dog.h"
#import <objc/message.h> @implementation Dog
// 定义函数
// 默认一个方法都有两个隐式参数, self:方法调用者, _cmd:调用方法的编号
void runImp(id self, SEL _cmd, NSString *param)
{
NSLog(@"调用run %@ %@ %@",self,NSStringFromSelector(_cmd),param);
} // 动态添加方法,首先实现这个resolveInstanceMethod
// resolveInstanceMethod调用:当调用了没有实现的方法没有实现就会调用resolveInstanceMethod
// resolveInstanceMethod作用:知道哪些方法没有实现,从而动态添加方法
// sel:没有实现方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// NSLog(@"%@",NSStringFromSelector(sel));
// 动态添加run方法 if (sel == @selector(run:)) {
/*
cls:给哪个类添加方法
SEL:添加方法的方法编号是什么
IMP:方法实现,函数入口,函数名
types:方法类型
*/
// @:对象 :SEL
class_addMethod(self, sel, (IMP)runImp1, "v@:@"); // 处理完
return YES; }
return [super resolveInstanceMethod:sel];
}
@end

使用: (会调用runImp方法)

 Dog *dog = [[Dog alloc] init];
[dog performSelector:@selector(run:) withObject:@"跑啊"];

说明:那两个隐式参数可写可不写,types:方法类型只是对runImp方法的类型说明,具体说明可以搜官方文档,v代表返回值void,@代表对象,:代表SEL。但是我故意把方法类型改错了也就是和方法定义的真实类型不匹配的时候,运行也没什么问题。

3、在分类中添加属性

都知道你在分类中定义属性的时候,只会生成get和set方法的声明,不会生成实际的成员变量和方法实现。再顺便说一句,分类没有父类

第一个想到的办法可能是自己定义一个全局变量,像这样

代码:

 @interface NSObject (ATTest)
@property (nonatomic, copy) NSString *sex;
@end
 @implementation NSObject (ATTest)
NSString *_sex;
- (void)setSex:(NSString *)sex {
_sex = sex;
}
- (NSString *)sex {
return _sex;
}
@end

尝试用一下:

  NSObject *objText = [[NSObject alloc] init];
objText.sex = @"女博士";
NSLog(@"%@",objText.sex); NSObject *objText1 = [[NSObject alloc] init];
NSLog(@"%@",objText1.sex);

两个对象打印出来的都是“女博士”;

因此,给一个类声明属性,本质就是给这个类 和 属性值 设置关联,使类中的属性指向属性值的内存空间,而上面的做法是直接把这个值的内存空间添加到了这个类的内存空间。

下面是runtime做法:

 @interface NSObject (ATDog)
@property (nonatomic, copy) NSString *name;
@end
 #import "NSObject+ATDog.h"
#import <objc/message.h> @implementation NSObject (ATDog)
- (void)setName:(NSString *)name {
//设置关联属性
/* 参数说明
object:添加属性的对象
key:属性名
value:属性关联的值
policy:属性策略,就是strong,copy那些东西
*/
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
//获取关联属性
/* 参数说明
object:获取属性的对象
key:属性名
*/
return objc_getAssociatedObject(self, @"name");
}
@end

使用:

     NSObject *obj = [[NSObject alloc] init];
obj.name = @"";
NSLog(@"%@",obj.name); NSObject *obj1 = [[NSObject alloc] init];
obj1.name = @"";
NSLog(@"%@",obj1.name);

前面打印“1”,后面打印“2”;一个obj对象对应一个属性

4、自动生成模型属性代码的工具类

不用每次手打属性了,挺方便的

没有访问网络,直接搞了个plist文件,反正都一样的,plist文件是微博首页的微博列表,具体见最下面demo

先复习一下读取plist:

  //读取plist(最外层一个字典,字典里是个大数组)
NSString *path = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *arr = dict[@"statuses"];
 //调用分类方法打印模型属性代码
[NSObject createPropertyCodeWithDict:arr[][@"user"]];

打印user模型属性是这个样子的:

现在看一下工具类是怎么实现的吧:先声明一个类方法供外部调用,之后是实现,如果还有没考虑到的类型自行添加就OK,具体实现就不说了,代码已经很清楚了。也可以自己看一下下面的demo

 @interface NSObject (ATProperty)

 + (void)createPropertyCodeWithDict:(NSDictionary *)dict;
@end
 @implementation NSObject (ATProperty)
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict {
NSMutableString *propertyCode = [NSMutableString string]; //遍历字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// NSLog(@"%@ %@", key, [obj class]); NSString *code = nil;
//判断不同类型的属性定义代码
if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, copy) NSString *%@;", key];
} else if ([obj isKindOfClass:NSClassFromString(@"__NSCFBoolean")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;", key];
} else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSUInteger %@;", key];
} else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, copy) NSDictionary *%@;", key];
} else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, copy) NSArray *%@;", key];
}
//拼接字符串
[propertyCode appendFormat:@"\n%@\n", code];
}];
NSLog(@"%@",propertyCode);
}
@end

5、字典转模型

1、直接用KVC

1、导入plist文件,导入上面打印模型属性代码的工具类,新建status模型类,将打印出来的属性代码copy进去

打印属性代码的时候的一点问题

 //先打印一下属性代码(有时候每个字典里不一定每个属性都有,比如转发微博的属性,有的微博有转发,有的则没有,所以
// 用这个打印出的模型属性放到模型里不一定是全部属性都有的,还好如果崩溃的话会提醒你哪个没有写上去)
[NSObject createPropertyCodeWithDict:arr[]];

2、解析plist

  //解析plist
NSString *path = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *arr = dict[@"statuses"];

3、转模型

  //转模型
NSMutableArray *statuses = [NSMutableArray array];
for (NSDictionary *statusDict in arr) {
ATStatus *status = [ATStatus statusWithDict:statusDict];
[statuses addObject:status];
}
NSLog(@"%@",statuses);

在模型类中实现转模型方法statusWithDict的代码

 //转模型实现
+ (ATStatus *)statusWithDict:(NSDictionary *)dict {
ATStatus *status = [[self alloc] init];
//KVC
[status setValuesForKeysWithDictionary:dict];
return status;
}

4、还有些问题((1)服务器返回的字段不一定都用的到,但是现有方法如果不把服务器返回的所有字段属性都写到模型的话就会因为找不到key崩溃;(2)服务器经常返回属性名id的字段,但是id在OC中是关键字,直接写id可能会导致一些问题,所以最好在模型中给丫换个名字,比如ID)这两个问题都能用下面的方法解决:

 //解决KVC对应属性崩溃找不到崩溃
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
//key:没有找到的key
//value:没有找到的key对应的value //找不到id,把id的值赋给ID
if ([key isEqualToString:@"id"]) {
_ID = [value integerValue];
}
//打印出没找到的key
NSLog(@"unFind: key:%@ value:%@", key, value);
}

实现效果:(最下有代码demo)

打个断点,看到已经成功转模型。

2、runtime转模型

二者的区别:

KVC:遍历字典中所有key,去模型中查找有没有对应的属性名,没找到就会崩

runtime:遍历模型中所有属性名,去字典中查找,如果找不到也不会崩

1、导入plist文件,导入上面打印模型属性代码的工具类,新建status模型类,将打印出来的属性代码copy进去

2、新建一个分类做转模型的工具类,具体用法注释已经很详细了,包括服务器返回id的问题(我把id一律在模型中定义为ID)

#import <objc/message.h>

@implementation NSObject (ATObjectModel)
+ (__kindof NSObject *)objectModelWithDict:(NSDictionary *)dict {
//创建对应模型类
id obj = [[self alloc] init]; //成员属性数量
unsigned int count = ;
//获取模型类属性列表数组
Ivar *ivarList = class_copyIvarList(self, &count);
//遍历所有成员属性
for (int i = ; i < count; i++) {
//获取成员属性(Ivar)
Ivar ivar = ivarList[i];
//获取成员属性名
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去掉proprtyName前面的下划线
propertyName = [propertyName substringFromIndex:]; //获取value
id value = nil;
if ([propertyName isEqualToString:@"ID"]) {
value = dict[@"id"];
} else {
value = dict[propertyName];
} if (value) {
//KVC赋值,不能传空
[obj setValue:value forKey:propertyName];
} }
//C语言函数,ARC不会自动释放,需要手动释放
free(ivarList);
return obj;
}
@end

使用就很简单了:

  NSString *path = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *array = dict[@"statuses"];
// [NSObject createPropertyCodeWithDict:array[0]]; NSMutableArray *statuses = [NSMutableArray array];
for (NSDictionary *dict in array) {
//runtime转模型
ATStatus *status = [ATStatus objectModelWithDict:dict];
[statuses addObject:status];
}
NSLog(@"%@",statuses);

打个断点查看statuses

OK。

当然上面只是最外层的转换,那么value如果是字典或者数组呢?

这就需要在上面的代码里再加点东西:

首先是字典的情况:

         //二级转换
//获取成员属性类型
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//如果value是字典(实质是字典类型,但是不是NSDictionary,因为如果类型还是NSDictionary没有必要转换)
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
// NSLog(@"%@", propertyType);
//获取属性类型(剪切字符串@"@\"ATUser\"")
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSRange range = [type rangeOfString:@"\""];
type = [type substringFromIndex:range.location + range.length];
range = [type rangeOfString:@"\""];
type = [type substringToIndex:range.location]; Class modelClass = NSClassFromString(type);
if (modelClass) { //有对应的类型才需要转
value = [modelClass objectModelWithDict:value];
}
}

数组的情况:

 //三级转换(如果value是数组,数组中再包含字典)
if ([value isKindOfClass:[NSArray class]]) {
//如果模型类实现了字典数组转模型数组的协议
if ([self respondsToSelector:@selector(ModelClassInArray)]) {
//转换成id类型,就能调用任何对象的方法
id idSelf = self;
//获取数组中的模型
NSString *type = [idSelf ModelClassInArray][propertyName];
Class modelClass = NSClassFromString(type); NSMutableArray *dictArr = [NSMutableArray array];
//遍历数组
for (NSDictionary *dict in value) {
//转模型
id model = [modelClass objectModelWithDict:dict];
[dictArr addObject:model];
}
//把模型数组赋值给value
value = dictArr;
}
}

数组的情况需要转模型的工具类提供一个协议供ATStatus遵守并实现,返回一个字典告诉工具类数组中是一个什么样的字典,这样工具类才知道给他转成一个什么类的模型。

最后仍然是KVC赋值。

可以看见数组和字典中都转换成model对象了,而且我特意在每层添加了id,经过上面对id的处理也没有问题了(id的处理做的比较简单,只要在定义模型类的时候把id的情况改成ID就行了,其他情况就没有处理,比如ie也是个关键字呢?没做到人家框架中可以让使用者自定义模型属性叫什么的程度)。

注意:当你把NSDictionary改成 ATUser 的时候注意策略如果不匹配也要改一下 ,我一开始就忘改了,结果用的是copy,然后就崩了,要改成strong。

6、利用runtime归档和解档

同样是用NSObject分类来做这个工具类;

先看头文件提供的方法:

 @interface NSObject (ATArchiver)
- (NSArray *)ignorePropertyNames;
- (void)encode:(NSCoder *)encoder;
- (void)decode:(NSCoder *)decoder;
@end

具体实现:归档和解档(比较简单没写注释,说一下大体流程:1:获取调用类的属性列表;2:遍历属性(propertyName = [propertyName substringFromIndex:1];这句代码的意思是去掉成员变量名前面的下划线)3:如果有需要忽略的属性,忽略掉;4:归解档;5:释放ivarList)

 - (void)encode:(NSCoder *)encoder {
unsigned int count = ;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i = ; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
propertyName = [propertyName substringFromIndex:]; if ([self respondsToSelector:@selector(ignorePropertyNames)]) {
if ([[self ignorePropertyNames] containsObject:propertyName]) {
continue;
}
}
id value = [self valueForKey:propertyName];
[encoder encodeObject:value forKey:propertyName];
}
free(ivarList);
}
- (void)decode:(NSCoder *)decoder {
unsigned int count = ;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i = ; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
propertyName = [propertyName substringFromIndex:];
if ([self respondsToSelector:@selector(ignorePropertyNames)]) {
if ([[self ignorePropertyNames] containsObject:propertyName]) {
continue;
}
}
id value = [decoder decodeObjectForKey:propertyName];
[self setValue:value forKey:propertyName];
}
free(ivarList);
}

控制器里面使用:

  NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"archiver.plist"];

     Dog *d = [[Dog alloc] init];
d.name = @"旺财";
d.age = ;
//归档
[NSKeyedArchiver archiveRootObject:d toFile:path];
//解档
Dog *d1 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"name:%@",d1.name);
NSLog(@"age:%ld",d1.age);

效果:

更具体可以看github源码demo

my github:https://github.com/alan12138/runtime

我的runtime学习笔记的更多相关文章

  1. runtime学习笔记

    获取属性objc_property_t * propertys = class_copyPropertyList(clazz, &outCount); 获取属性名NSString * key ...

  2. iOS Runtime学习笔记

    Associated Objects: @interface NSObject (AssociatedObject) @property (nonatomic, strong) id associat ...

  3. Objective-C RunTime 学习笔记 之 消息转发流程

    1) 当向某个对象发送消息时,先从cache(cache_t)中查找方法对象(method_t),如果找到则进行回调:否则通过查找对象的类(元类)定义中方法列表,一直追溯到NSObject, 如果找到 ...

  4. Objective-C RunTime 学习笔记 之 atomic/nonatomic 关键字

    atomic修饰的是变量/方法,对于可变对象的指针变量是安全的,内部实现加了锁,但是对可变对象本身没什么影响,不安全还是不安全.另外atomic仅仅对编译器生产的getter.setter有效,如果自 ...

  5. Objective-C RunTime 学习笔记 之 AutoReleasPool

    1.结构 struct magic_t {     /* 魔法 */     static const uint32_t M0 = 0xA1A1A1A1; #   define M1 "AU ...

  6. Objective-C RunTime 学习笔记 之 基础结构体

    1.OC 运行期常用对象结构体 基本的结构体定义 typedef objc_class Class; /* 类 */ typedef objc_object *id; /* 各种类型,只要第一个字段为 ...

  7. Flink学习笔记:Flink Runtime

    本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKhaz ...

  8. 0035 Java学习笔记-注解

    什么是注解 注解可以看作类的第6大要素(成员变量.构造器.方法.代码块.内部类) 注解有点像修饰符,可以修饰一些程序要素:类.接口.变量.方法.局部变量等等 注解要和对应的配套工具(APT:Annot ...

  9. WeX5学习笔记

    目录 WeX5学习笔记... 1 1.轻松看透WeX5产品能力和技术... 1 2.WeX5可以怎么玩?... 3 一.纯本地App. 3 二.关联一个网站,希望默认就打开某页... 4 三.UI设计 ...

随机推荐

  1. Jquery源码学习(第一天)

    jQuery是面向对象的设计通过window.$ = window.jQuery = $; 向外提供接口,将$挂在window下,外部就可以使用$和jQuery $("#div1" ...

  2. 一个基于Orchard的开源CRM --coevery简介

    Coevery是开源的.NET Web平台项目,力争打造一个开放而鲁棒的CRM系统,采用Orchard架构,并使用AngularJS改善页面体验.作为一个后发优势的CRM 产品,Coevery 具有一 ...

  3. ABP理论学习之数据过滤器

    返回总目录 本篇目录 介绍 预定义过滤器 关闭过滤器 开启过滤器 设置过滤器参数 定义自定义过滤器 其他ORM 介绍 软删除模式通常用于不会真正从数据库删除一个实体而是仅仅将它标记为"已删除 ...

  4. 手工给Meteor增加smart package的方法

    windows下无法装mrt(Meteor的包管理工具).不过还好smart package本身也就只是一个文件夹而已,不需要在Meteor中注册什么东西.所以直接把smart package扔到me ...

  5. Mac下配置Apache服务

    这篇文章主要是针对Mac用户,第一次搭建本地开发环境的同学,已经搭建过的同学可以忽略. Mac自带的Apache还是XAMPP? That is a question. 其实自带的apache也够用了 ...

  6. 飞鱼(FlyFish)——便捷的原型在线制作工具

    关于项目原型制作,小菜先前写过一篇文章<FastUI快速界面原型制作工具>,只不过那个是用C#写的原型制作工具,但是感觉用C#写起来比较费力,而且也不太好用,经过高人指点,茅塞顿开,决定重 ...

  7. 微冷的雨ASP.NET MVC之葵花宝典(MVC)

    微冷的雨ASP.NET MVC之葵花宝典 By:微冷的雨 第一章 ASP.NET MVC的请求和处理机制. 在MVC中: 01.所有的请求都要归结到控制器(Controller)上. 02.约定优于配 ...

  8. YY一下微信线下支付的场景

    在上一篇文章里面提到了 <跨行清算的实现原理>,这次来分析一下线下支付的场景和流程. 今天看到一篇文章:http://www.huxiu.com/article/23248/1.html? ...

  9. windows命令——taskmgr 1

    taskmgr.exe用于任务管理器.它显示系统中正在运行的进程. 该程序使用Ctrl+Alt+Del(一般是弹出Windows安全再点击“任务管理器”)或者Ctrl+Shift+Esc 有时候需要, ...

  10. MacOS中使用QT开发iOS应用

    因为项目合同中规定一部分业务内容要在手机端实现,包括安卓机和苹果机,因此选择了QT作为开发工具.程序在Win10和安卓系统上已经完美运行,这几天开始搭建iOS的编译和发布环境,因为以前没有使用过mac ...