众所周知OC是一门高级编程语言,也是一门动态语言。有动态语言那也就有静态语言,静态语言---编译阶段就要决定调用哪一个函数,若是函数未实现就会编译报错。如C语言。动态语言---编译阶段并不能决定真正调用哪一个函数,只要函数声明过即便没有实现也不会报错。如OC语言。 高级编程语言想要成为可执行文件须要先编译为汇编语言再汇编为机器语言,机器语言也是计算机可以识别的惟一语言,可是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操做,从OC到C语言的过渡就是由runtime来实现的。然而咱们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就须要将面向对象的类转变为面向过程的结构体。 每当我面试的时候被问起Runtime相关知识的时候,老是只能回答个大致内容,具体的应用场景也是说的三三两两,因此我感受是时候总结一波Runtime的应用场景了。git
OC中类能够经过Category来直接扩展方法,可是却不能直接经过添加属性来扩展属性(以我项目中用到的一个为例)。github
#import <UIKit/UIKit.h>
@interface UIView (SPUtils)
@property(nonatomic)CALayer * shadowLayer;
@end
复制代码
#import "UIView+SPUtils.h"
#import <objc/runtime.h>
@implementation UIView (SPUtils)
-(void)setShadowLayer:(CALayer *)shadowLayer{
objc_setAssociatedObject(self, @selector(shadowLayer), shadowLayer, OBJC_ASSOCIATION_RETAIN);
}
-(CALayer *)shadowLayer{
return objc_getAssociatedObject(self, _cmd);
}
@end
复制代码
在iOS新发布的时候在Scrollview的头部会系统默认多出一段空白,解决方法是设置其contentInsetAdjustmentBehavior属性为UIScrollViewContentInsetAdjustmentNever。但对于现存的项目来讲挨个修改工做量无疑是巨大的,也容易出问题。这时候就用到Runtime了,用runtime来交换其初始化方法来统一设置这个属性就能够获得解决。面试
#import <UIKit/UIKit.h>
@interface UIScrollView (Inset)
@end
复制代码
#import "UIScrollView+Inset.h"
#import "CYXRunTimeUtility.h"
@implementation UIScrollView (Inset)
+(void)load{
[CYXRunTimeUtility swizzlingInstanceMethodInClass:[self class] originalSelector:@selector(initWithFrame:) swizzledSelector:@selector(m_initWithFrame:)];
}
- (instancetype)m_initWithFrame:(CGRect)frame {
UIScrollView *scrollV = [self m_initWithFrame:frame];
if (@available(iOS 11.0, *)) {
scrollV.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
return scrollV;
}
@end
复制代码
实现交换方法的代码:编程
#import <Foundation/Foundation.h>
@interface CYXRunTimeUtility : NSObject
/**
交换实例方法
@param cls 当前class
@param originalSelector originalSelector description
@param swizzledSelector swizzledSelector description
@return 返回
*/
+ (BOOL)swizzlingInstanceMethodInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector;
/**
交换类方法
@param cls 当前class
@param originalSelector originalSelector description
@param swizzledSelector swizzledSelector description
@return 成
*/
+ (BOOL)swizzlingClassMethodInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector;
@end
复制代码
#import "CYXRunTimeUtility.h"
#import <objc/runtime.h>
@implementation CYXRunTimeUtility
+ (BOOL)swizzlingInstanceMethodInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
Class class = cls;
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
return didAddMethod;
}
+ (BOOL)swizzlingClassMethodInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
Class class = cls;
Method originalMethod = class_getClassMethod(class, originalSelector);
Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
return didAddMethod;
}
@end
复制代码
开发平常中咱们对网络请求下来的数据进行解析是必然的操做,包括不少三方解析框架都是经过runtime来获取相关属性进行映射解析的。 下面是我本身利用runtime获取对象相关属性并进行简单深拷贝的例子(有不足之处,进攻参考):bash
#import <Foundation/Foundation.h>
@interface NSObject (MutableCopy)
-(id)getMutableCopy;
@end
复制代码
#import "NSObject+MutableCopy.h"
@implementation NSObject (MutableCopy)
-(id)getMutableCopy{
NSArray * keys = [self getObjcPropertyWithClass:[self class]];
id objc = [[[self class] alloc] init];
for (NSString * key in keys) {
if ([self valueForKey:key] == nil) continue;
[objc setValue:[self valueForKey:key] forKey:key];
//[objc setValue:[[self valueForKey:key] getMutableCopy] forKey:key];
}
return objc;
}
- (NSArray<NSString *> *)getObjcPropertyWithClass:(id )objc{
//(1)获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
Class cls = [objc class];
do {
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//经过property_getName函数得到属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//经过property_getAttributes函数能够得到属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//当即释放properties指向的内存
free(properties);
cls = [objc superclass];
objc = [cls new];
} while ([NSStringFromClass([objc superclass]) isEqualToString:@"NSObject"]);
return [keys valueForKeyPath:@"@distinctUnionOfObjects.self"];
}
@end
复制代码
面向对象中每个对象都必须依赖一个类来建立,所以对象的isa指针就指向对象所属的类根据这个类模板可以建立出实例变量、实例方法等。 Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态建立一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法以前和以后,通知全部观察对象属性值的更改状况。 首先建立一个person类定义一个实例变量:网络
@interface Person : NSObject
{
@public
NSString * _name;
}
@property (nonatomic,copy) NSString *name;
@end
复制代码
建立一个NSObject的Category用于给全部NSObject及其子类新增 添加监听方法:框架
@interface NSObject (KVO)
- (void)cyx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end
NSString * const cyx_key = @"observer";
@implementation NSObject (KVO)
-(void)cyx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
objc_setAssociatedObject(self, (__bridge const void *)(cyx_key), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//修改isa 指针
object_setClass(self, [SonPerson class]);
}
复制代码
这里利用runtime修改isa指针,修改调用方法时寻找方法的类。这里咱们修改到SonPerson类。并在SonPerson类里面实现监听方法。编程语言
extern NSString * const cyx_key;
@implementation SonPerson
-(void)setName:(NSString *)name{
[super setName:name];
NSObject * observer = objc_getAssociatedObject(self, cyx_key);
[observer observeValueForKeyPath:@"name" ofObject:self change:nil context:nil];
}
复制代码
这里也用到了runtime 里面 objc_getAssociatedObject 和objc_setAssociatedObject动态存储方法。 好了那咱们来用一下试一下效果吧。函数
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KVO.h"
@interface ViewController ()
@property (nonatomic,strong) Person *p;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person * p = [[Person alloc] init];
[p cyx_addObserver:self forKeyPath:@"name" options:0 context:nil];
_p = p;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",_p.name);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
static int i=0;
i++;
_p.name = [NSString stringWithFormat:@"%d",i];
//_p -> _name = [NSString stringWithFormat:@"%d",i];
}
复制代码
输出:ui
2018-04-21 11:15:17.974785+0800 04-响应式编程思想[1882:274712] 1
2018-04-21 11:15:18.293700+0800 04-响应式编程思想[1882:274712] 2
2018-04-21 11:15:18.687331+0800 04-响应式编程思想[1882:274712] 3
2018-04-21 11:15:19.036166+0800 04-响应式编程思想[1882:274712] 4
2018-04-21 11:15:19.396075+0800 04-响应式编程思想[1882:274712] 5
2018-04-21 11:15:19.699907+0800 04-响应式编程思想[1882:274712] 6
2018-04-21 11:15:19.981256+0800 04-响应式编程思想[1882:274712] 7
复制代码
这个参考个人另外一篇文章:www.jianshu.com/p/1073daee5…
固然runtime的强大不只仅是只能作这些事情,runtime还有不少用处等待咱们你们去挖掘。