完善这篇文章的目的在于巩固一下自身,之前的并不完善,对有些问题的理解也不是很透彻,今儿重温了下,在之前的基础上,再次进行完善,也算自己对技术知识上的负责任吧。昨晚听了汉克老师的公开课,讲的挺好的,其实很多时候不管是学习上的还是生活中的某些事情,都会发现我们无法一直坚持下去,也许外在原因是一因素,但是很多时候还是在于自我的放松,这里不得不提一下汉克老师在直播说过这么一句话——反馈机制。
比如说:打游戏。有时候一下就是一上午,有时候天天都会去玩一把。为什么打游戏就能坚持,对于学习或者热点一直不减的减肥话题。大多数都是不了了知了呢。
分析原因:
上面有说过反馈机制,游戏里面会产生等级的提升、金币的上涨、英雄的购买,对此你可能产生了一个欲望,一种想要更多的欲望,于是有了有瘾的说法,站在产品的角度来看,这就是这一种及时性的反馈机制。而对于减肥以及学习始终不能坚持下去的根源在于什么呢?比方说学习,可能你学的东西,你用不上,减肥也是一个漫长的过程,今天重量也许比以往轻了点,结果每次看到好吃的,一下就绷不住了,往上飙了好几斤。由于反馈不及时,结果导致泄气,放弃。
引言
对于从事 iOS 开发人员来说,太多数人都会说「 runtime 是运行时 」
什么情况下用 runtime ?大部分人能说出「 给分类动态添加属性 || 交换方法」
runtime – 运行时(iOS的黑魔法!!)
runtime是OC的底层实现,可以静心一些非常底层的操作(OC无法办到的)
一.runtime简介
二.消息机制<了解>
2.1消息机制原理
2.2消息调用流程
三.常用开发场景
3.1 UITextField占位文字的颜色以及字体大小
3.2 给分类动态添加属性
四.面试须知
4.1 动态添加方法(开发中几乎不用,但面试须知)
4.2 使用runtime实现自动归档和解档(之前傻逼式的一个一个写,昨天看了hanK的公开课,顿时打开眼界,原来还能这么搞,大写的服)
4.3 Method Swizzling方法交换(俗称黑魔法,也有说法叫方法欺骗)
4.4 runtime 字典转模型(这个本文不会讲到,因为解析都是用第三方框架了,成熟且实用便利。MJExtension字典转模型实现也是对 runtime 的封装,才可以把一个模型中所有属性遍历出来)
五.利用runtime进行实用性封装
5.1 UIAlertView的封装
5.2 Method Swizzling方法交换封装
5.3 自动归档和解档定义成宏
一. runtime简介:
runtime简称运行时,OC就是运行机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候回决定调用哪个函数
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用
二.消息机制
2.1消息机制原理
方法调用本质:让对象发送消息
objc_msgSend只有对象才能发送消息,故以objc开头
使用消息机制,必须导入#import
runtime都有一个前缀,谁的事情使用谁
解决提示步骤
查找build setting -> 搜索msg ->设为NO
查看生成runtime代码,首先终端切换到该目录回车后输入clang -rewrite-objc main.m
2.2 消息调用流程
- 1.通过isa去对应的类中查找 - 2.注册方法编号 - 3.根据方法编号去查找对应方法 - 4.找到只是最终函数实现地址,根据地址去方法区调用对应函数
消息机制原理图
/*************ViewController.m******************/ -(void)setupobjc_msgSend{ /* objc_msgSend :谁发送消息 :发送什么消息 */ // id objc = objc_msgSend([NSObject class], @selector(alloc)); // objc = objc_msgSend(objc, @selector(init)); // OC: Person *p = [Person alloc]; // 底层的实际写法 Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")); // 调用对象方法(本质:让对象发送消息) // OC: p = [p init]; //runtime objc_msgSend(p, sel_registerName("init")); //OC: // [p hungry]; // [p run:20]; //runtime objc_msgSend(p, @selector(hungry)); objc_msgSend(p, @selector(run:),20); } Person.m文件 -(void)run:(NSUInteger)m{ NSLog(@"他跑了%zdM",m); } -(void)hungry{ NSLog(@"他饿了"); }
打印如下:
打印结果
三.常用开发场景
3.1 UITextField占位文字的颜色以及字体大小
[_textField setValue:kFont(14) forKeyPath:@"_placeholderLabel.font"]; [_textField setValue:kThemeColor forKeyPath:@"_placeholderLabel.textColor"];
3.2 给分类动态添加属性
注意:我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。
原理:给一个类声明属性,其本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
需求:给系统 NSObject 类动态添加属性 name 字符串。
#import "NSObject+Person.h" #import static char const * const kName = "kName"; @implementation NSObject (Person) - (void)setName:(NSString *)name{ /* objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中) : 给哪个对象添加属性 :属性名称 :属性值 :保存策略 **/ objc_setAssociatedObject(self, kName, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name{ //objc_getAssociatedObject:获取某个对象的值 return objc_getAssociatedObject(self, kName); } /*************调用******************/ NSObject *obj =[NSObject new]; obj.name = @"flowerflower"; NSLog(@"我是分类obj中的%@",obj.name); /*************打印输出******************/ 我是分类obj中的flowerflower
小结:
属性赋值的本质:就是让属性与一个对象产生关联,所以要给NSObject的分类的name属性赋值就是让name和NSObject产生关联,而runtime可以做到这一点。
四.面试须知
4.1 动态添加方法(开发中几乎不用,但面试须知)
需求:runtime 动态添加方法处理调用一个未实现的方法 和 去除报错。
案例代码
/*************UserModel.h******************/ - (void)eat; - (void)run:(NSNumber *)m; /*************UserModel.m******************/ @implementation UserModel // 没有返回值,1个参数 // void,(id,SEL) void aaa(id self, SEL _cmd, NSNumber *meter) { NSLog(@"跑了%@米", meter); } + (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == NSSelectorFromString(@"run:")) { // 动态添加run方法 // class: 给哪个类添加方法 // SEL: 添加哪个方法,即添加方法的方法编号 // IMP: 方法实现 => 函数 => 函数入口 => 函数名(添加方法的函数实现(函数地址)) // type: 方法类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, sel, (IMP)aaa, "v@:@"); return YES; } return [super resolveInstanceMethod:sel]; } - (void)eat{ NSLog(@"我吃了"); } @end /*************ViewController.m******************/ - (void)viewDidLoad { [super viewDidLoad]; UserModel *user = [UserModel new]; [user performSelector:@selector(run:) withObject:@121]; [user eat]; objc_msgSend(user,@selector(eat)); objc_msgSend(user,@selector(run:),@1); }
图片.png
4.2 使用runtime实现自动归档和解档(之前傻逼式的一个一个写,昨天看了hanK的公开课,顿时打开眼界,原来还能这么搞,大写的服)
如果你实现过自定义模型数据持久化的过程,那么你也肯定明白,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍encodeObject 和 decodeObjectForKey方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。下面来看看简单的实现方式。
图片.png
假设这里有100个属性,那是不是我们也只能把100个属性都给写一遍。看着HanK老师的公开课之后,我会觉得自己挺傻乎乎的,不管模型中再多属性,轻轻松松50行搞定。
首先要看个小示例
/*************UserModel.h******************/ @interface UserModel : NSObject@property(nonatomic,copy)NSString *name; @property(nonatomic,assign)NSInteger age; /*************UserModel.m******************/ #import "UserModel.h" #import @interface UserModel() @property (nonatomic,copy) NSString *userId; @property (nonatomic,copy) NSString *phone; //注册手机号 @end @implementation UserModel @end /*************调用******************/ unsigned int count = 0; //成员变量的个数 Ivar *ivarList = class_copyIvarList(UserModel.class, &count); Ivar ivar1 = ivarList[0]; NSLog(@"%s",ivar_getName(ivar1)); //_name Ivar ivar2 = ivarList[1]; NSLog(@"%s",ivar_getName(ivar2));//_age Ivar ivar3 = ivarList[2]; // 打印UserModel里面的成员属性的个数 NSLog(@"%d",count);
图片.png
也就是说即使在.m声明的私有属性,也可以使用runtime获取到成员变量的属性
回归正题(使用runtime实现归档和接档,不管属性是10个,20个,甚至上100个)
/*************UserModel.h******************/ @interface UserModel : NSObject//定义一堆属性 假设这里有22个属性 这里就不全部展示了。就简单写了几个 @property (nonatomic,copy) NSString *userId; //用户Id @property (nonatomic,copy) NSString *phone; //注册手机号 @property (nonatomic,copy) NSString *nickName; //昵称 @property (nonatomic,copy) NSString *loginPSW; //登陆的md5密码 @end /*************UserModel.m******************/ #import "UserModel.h" #import @interface UserModel() @end @implementation UserModel - (void)encodeWithCoder:(NSCoder *)aCoder{ /* 获取类中的ivar列表 count: count为ivar总数 class_copyIvarList: 获取类的全部属性 **/ unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self.class, &count); for (int i = 0; i< count; i++) { //取出Ivar Ivar ivar = ivarList[i]; //属性名称 ivar_getName:获取类实例成员变量,只能取到本类的,父类的访问不到 NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; //归档 通过KVC取的 就没有int类型了 [aCoder encodeObject:[self valueForKey:key] forKey:key]; } //但凡在C语言里面 看到New Creat Copy 都需要释放 free(ivarList); //释放 } - (instancetype)initWithCoder:(NSCoder *)coder{ if ( self = [super init]) { unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self.class, &count); for (int i = 0; i< count; i++) { //取出Ivar Ivar ivar = ivarList[i]; //属性名称 NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; //解档 设置到成员变量上 [self setValue: [coder decodeObjectForKey:key] forKey:key]; } free(ivarList); //释放 } return self; } /*************ViewController******************/ //宏 #define UserDataFilePath ([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"user.data"]) - (void)viewDidLoad { [super viewDidLoad]; unsigned int count = 0; //成员变量的个数 Ivar *ivarList = class_copyIvarList(UserModel.class, &count); Ivar ivar1 = ivarList[0]; NSLog(@"%s",ivar_getName(ivar1)); //_userId Ivar ivar2 = ivarList[1]; NSLog(@"%s",ivar_getName(ivar2));//_phone Ivar ivar3 = ivarList[2]; // 打印UserModel里面的成员属性的个数 NSLog(@"%d",count); //打印结果:22 } - (IBAction)save:(id)sender{ UserModel *model = [UserModel new]; model.nickName = @"flowerflower"; model.phone = @"1234567890"; //归档 [NSKeyedArchiver archiveRootObject:model toFile:UserDataFilePath]; } - (IBAction)read:(id)sender{ UserModel *model = [NSKeyedUnarchiver unarchiveObjectWithFile:UserDataFilePath]; NSLog(@"%@----%@",model.nickName,model.phone); }
图片.png
4.3 方法交换(俗称黑魔法,也有说法叫方法欺骗)
系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能
简单粗暴方式: 继承系统的类,重写方法.
高端大气上档次的runtime:使用runtime,交换方法.
简单粗暴方式:
//Image继承于UIImage //重写系统方法 +(UIImage *)imageNamed:(NSString *)name{ UIImage *image =[super imageNamed:name]; if (image) { NSLog(@"加载成功"); }else{ NSLog(@"加载失败"); } return image; } //ViewController中导入头文件直接调用 //有图片则加载成功,无图片则加载失败。 UIImage *img = [Image imageNamed:@"brand_foreclock"];
高端大气上档次的runtime交换方法
UIImage+yhp_Image.m文件 #import "UIImage+yhp_Image.h" #import @implementation UIImage (yhp_Image) // 把类加载进内存的时候调用,只会调用一次 +(void)load{ //交换方法 Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:)); Method yhp_imageNameMethod = class_getClassMethod(self, @selector(yhp_imageNamed:)); //交换方法地址,相当于交换实现方法 method_exchangeImplementations(imageNameMethod, yhp_imageNameMethod); } //不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super. //注意:不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉 +(UIImage *)yhp_imageNamed:(NSString *)name{ // 图片 UIImage *image = [UIImage yhp_imageNamed:name]; if (image) { NSLog(@"加载成功"); } else { NSLog(@"加载失败"); } return image; } //ViewController不需要头文件 UIImage *image = [UIImage imageNamed:@"1.peng"];
五.利用runtime进行实用性封装
5.1 UIAlertView的封装
正常写法
/** 使用步骤: 1.初始化 2.设置代理 3.实现代理方法 **/ @interface ViewController : UIViewController @end - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"AlertViewTest" message:@"message" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定",nil]; [alert show]; } #pragma marks -- UIAlertViewDelegate -- //根据被点击按钮的索引处理点击事件 -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { NSLog(@"clickButtonAtIndex:%d",buttonIndex); }
使用runtime对UIAlertView进行封装
/*************UIAlertView+HHAddition.h******************/ #import typedef void(^AlertCallBack)(NSInteger buttonIndex); @interface UIAlertView (HHAddition) -(void)showAlertWithHandler:(AlertCallBack)callback; @end /*************UIAlertView+HHAddition.m******************/ #import "UIAlertView+HHAddition.h" #import static char kUIAlertViewBlockAddress; @implementation UIAlertView (HHAddition) -(void)showAlertWithHandler:(void(^)(NSInteger))callback{ self.delegate = self; //为某个对象设置关联对象的值 //第一个参数是主对象,第二个参数是键,第三个参数是关联的对象,第四个参数是存储策略:是枚举,定义了内存管理语义 objc_setAssociatedObject(self, &kUIAlertViewBlockAddress, callback, OBJC_ASSOCIATION_COPY); [self show]; } - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{ //根据给定的键从某对象中获取相应的关联对象值 AlertCallBack alertCallBack = objc_getAssociatedObject(self, &kUIAlertViewBlockAddress); if (alertCallBack) { alertCallBack(buttonIndex); objc_setAssociatedObject(self, &kUIAlertViewBlockAddress, nil, OBJC_ASSOCIATION_COPY); } } @end /*************调用******************/ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"是否联系客服" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; [alertView showAlertWithHandler:^(NSInteger buttonIndex) { NSLog(@"clickButtonAtIndex:%d",buttonIndex); }]; 5.2 Method Swizzling方法交换封装 /*************NSObject+Swizzling.h******************/ #import @interface NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector; @end /*************NSObject+Swizzling.m******************/ #import "NSObject+Swizzling.h" @implementation NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); //替换原有方法的新方法 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); /** 先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况. class_addMethod会添加一个覆盖父类的实现,但不会取代原有类的实现。 也就是说如果class_addMethod返回YES,说明子类中没有方originalSelector, 通过class_addMethod为其添加了方法originalSelector,并使其实现(IMP)为我们想要替换的实现。 */ BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可 method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
5.3 自动归档和解档定义成宏
两句代码搞定
#import "Movie.h" #import #define encodeRuntime(className) / / unsigned int count = 0;/ Ivar *ivars = class_copyIvarList([A class], &count);/ for (int i = 0; i作者:flowerflower
链接:https://www.jianshu.com/p/6ae5e28b3365