Runtime的几点用法总结
来源:互联网 发布:北通 手柄 知乎 编辑:程序博客网 时间:2024/04/30 12:21
Objective-C是一门动态(运行时)语言。它将很多静态语言在编译和链接时期做的事放到了运行时候来做,这就使得我们写代码时候更具灵活性,如可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。这就意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译代码,即使Objc Runtime。
所做的事
在这个库中,对象使用C语言中的结构体表示,方法使用C函数来实现。这些结构体和方法,在被runtime函数封装之后,我们就可以在运行时创建,检查,修改类、对象和他们的方法了。找出方法的最终执行代码 runtime 根据消息接受者是否能响应该消息而做出不同的反应。
给类别Category
添加属性
比如说我们需要在类别中添加一个 NSString 类型的属性,直接在 .h 文件添加 @property(nonatomic,copy) NSString *categoryProperty;
,这时候使用点语法进行调用的话,程序会出现crash错误 :Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[ViewController setCategoryProperty:]: unrecognized selector sent to instance 0x7ff661e43dd0’。这种状况的原因其实很简单,只是没有实现setter和getter方法而已,所以我们的问题就转为实现setter 和 getter方法。
一言不合就要上代码了,主要记录两种类型数据的处理方式。例子为给UIImage添加了两个属性,没什么具体含义,主要记录用法:
//.h#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface UIImage (JYAdd)@property(nonatomic,strong) NSString *name;@property(nonatomic,assign) CGFloat add;@endNS_ASSUME_NONNULL_END//.m#import "UIImage+JYAdd.h"#import <objc/runtime.h>@implementation UIImage (JYAdd)#pragma mark - 添加属性- (void)setName:(NSString *)name{ [self willChangeValueForKey:NSStringFromSelector(@selector(name))]; objc_setAssociatedObject(self, _cmd, name, OBJC_ASSOCIATION_COPY); [self didChangeValueForKey:NSStringFromSelector(@selector(name))];}- (NSString *)name{ return objc_getAssociatedObject(self, @selector(setName:));}- (void)setAdd:(CGFloat)add{ [self willChangeValueForKey:NSStringFromSelector(@selector(add))]; //区别在这里,区别在这里 NSValue *value = [NSValue value:&add withObjCType:@encode(CGFloat)]; objc_setAssociatedObject(self, _cmd, value, OBJC_ASSOCIATION_RETAIN); [self didChangeValueForKey:NSStringFromSelector(@selector(add))];}- (CGFloat)add{ CGFloat cValue = {0}; NSValue *value = objc_getAssociatedObject(self, @selector(setAdd:)); [value getValue:&cValue]; return cValue;}@end
参考了YY的实现,直接使用YY的宏定义其实也挺好的啊,代码不贴了,反正也有。
利用runtime来替换已有的系统方法
例子,初始化UIImage的时候,在不同的系统版本中添加不同的风格的切图,怎么就是和UIImage过不去了。
//.h#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface UIImage (JYAdd)/*! @brief 如果调用这个,其实调用的是原来系统的方法,因为他两交换了实现 @note 为防止误用,可以不声明该方法 */+ (nonnull UIImage *)jy_imageNamed:(NSString *)name;@endNS_ASSUME_NONNULL_END//.m#import "UIImage+JYAdd.h"#import <objc/runtime.h>@implementation UIImage (JYAdd)+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(imageNamed:); SEL swizzledSelector = @selector(jy_imageNamed:); /* //实例方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); */ //类方法 Method originalMethod = class_getClassMethod(class, originalSelector); Method swizzledMethod = class_getClassMethod(class, swizzledSelector); //有就不添加了,没有就添加。有则改之无则加勉吧。 class_addMethod(class, originalSelector, class_getMethodImplementation(class, originalSelector), method_getTypeEncoding(originalMethod)); class_addMethod(class, swizzledSelector, class_getMethodImplementation(class, swizzledSelector), method_getTypeEncoding(swizzledMethod)); //交换他两的实现 method_exchangeImplementations(originalMethod, swizzledMethod); });}#pragma mark - 交换系统方法+ (nonnull UIImage *)jy_imageNamed:(NSString *)name{ /*! 在这里实现我们所需要做的操作 */ double systemVersion = [[[UIDevice currentDevice]systemVersion]floatValue]; if (systemVersion >= 9.0) { name = [name stringByAppendingString:@"_os"]; } UIImage *image = [UIImage jy_imageNamed:name];//这个地方很关键 return image;}@end
方法+ (void)load
不是这里的重点,简单知道一下:一般情况下,类别中的方法会重写掉主类里面相同命名的方法,但+load:
是个特例,当一个类被读到内存的时候,runtime会给这个类以及他的每一个类别都发送一个 +load:
消息(知道这一点很重要)。。
Note:注意交换方法只能执行一次,不要总是执行,load的意义在这儿也有体现的。
还有一点是,尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但是其父类实现了,那么 class_getInstanceMethod 返回的将是父类的方法。这样就导致了 method_exchangeImplementations 替换的是父类的方法。所以先尝试添加 originalSelector ,如果已经存在,再用 method_exchangeImplementations 把原来的方法的实现交换成新方法的实现。
可以借用了大神 @sunnyxx的开源项目 FDFullscreenPopGesture来理解该方面,感谢。
实现自动归档和自动解档
其实归档的实现很简单,只不过就是实现协议<NSCoding>
,需要说明一点的是:
实现了 ‘NSCoding’协议,就可以支持数据类和数据流间的编码和解码,而数据流可以持久化到硬盘。所以,这就有趣了。
我怎么那么喜欢上代码,哈哈
//.h#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface JYEncodeModel : NSObject<NSCoding>@property(nonatomic,strong) NSString *name;@property(nonatomic,assign) int age;@property(nonatomic,assign) NSRange range;@endNS_ASSUME_NONNULL_END//.m#import "JYEncodeModel.h"#import "NSObject+JYEncode.h"NSString *const kEncodeName = @"name";NSString *const kEncodeAge = @"age";NSString *const kEncodeRange = @"range";@implementation JYEncodeModel- (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super init]; if (self) { _name = [aDecoder decodeObjectForKey:kEncodeName]; _age = [aDecoder decodeIntForKey:kEncodeAge]; _range = [[aDecoder decodeObjectForKey:kEncodeRange] rangeValue]; } return self;}- (void)encodeWithCoder:(NSCoder *)aCoder{ [aCoder encodeObject:self.name forKey:kEncodeName]; [aCoder encodeInt:self.age forKey:kEncodeAge]; [aCoder encodeObject:[NSValue valueWithRange:self.range] forKey:kEncodeRange];}@end
可问题在于,像这样只有三个属性需要我们,可以这样写,那如果有三十个属性呐,身为一个会偷懒的猴子自然要找点版本偷懒了,所以,我又要写代码了
//.h#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface JYEncodeModel : NSObject<NSCoding>@property(nonatomic,strong) NSString *name;@property(nonatomic,assign) int age;@property(nonatomic,assign) NSRange range;@endNS_ASSUME_NONNULL_END//.m#import "JYEncodeModel.h"#import <objc/runtime.h>@implementation JYEncodeModel- (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super init]; if (self) { unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } free(ivars); } return self;}- (void)encodeWithCoder:(NSCoder *)aCoder{ unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } free(ivars);}@end
简化一下使用方式
而这样每一个模型类都要写着无聊的代码,而大部分类都是继承自NSObject
,所以,我们可以实现一个NSObject
类别来专门做这件事。
//.h#import <Foundation/Foundation.h>@interface NSObject (JYEncode)- (instancetype)jy_initWithCoder:(NSCoder *)aDecoder;- (void)jy_encodeWithCoder:(NSCoder *)aCoder;@end//.m#import "NSObject+JYEncode.h"#import <objc/runtime.h>@implementation NSObject (JYEncode)- (instancetype)jy_initWithCoder:(NSCoder *)aDecoder{ if (!aDecoder) return self; if (self == (id)kCFNull) return self; unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { //取出i对应位置的成员变量 Ivar ivar = ivars[i]; //查看成员变量 const char *name = ivar_getName(ivar); //归档 NSString *key = [NSString stringWithUTF8String:name]; id value = [aDecoder decodeObjectForKey:key]; //设置到成员变量身上 [self setValue:value forKey:key]; } free(ivars); return self;}- (void)jy_encodeWithCoder:(NSCoder *)aCoder{ if (!aCoder) return; if (self == (id)kCFNull) { [((id<NSCoding>)self)encodeWithCoder:aCoder]; return; } unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } free(ivars);}@end
这样在使用的时候只需要简单的引用一下就可以了,为了验证可行性,每种类型的数据我都添加了一个,事实证明是可以的
//.h#import <Foundation/Foundation.h>#import "JYEncodeSubModel.h"/*! 实现 ‘NSCoding’协议,支持数据类和数据流间的编码和解码,而数据流可以持久化到硬盘。 */NS_ASSUME_NONNULL_BEGIN@interface JYEncodeModel : NSObject<NSCoding>@property(nonatomic,strong) NSString *name;@property(nonatomic,assign) int num;@property(nonatomic,assign) NSRange range;@property(nonatomic,strong) NSDate *date;@property(nonatomic,strong) NSDictionary *dict;@property(nonatomic,strong) NSArray *array;@property(nonatomic,strong) JYEncodeSubModel *subModel;@endNS_ASSUME_NONNULL_END//.m#import "JYEncodeModel.h"#import "NSObject+JYEncode.h"@implementation JYEncodeModel- (instancetype)initWithCoder:(NSCoder *)aDecoder{ return [self jy_initWithCoder:aDecoder];}- (void)encodeWithCoder:(NSCoder *)aCoder{ [self jy_encodeWithCoder:aCoder];}@end//附上JYEncodeSubModel的内容//.h#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface JYEncodeSubModel : NSObject<NSCoding>@property(nonatomic,strong) NSString *subName;@endNS_ASSUME_NONNULL_END//.m#import "JYEncodeSubModel.h"#import "NSObject+JYEncode.h"@implementation JYEncodeSubModel- (instancetype)initWithCoder:(NSCoder *)aDecoder{ return [self jy_initWithCoder:aDecoder];}- (void)encodeWithCoder:(NSCoder *)aCoder{ [self jy_encodeWithCoder:aCoder];}@end
所以,尽情添加吧,我自岿然不动。
实现模型类的转化
其实原理很简单,就是遍历模型中属性的名字,去数据字典中取值,如果取到就进行赋值。
重点内容
objc_msgSend
方法的使用
objc_msgSend(receiver,selector)
或者传入参数
objc_msgSend(receiver,selector,arg1,arg2,...)
当一个message被发送给object,会根据object的isa 指针找到类结构里的方法,如果不能找到,一直顺着父类寻找该方法的实现,直到NSObject类。
为加快速度,runtime system 会缓存使用过的selector和方法地址。
通过object的isa指针找到他的class在class的method_list中找到方法如果class中没有找到方法,继续往superclass中查找一旦找到这个函数,执行对应的方法实现 (IMP)找不到 Dynamic Method Resolution(动态方法决议) 如果是实例方法,调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
,如果是类方法,调用+ (BOOL)resolveClassMethod:(SEL)sel
,这样可以让程序在运行时动态的为一个selector提供实现。如果返回YES,运行时系统会重启一次消息的发送过程,调动动态添加方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(foo)) { class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "V@:"); } return [super resolveInstanceMethod:sel]; } void dynamicMethodIMP(id self,SEL _cmd){ NSLog(@"%s",__PRETTY_FUNCTION__); } + (BOOL)resolveClassMethod:(SEL)sel { return [super resolveClassMethod:sel]; }
Note:Objective-C的方法本质上是一个至少包含了两个参数(id self,SEL _cmd)的C函数。
Message Forwarding(消息转发)分两步:1、首先运行时系统会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法,如果这个方法中返回的不是nil或者self,运行时系统将把消息发送给返回的那个对象2、如果- (id)forwardingTargetForSelector:(SEL)aSelector返回的是nil或者self,运行时系统首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法来获得方法签名,方法签名记录了方法的参数和返回值的信息,如果-methodSignatureForSelector 返回的是nil, 运行时系统会抛出unrecognized selector exception,程序到这里就结束了
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature =[super methodSignatureForSelector:aSelector]; if (!signature) { //获取指定对象的方法签名 signature = [target methodSignatureForSelector:aSelector]; } return signature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { //检测target是否实现来该方法 if ([target respondsToSelector:anInvocation.selector]) { //如果实现了,在这儿将方法分发到对象中去 。可利用这个实现多重代理 [anInvocation invokeWithTarget:target]; } } - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; }
流程图:
原文链接:http://www.jianshu.com/p/e8e523046237
- Runtime的几点用法总结
- const的几点用法总结
- const 的几点用法
- const的几点用法
- const的几点用法
- MessageBox的几点用法
- 求职的几点总结
- 正则的几点总结
- udhcp的几点总结
- appium的几点总结
- appium的几点总结
- 最近总结的几点
- typedef用法的几点介绍
- [C] const 的几点用法
- 关于DropDownList的几点用法
- const几点相当重要的用法
- 关于Xpath的几点特别用法
- 对ReportViewer的几点用法
- Java中instanceof关键字的用法
- 父视图透明,子视图不透明
- 原生style 与 jquery 获取修改元素总结
- dos网络配置命令,从新获取ip刷新dns
- 两年多工作心得和体会
- Runtime的几点用法总结
- [039]文本去重、过滤——文本指纹
- 5. 引用类型学习笔记
- linux定时备份MySQL数据库并删除七天前的备份文件
- sql server 中用 convert 函数转换日期格式
- sdutoj3363 数据结构实验之图论七:驴友计划 (暴力最短路+限制条件)
- 4.Linux系统虚拟机—安装VMware Tools步骤(原创)
- ADB LOGCAT Usage
- android长截屏beta1