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

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還有很多用處等待我們大家去挖掘腐泻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末决乎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子派桩,更是在濱河造成了極大的恐慌构诚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铆惑,死亡現(xiàn)場離奇詭異范嘱,居然都是意外死亡送膳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門彤侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肠缨,“玉大人,你說我怎么就攤上這事盏阶∩罐龋” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵名斟,是天一觀的道長脑慧。 經(jīng)常有香客問我,道長砰盐,這世上最難降的妖魔是什么闷袒? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮岩梳,結(jié)果婚禮上囊骤,老公的妹妹穿的比我還像新娘。我一直安慰自己冀值,他們只是感情好也物,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著列疗,像睡著了一般滑蚯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抵栈,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天告材,我揣著相機與錄音,去河邊找鬼古劲。 笑死斥赋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的产艾。 我是一名探鬼主播灿渴,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胰舆!你這毒婦竟也來了骚露?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缚窿,失蹤者是張志新(化名)和其女友劉穎棘幸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倦零,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡误续,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年吨悍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹋嵌。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡育瓜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栽烂,到底是詐尸還是另有隱情躏仇,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布腺办,位于F島的核電站焰手,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怀喉。R本人自食惡果不足惜书妻,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躬拢。 院中可真熱鬧躲履,春花似錦、人聲如沸聊闯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馅袁。三九已至,卻和暖如春荒辕,著一層夾襖步出監(jiān)牢的瞬間汗销,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工抵窒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓灭贷,卻偏偏與公主長得像肴沫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子掉房,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355