Runtime實(shí)際應(yīng)用場(chǎng)景詳解

目錄

1.給分類(lèi)增加屬性
2.方法添加和替換和KVO實(shí)現(xiàn)
3.weak 釋放 nil 的過(guò)程
4.消息轉(zhuǎn)發(fā)(熱更新)解決 Bug(JSPatch)
5.實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔和自動(dòng)解檔
6.實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)
7.[self class] 和 [super class]
8.Runtime補(bǔ)充說(shuō)明

相關(guān)鏈接:

https://juejin.cn/post/6844903586216804359
http://www.itdecent.cn/p/c85e478d984c
https://juejin.cn/post/6844904079957688328
https://juejin.cn/post/6921348387501834254

1.關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類(lèi)增加屬性

我們都是知道分類(lèi)是不能自定義屬性和變量的,下面通過(guò)關(guān)聯(lián)對(duì)象實(shí)現(xiàn)給分類(lèi)添加屬性。關(guān)聯(lián)對(duì)象 Runtime 提供了幾個(gè)接口如下代碼。

其中參數(shù)解釋為:
id object:被關(guān)聯(lián)的對(duì)象
const void *key:關(guān)聯(lián)的key,要求唯一
id value:關(guān)聯(lián)的對(duì)象
objc_AssociationPolicy policy:內(nèi)存管理的策略

//關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關(guān)聯(lián)的對(duì)象
id objc_getAssociatedObject(id object, const void *key)
//移除關(guān)聯(lián)的對(duì)象
void objc_removeAssociatedObjects(id object)
  • 內(nèi)存管理的策略

下面我們看看內(nèi)存策略對(duì)應(yīng)的屬性修飾。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
內(nèi)存策略對(duì)應(yīng)的屬性修飾
  • 舉例-下面實(shí)現(xiàn)一個(gè) UIView 的 Category 添加自定義屬性 defaultColor:
#import "ViewController.h"
#import "objc/runtime.h"

@interface UIView (DefaultColor)

@property (nonatomic, strong) UIColor *defaultColor;

@end

@implementation UIView (DefaultColor)

@dynamic defaultColor;

static char kDefaultColorKey;

- (void)setDefaultColor:(UIColor *)defaultColor {
    objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)defaultColor {
    return objc_getAssociatedObject(self, &kDefaultColorKey);
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    UIView *test = [UIView new];
    test.defaultColor = [UIColor blackColor];
    NSLog(@"%@", test.defaultColor);
}

@end

打印結(jié)果: 2018-04-01 15:41:44.977732+0800 ocram[2053:63739] UIExtendedGrayColorSpace 0 1

打印結(jié)果來(lái)看,我們成功在分類(lèi)上添加了一個(gè)屬性,實(shí)現(xiàn)了它的 setter 和 getter 方法。通過(guò)關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的屬性的內(nèi)存管理也是有 ARC 管理的,所以我們只需要給定適當(dāng)?shù)膬?nèi)存策略就行了,不需要操心對(duì)象的釋放。

2.方法魔法(Method Swizzling)方法添加和替換和KVO實(shí)現(xiàn)

  • 2.1 方法添加

實(shí)際上添加方法在講消息轉(zhuǎn)發(fā)的時(shí)候,動(dòng)態(tài)方法解析的時(shí)候就提到了。

  • cls 被添加方法的類(lèi)
  • name 添加的方法的名稱(chēng)的SEL
  • imp 方法的實(shí)現(xiàn)。該函數(shù)必須至少要有兩個(gè)參數(shù),self,_cmd
  • 類(lèi)型編碼
//class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
  • 2.2 方法替換

例子1:實(shí)現(xiàn)替換 ViewController 的 viewDidLoad 方法

其中:

1.swizzling 應(yīng)該只在 +load 中完成。 在 Objective-C 的運(yùn)行時(shí)中,每個(gè)類(lèi)有兩個(gè)方法都會(huì)自動(dòng)調(diào)用。+load 是在一個(gè)類(lèi)被初始裝載時(shí)調(diào)用,+initialize 是在應(yīng)用第一次調(diào)用該類(lèi)的類(lèi)方法或?qū)嵗椒ㄇ罢{(diào)用的。兩個(gè)方法都是可選的,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用。

2.swizzling 應(yīng)該只在 dispatch_once 中完成,由于 swizzling 改變了全局的狀態(tài),所以我們需要確保每個(gè)預(yù)防措施在運(yùn)行時(shí)都是可用的。原子操作就是這樣一個(gè)用于確保代碼只會(huì)被執(zhí)行一次的預(yù)防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once 滿(mǎn)足了所需要的需求,并且應(yīng)該被當(dāng)做使用 swizzling 的初始化單例方法的標(biāo)準(zhǔn)。

@implementation ViewController

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(jkviewDidLoad);
        
        Method originalMethod = class_getInstanceMethod(class,originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
        
        //judge the method named  swizzledMethod is already existed.
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        // if swizzledMethod is already existed.
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)jkviewDidLoad {
    NSLog(@"替換的方法");
    
    [self jkviewDidLoad];
}

- (void)viewDidLoad {
    NSLog(@"自帶的方法");
    
    [super viewDidLoad];
}

@end
實(shí)現(xiàn)圖片詳解

例子2:友盟統(tǒng)計(jì)埋點(diǎn)時(shí)候交換系統(tǒng)方法

友盟的方法照著文檔寫(xiě)就可以;runtime那個(gè)就是交換系統(tǒng)的方法達(dá)到無(wú)侵入的埋點(diǎn)。主要看要交換的方法,比如最常見(jiàn)的vc的那幾個(gè)周期方法。無(wú)侵入的意思就是不要到處都去寫(xiě)。

例子3:修改重寫(xiě) KVC 的 undefinedKey 方法

KVC 鍵值對(duì)的時(shí)候 key 找不到 vaue 的時(shí)候就會(huì)調(diào)用 undefinedKey 方法,就無(wú)法字典轉(zhuǎn)模型,這個(gè)時(shí)候可以利用 Runtime 修改重寫(xiě)這個(gè) undefinedKey 方法解決奔潰,就可以繼續(xù)使用 KVC 進(jìn)行字典轉(zhuǎn)模型來(lái)使用。

  • 2.3 KVO 實(shí)現(xiàn)

全稱(chēng)是 Key-value observing,翻譯成鍵值觀察。提供了一種當(dāng)其它對(duì)象屬性被修改的時(shí)候能通知當(dāng)前對(duì)象的機(jī)制,在 MVC 大行其道的 Cocoa 中,KVO 機(jī)制很適合實(shí)現(xiàn) model 和 controller 類(lèi)之間的通訊。

KVO 實(shí)現(xiàn)詳解1
KVO 實(shí)現(xiàn)詳解2

3.weak 釋放 nil 的過(guò)程

Runtime 會(huì)對(duì) weak 屬性進(jìn)行內(nèi)存布局,構(gòu)建 hash 表:以 weak 屬性對(duì)象內(nèi)存地址為 key,weak 屬性值(weak自身地址)為 value。當(dāng)對(duì)象引用計(jì)數(shù)為0 dealloc 時(shí),會(huì)將 weak 屬性值自動(dòng)置 nil。(Hash 表你理解成字典就行)

Runtime 如何實(shí)現(xiàn) weak 屬性
  • 補(bǔ)充:weak 屬性需要在 dealloc 中置 nil 么?

4.消息轉(zhuǎn)發(fā)(熱更新)解決 Bug(JSPatch)

JSPatch 是一個(gè) iOS 動(dòng)態(tài)更新框架,只需在項(xiàng)目中引入極小的引擎,就可以使用 JavaScript 調(diào)用任何 Objective-C 原生接口,獲得腳本語(yǔ)言的優(yōu)勢(shì):為項(xiàng)目動(dòng)態(tài)添加模塊或替換項(xiàng)目原生代碼動(dòng)態(tài)修復(fù) bug。

關(guān)于消息轉(zhuǎn)發(fā),前面已經(jīng)講到過(guò)了,消息轉(zhuǎn)發(fā)分為三級(jí),我們可以在每級(jí)實(shí)現(xiàn)替換功能,實(shí)現(xiàn)消息轉(zhuǎn)發(fā),從而不會(huì)造成崩潰。JSPatch 不僅能夠?qū)崿F(xiàn)消息轉(zhuǎn)發(fā),還可以實(shí)現(xiàn)方法添加、替換等一系列功能。

5.實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔和自動(dòng)解檔

原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性,并對(duì)屬性進(jìn)行 encode 和 decode 操作。 核心方法為在 Model 的基類(lèi)中重寫(xiě)方法,代碼如下。

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

6.實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)

原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性,如果屬性在 json 中有對(duì)應(yīng)的值,則將其賦值。 核心方法為在 NSObject 的分類(lèi)中添加方法,代碼如下。

- (instancetype)initWithDict:(NSDictionary *)dict {

    if (self = [self init]) {
        //(1)獲取類(lèi)的屬性及屬性對(duì)應(yīng)的類(lèi)型
        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;
        objc_property_t * properties = class_copyPropertyList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            objc_property_t property = properties[i];
            //通過(guò)property_getName函數(shù)獲得屬性的名字
            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [keys addObject:propertyName];
            //通過(guò)property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            [attributes addObject:propertyAttribute];
        }
        //立即釋放properties指向的內(nèi)存
        free(properties);

        //(2)根據(jù)類(lèi)型給屬性賦值
        for (NSString * key in keys) {
            if ([dict valueForKey:key] == nil) continue;
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
    return self;

}

7.[self class] 和 [super class]

https://blog.csdn.net/qq_45836906/article/details/119176086
https://juejin.cn/post/6844904100228759560
https://juejin.cn/post/6844904100228759560

8.Runtime補(bǔ)充說(shuō)明

其實(shí) KVC 就是 Runtime,因?yàn)?KVC 的賦值取值都是在運(yùn)行時(shí)操作。

ios 中 Runtime 介紹以及使用:
http://www.itdecent.cn/p/595e4c63c732
http://www.itdecent.cn/p/add581f0e8bb#

作為一門(mén)動(dòng)態(tài)編程語(yǔ)言,Objective-C 會(huì)盡可能的將編譯(動(dòng)態(tài)加載)和鏈接(動(dòng)態(tài)綁定)時(shí)要做的事情推遲到運(yùn)行時(shí)。這意味著 Objective-C 語(yǔ)言不僅需要一個(gè)編譯環(huán)境,同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯好的代碼。運(yùn)行時(shí)它是蘋(píng)果提供的純C語(yǔ)言的開(kāi)發(fā)庫(kù)很優(yōu)秀。運(yùn)行時(shí)的作用如圖

例子 - 運(yùn)行時(shí)獲取成員變量名稱(chēng)

#import <Foundation/Foundation.h>
#import "XMGPerson.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 成員變量的數(shù)量
        unsigned int outCount = 0;

        // 獲得所有的成員變量
        // ivars是一個(gè)指向成員變量的指針
        // ivars默認(rèn)指向第0個(gè)成員變量(最前面)
        Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);

        // 遍歷所有的成員變量
        for (int i = 0; i<outCount; i++) {
            // 取出i位置對(duì)應(yīng)的成員變量
//            Ivar ivar = *(ivars + i);
            Ivar ivar = ivars[i];
            // 獲得成員變量的名字
            NSLog(@"%s", ivar_getName(ivar));
        }

        // 如果函數(shù)名中包含了copy\new\retain\create等字眼,那么這個(gè)函數(shù)返回的數(shù)據(jù)就需要手動(dòng)釋放
        free(ivars);

//        Ivar ivar = *ivars;
//        Ivar ivar2 = *(ivars + 1);
//        NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2));


        // 一個(gè)Ivar就代表一個(gè)成員變量

        // int *p; 指向int類(lèi)型的變量
        // Ivar *ivars; 指向Ivar類(lèi)型的變量
    }
    return 0;
}
獲取UITextFiled成員變量的名稱(chēng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容