iOS runtime運行時的做用和應用場景

Runtime是什麼?

  衆所周知OC是一門高級編程語言,也是一門動態語言。有動態語言那也就有靜態語言,靜態語言---編譯階段就要決定調用哪一個函數,若是函數未實現就會編譯報錯。如C語言。動態語言---編譯階段並不能決定真正調用哪一個函數,只要函數聲明過即便沒有實現也不會報錯。如OC語言。   高級編程語言想要成爲可執行文件須要先編譯爲彙編語言再彙編爲機器語言,機器語言也是計算機可以識別的惟一語言,可是OC並不能直接編譯爲彙編語言,而是要先轉寫爲純C語言再進行編譯和彙編的操做,從OC到C語言的過渡就是由runtime來實現的。然而咱們使用OC進行面向對象開發,而C語言更多的是面向過程開發,這就須要將面向對象的類轉變爲面向過程的結構體。   每當我面試的時候被問起Runtime相關知識的時候,老是隻能回答個大致內容,具體的應用場景也是說的三三兩兩,因此我感受是時候總結一波Runtime的應用場景了。git

Runtime應用場景

場景1--動態擴展屬性

  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
複製代碼

場景2--交換方法用於統一處理某個方法

  在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
複製代碼

場景3--遍歷類屬性--映射解析

  開發平常中咱們對網絡請求下來的數據進行解析是必然的操做,包括不少三方解析框架都是經過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
複製代碼

場景4--修改isa指針,本身實現kvo

  面向對象中每個對象都必須依賴一個類來建立,所以對象的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

複製代碼

  demo:github.com/SionChen/Re…

場景5--利用runtime實現消息轉發機制的三次補救

  這個參考個人另外一篇文章:www.jianshu.com/p/1073daee5…

總結

  固然runtime的強大不只僅是隻能作這些事情,runtime還有不少用處等待咱們你們去挖掘。

相關文章
相關標籤/搜索