Runtime簡單使用一

首先介紹一下本文的目錄結(jié)構(gòu)

目錄結(jié)構(gòu)

一.Runtime認識

通常我們向一個類或者對象發(fā)送一個方法如:[object message]其實都是轉(zhuǎn)為Runtimeobjc_msgSend(object, message)實現(xiàn)。這點可以通過clang查看OC語言底層實現(xiàn)過程如:
我們在一個工程中創(chuàng)建一個對象:

NSObject *object = [NSObject new];

通過clang -rewrite-objc main.m編譯我們的main.m文件可以得到一個main.cppC++文件,打開可以看到主要的實現(xiàn)代碼如下

[NSObject new] Runtime實現(xiàn)

可以看到程序使用了一個objc_msgSend方法,向NSObject類發(fā)送了一個被注冊的new消息。

使用到的方法如下:
objc_getClass(name):獲得name對應(yīng)的類名。
sel_registerName(name):返回類中注冊的方法選擇器。
關(guān)于前綴比如objc、class、sel、...可以理解為:大部分關(guān)于類的操作是以class開頭,對象的操作是以objcobject開頭,方法選擇器的操作是以sel開頭。

二.模擬Runtime消息發(fā)送

現(xiàn)在我們通過使用objc_msgSend方法,來模擬Runtime消息發(fā)送:
我們在項目中創(chuàng)建一個MyClass類,并為其添加一個屬性name如圖:

創(chuàng)建一個MyClass類

main.m文件中我們使用如下代碼來創(chuàng)建MyClass實例、為其屬性賦值并打印屬性值

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myClass = objc_msgSend(objc_getClass("MyClass"), sel_registerName("new"));
        objc_msgSend(myClass, sel_registerName("setName:"), @"jack");
        
        NSLog(@"%@", objc_msgSend(myClass, sel_registerName("name")));
    }
    return 0;
}

最終我們可以看到打印結(jié)果如下:

runtime打印結(jié)果

三.Runtime使用

1.為Category添加屬性(關(guān)聯(lián)對象)

一般情況下,如果在分類文件的.h文件中添加一個@property屬性只會在.m文件中創(chuàng)建這個@property的getter和setter聲明但是無法達到我們添加成員屬性的地步。關(guān)于這點可以查看深入理解Objective-C:Category。但是怎么創(chuàng)建成員屬性了?

首先我們可能會考慮到在分類中使用static全局變量來達到這一效果,但是這種方式是獨立于實例的變量因此可以不用考慮。

第二種就是通過runtime使用關(guān)聯(lián)屬性添加屬性。例如:創(chuàng)建一個NSObject的分類然后在分類中添加name屬性

在分類中添加屬性

然后在.m文件中使用如下代碼創(chuàng)建關(guān)聯(lián)對象

const char myKey;

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, &myKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, &myKey);
}

值得注意的是,在objc_setAssociatedObject方法中,最后一個枚舉參數(shù)objc_AssociationPolicy中,有如下類型可供選擇

OBJC_ASSOCIATION_ASSIGN //相當于(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC // 相當于(nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC   // 相當于(nonatomic, copy)
OBJC_ASSOCIATION_RETAIN // 相當于(strong)
OBJC_ASSOCIATION_COPY // 相當于(copy)

mian.m文件中,我們就可以使用如下代碼,為NSObject類添加屬性了

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [NSObject new];
        objc.name = @"jack";
        
        NSLog(@"%@", objc.name);
    }
    return 0;
}
2.方法交換(Method Swizzling)

在開發(fā)過程中,我們可能會考慮使用自己的方法來替換系統(tǒng)的方法。比如我遇到一個需求,要求是在- (void)viewWillDisappear:方法中停止音頻播放,因為太多的控制器包含播放的view,如果每個控制器都寫中一下停止播放,估計整個項目文件都得改一遍了,任務(wù)繁重而復(fù)雜。

但是如果使用runtime方法交換,修改系統(tǒng)方法實現(xiàn)可以讓程序變得非常簡單。

實現(xiàn)交換代碼如下:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidDisappear:);
        SEL swizzledSelector = @selector(fml_viewDidDisappear:);
        
        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_addMethod和class_replaceMethod函數(shù)添加和替換兩個方法的實現(xiàn)
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            // 如果類中已經(jīng)有了想要替換的方法,那么就調(diào)用method_exchangeImplementations函數(shù)交換了兩個方法的 IMP
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)fml_viewDidDisappear:(BOOL)animated
{
    [self fml_viewDidDisappear:animated];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:FMLResetAudioViewNotification object:nil];
}

首先我們對比看一下+ load+ initialize兩個方法的區(qū)別:

  • + load方法會在程序加載進內(nèi)存就會調(diào)用,而且只會調(diào)用一次。
  • + initialize程序第一次被調(diào)用的時候調(diào)用,并且有可能不會被調(diào)用,還有可能會調(diào)用多次。
    為了讓程序有效的運行一次,我們會將交換方法放入+ load中,并且在dispatch_once代碼快中實現(xiàn)。

然后讓我們來了解幾個方法:

  • class_getInstanceMethod:獲取實例方法
  • class_getClassMethod:獲取類方法
  • class_addMethod:添加方法
  • class_replaceMethod:替換方法的實現(xiàn),如果不存在要替換的方法則相當于調(diào)用class_addMethod方法
  • method_exchangeImplementations:交換兩個方法的實現(xiàn)
  • method_getImplementation:返回一個指向方法實現(xiàn)函數(shù)的指針
    這里我們需要了解兩個概念SEL和IMP。
    (1).SEL又叫選擇器,是表示一個方法的selector的指針,它會在程序編譯的時候根據(jù)方法名和參數(shù)序列生成為唯一一個整型標示(Int類型的地址)以用來在各自的方法列表中查找IMP。
    (2).IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的首地址,可以通過SEL快速查找到它。
  • method_getTypeEncoding:獲取描述方法參數(shù)和返回值類型的字符串比如v@:更多的類型可以查看Type Encodings

可能會有人有疑問,在- fml_viewDidDisappear:方法中,調(diào)用了[self fml_viewDidDisappear:animated]會不會引發(fā)死循環(huán)?其實這樣是不會引發(fā)死循環(huán)的,因為我們在調(diào)用這個方法的時候,其實是在調(diào)用viewDidDisappear,因此不會觸發(fā)死循環(huán)。
因為Method Swizzling方法會導(dǎo)致不可預(yù)計的后果,比如并不知道代碼中使用了這中方法,因此在使用的時候要慎重。

參考鏈接:
http://southpeak.github.io/2014/10/25/objective-c-runtime-1/
http://southpeak.github.io/2014/11/03/objective-c-runtime-3/
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

因為文章太長,下一篇準備總結(jié)消息轉(zhuǎn)發(fā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,776評論 0 9
  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,733評論 7 64
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,637評論 33 466
  • 官方源碼下載地址:http://download.csdn.net/detail/liangliang103377...
    有一種再見叫青春閱讀 2,026評論 2 11
  • 現(xiàn)在管理中,有很多關(guān)于時間管理的書籍,教導(dǎo)人們?nèi)绾瘟杏媱潱谧约旱娜蝿?wù)清單上把時間節(jié)點管理的井井有條,卻忽視了精力...
    小天先森閱讀 153評論 0 0