玩轉iOS開發:裝逼技術RunTime的應用(三)

CainLuo發表於2017-10-15

文章分享至我的個人技術部落格:https://cainluo.github.io/15074742481003.html


在上一章節裡曉得了怎麼在Category裡關聯物件, 以及利用RunTime轉換模型的時候預防了三種轉換時的情況, 如果沒有去看的朋友可以到玩轉iOS開發:裝逼技術RunTime的應用(二)看看.

轉載宣告:如需要轉載該文章, 請聯絡作者, 並且註明出處, 以及不能擅自修改本文.


利用Runtime歸檔

在以前我們在使用歸檔的時候都會有一個煩惱, 就是寫的太多, 不信? 我們來宣告一個物件:

#import <Foundation/Foundation.h>

@interface RunTimeCoderModel : NSObject <NSCoding>

@property (nonatomic, copy) NSString *cl_name;
@property (nonatomic, copy) NSString *cl_height;
@property (nonatomic, copy) NSString *cl_age;

@end
複製程式碼

常規歸檔的寫法:

- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    [aCoder encodeObject:_cl_name
                  forKey:@"name"];
    [aCoder encodeObject:_cl_height
                  forKey:@"height"];
    [aCoder encodeObject:_cl_age
                  forKey:@"age"];
}
複製程式碼

常規解檔的寫法:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    
    if (self) {
        
        self.cl_name   = [aDecoder decodeObjectForKey:@"name"];
        self.cl_height = [aDecoder decodeObjectForKey:@"height"];
        self.cl_age    = [aDecoder decodeObjectForKey:@"age"];
    }
    
    return self;
}
複製程式碼

現在看著好像也不怎麼樣, 但在實際開發中, 我們要寫的屬性可不是隻有這三個, 如果遇到變態的, 有上百個那怎麼辦呢?

逐個逐個去寫麼? 萬一寫完之後突然要改屬性怎麼辦? 逐個去改? 這樣子就會大量的浪費我們的時間, 這是不明智的寫法.

回想一下, 每個類都有一個isa的結構體指標, 裡面可以拿到所有的每個類的資訊, 那我們是否可以通過這個特性, 來給歸檔解檔操作一番呢? 試試看:

RunTime歸檔的寫法:

- (void)cl_runtimeEncoderWithCoder:(NSCoder *)coder {
    
    unsigned int count = 0;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        
        Ivar ivar = ivarList[i];
        
        const char *name = ivar_getName(ivar);
        
        NSString *key = [NSString stringWithUTF8String:name];
        
        id value = [self valueForKey:key];
        
        [coder encodeObject:value
                     forKey:key];
    }
    
    free(ivarList);
}
複製程式碼

RunTime解檔寫法:

- (void)cl_runtimeDecideWithCoder:(NSCoder *)decoder {
    
    unsigned int count = 0;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        
        Ivar ivar = ivarList[i];
        
        const char *name = ivar_getName(ivar);
        
        NSString *key = [NSString stringWithUTF8String:name];
        
        id value = [decoder decodeObjectForKey:key];
        
        [self setValue:value
                forKey:key];
    }
    
    free(ivarList);
}
複製程式碼

最終的使用:

- (void)encodeWithCoder:(NSCoder *)aCoder {
        
    [self cl_runtimeEncoderWithCoder:aCoder];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    
    if (self) {
    
        [self cl_runtimeDecideWithCoder:aDecoder];
    }
    
    return self;
}
複製程式碼

最終的效果:

1

這的確是可行的, 這樣子我們就把這個寫成一個通用的類, 並且遵守<NSCoding>協議, 就可以把所有繼承與NSObject的類全部一次性歸檔.

在這裡我就不對歸檔和解檔的方法進行封裝了, 都寫在RunTimeCoderController這個控制器上, 有想法的朋友可以自行進行封裝, 這樣子就可以抽成一個通用類.


RunTime黑魔法

前段時間搜了一下關於RunTime的一些部落格, 發現有很多人都說RunTime黑魔法, 那什麼是黑魔法?

  • 簡單的來說, 其實就是進行方法交換.
  • 我們都知道, 在Objective-C中呼叫一個方法, 其實是向一個物件傳送訊息, 而查詢訊息的唯一依據就是selector的名字, 利用Objective-C的動態特性, 我們可以實現在執行時偷偷的換掉selector對應的方法實現.
  • 而我們也逗知道, 每一個類都有一個方法列表, 存放著方法的名字和方法實現的對映關係, selector其實就是方法名, 而IMP類似函式指標, 指向具體的Method實現, 通過selector就可以找到對應的IMP.

2

  • 交換方法的實現方式
    • 利用method_exchangeImplementations來交換兩個方法的實現
    • 利用class_replaceMethod替換方法的實現
    • 利用method_setImplementation來直接設定某個方法的IMP

3

除了我們在演示裡寫過的程式碼, 在實際上又是怎麼運用呢? 這裡收集到了幾種場景:

  • 替換ViewController的生命週期方法
  • 解決集合類的獲取索引, 新增, 刪除元素越界崩潰的問題
  • 防止按鈕重複暴力點選
  • 全域性更換控制元件初始化的效果
  • App的熱修復
  • App空資料時的佔點陣圖工具類封裝
  • 全域性修改UINavigationBarBackButtonItem

程式碼這裡就不寫了, 想詳細瞭解的朋友可以到下面的文章去了解.

Runtime Method Swizzling開發例項彙總


推薦文章

runtime 完整總結 objc_msgSend runtime詳解 讓你快速上手Runtime 利用Runtime 實現自動化歸檔 runtime那些事(訊息機制) OC最實用的runtime總結,面試、工作你看我就足夠了! Runtime在實際開發中的應用


工程地址

專案地址: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/Five


最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶

相關文章