Runtime是什么寝衫?
??眾所周知OC是一門高級編程語言,也是一門動態(tài)語言。有動態(tài)語言那也就有靜態(tài)語言拐邪,靜態(tài)語言---編譯階段就要決定調(diào)用哪個函數(shù)慰毅,如果函數(shù)未實現(xiàn)就會編譯報錯。如C語言庙睡。動態(tài)語言---編譯階段并不能決定真正調(diào)用哪個函數(shù)事富,只要函數(shù)聲明過即使沒有實現(xiàn)也不會報錯。如OC語言乘陪。
??高級編程語言想要成為可執(zhí)行文件需要先編譯為匯編語言再匯編為機器語言统台,機器語言也是計算機能夠識別的唯一語言,但是OC并不能直接編譯為匯編語言啡邑,而是要先轉(zhuǎn)寫為純C語言再進行編譯和匯編的操作贱勃,從OC到C語言的過渡就是由runtime來實現(xiàn)的。然而我們使用OC進行面向?qū)ο箝_發(fā)谤逼,而C語言更多的是面向過程開發(fā)贵扰,這就需要將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^程的結(jié)構(gòu)體。
??每當我面試的時候被問起Runtime相關(guān)知識的時候流部,總是只能回答個大體內(nèi)容戚绕,具體的應用場景也是說的三三兩兩,所以我感覺是時候總結(jié)一波Runtime的應用場景了枝冀。
Runtime應用場景
場景1--動態(tài)擴展屬性
??OC中類可以通過Category來直接擴展方法舞丛,但是卻不能直接通過添加屬性來擴展屬性(以我項目中用到的一個為例)耘子。
#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
場景2--交換方法用于統(tǒng)一處理某個方法
??在iOS新發(fā)布的時候在Scrollview的頭部會系統(tǒng)默認多出一段空白,解決方法是設置其contentInsetAdjustmentBehavior屬性為UIScrollViewContentInsetAdjustmentNever球切。但對于現(xiàn)存的項目來說挨個修改工作量無疑是巨大的谷誓,也容易出問題。這時候就用到Runtime了吨凑,用runtime來交換其初始化方法來統(tǒng)一設置這個屬性就可以得到解決捍歪。
#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
??實現(xiàn)交換方法的代碼:
#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
場景3--遍歷類屬性--映射解析
??開發(fā)日常中我們對網(wǎng)絡請求下來的數(shù)據(jù)進行解析是必然的操作,包括很多三方解析框架都是通過runtime來獲取相關(guān)屬性進行映射解析的鸵钝。
??下面是我自己利用runtime獲取對象相關(guān)屬性并進行簡單深拷貝的例子(有不足之處糙臼,進攻參考):
#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函數(shù)獲得屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內(nèi)存
free(properties);
cls = [objc superclass];
objc = [cls new];
} while ([NSStringFromClass([objc superclass]) isEqualToString:@"NSObject"]);
return [keys valueForKeyPath:@"@distinctUnionOfObjects.self"];
}
@end
場景4--修改isa指針,自己實現(xiàn)kvo
??面向?qū)ο笾忻恳粋€對象都必須依賴一個類來創(chuàng)建恩商,因此對象的isa指針就指向?qū)ο笏鶎俚念惛鶕?jù)這個類模板能夠創(chuàng)建出實例變量弓摘、實例方法等『劢欤看一下runtime中關(guān)于objc_class
結(jié)構(gòu)體的定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息末患,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
??Apple 使用了 isa 混寫(isa-swizzling)來實現(xiàn) KVO 研叫。當觀察對象A時,KVO機制動態(tài)創(chuàng)建一個新的名為:NSKVONotifying_A 的新類璧针,該類繼承自對象A的本類嚷炉,且 KVO 為 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會負責在調(diào)用原 setter 方法之前和之后探橱,通知所有觀察對象屬性值的更改情況申屹。
??首先創(chuàng)建一個person類定義一個實例變量:
@interface Person : NSObject
{
@public
NSString * _name;
}
@property (nonatomic,copy) NSString *name;
@end
創(chuàng)建一個NSObject的Category用于給所有NSObject及其子類新增 添加監(jiān)聽方法:
@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指針,修改調(diào)用方法時尋找方法的類隧膏。這里我們修改到SonPerson類哗讥。并在SonPerson類里面實現(xiàn)監(jiān)聽方法。
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動態(tài)存儲方法胞枕。
??好了那我們來用一下試一下效果吧杆煞。
#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];
}
輸出:
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
??demo:https://github.com/SionChen/ReactiveProgramming
場景5--利用runtime實現(xiàn)消息轉(zhuǎn)發(fā)機制的三次補救
??這個參考我的另一篇文章:http://www.reibang.com/p/1073daee5b92
總結(jié)
??當然runtime的強大不僅僅是只能做這些事情,runtime還有很多用處等待我們大家去挖掘腐泻。