iOS Runtime基礎(chǔ)學(xué)習(xí)

Runtime簡介

因?yàn)镺bjc是一門動態(tài)語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時。也就是說只有編譯器是不夠的,還需要一個運(yùn)行時系統(tǒng) (runtime system) 來執(zhí)行編譯后的代碼。這就是 Objective-C Runtime 系統(tǒng)存在的意義,它是整個Objc運(yùn)行框架的一塊基石。

Runtime其實(shí)有兩個版本:“modern”和 “l(fā)egacy”。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行(Modern)版的Runtime系統(tǒng),只能運(yùn)行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X較老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統(tǒng)。這兩個版本最大的區(qū)別在于當(dāng)你更改一個類的實(shí)例變量的布局時,在早期版本中你需要重新編譯它的子類,而現(xiàn)行版就不需要。

Runtime基本是用C和匯編寫的,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護(hù)的開源代碼。蘋果和GNU各自維護(hù)一個開源的runtime版本,這兩個版本之間都在努力的保持一致。

運(yùn)行時在開發(fā)中的主要應(yīng)用場景

  • 字典轉(zhuǎn)模型
  • 給分類增加關(guān)聯(lián)對象,開發(fā)框架時解耦
  • 交換方法,在無法修改系統(tǒng)或者第三方框架的方式時
    • 利用交換方法,先執(zhí)行自己的方法
    • 再執(zhí)行系統(tǒng)或第三方框架的方法
    • 黑魔法,對系統(tǒng)/框架版本有很強(qiáng)的依賴性

1、動態(tài)獲取類的屬性

常用與字典轉(zhuǎn)模型的時候使用

我這里就用一個NSObject的分類給大家詳細(xì)的講解一下
大概思路:
1、class_copyPropertyList 獲取屬性的數(shù)組
2、遍歷數(shù)組,property_getName 獲取每一個屬性的名稱
3、添加到數(shù)組中
4、free 釋放數(shù)組

首先我先創(chuàng)建了一個繼承自NSObject的類,里面有兩個成員變量,然后在創(chuàng)建一個分類,我們要在分類中獲取person類中的成員變量

DB39A491-125F-4977-89DE-09572415982D.png

在寫之前我們肯定要讓分類中添加#import <objc/runtime.h>

在分類方法中
  • 第一步:調(diào)用運(yùn)行時方法,獲取類的屬性列表
    調(diào)用的是class_copy...方法
屏幕快照 2017-03-16 上午11.55.19.png

我們可以看到一共聯(lián)想出了4中方法,Ivar 成員變量,Method 方法,Property 屬性,Protocol 協(xié)議,通過這四種方法,我們可以獲取所有我們想要知道的。
這里我們想要獲取Person類的屬性,所以我們就調(diào)用了class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)這個方法。

方法解析

48CD87E0-FBCF-4451-AD55-36EA01AA064B.png

我們可以看到這個方法里面一共有兩個參數(shù),我們點(diǎn)住該方法,按住control鍵,可以看到該方法的一些具體內(nèi)容,內(nèi)容如下

1F0BF0E5-1A91-49E7-81DB-894ED7E39EB4.png

 /**
     參數(shù)
     1. 要獲取的類
     2. 類屬性的個數(shù)指針
     
     返回值
     所有屬性的`數(shù)組`,C 語言中,數(shù)組的名字,就是指向第一個元素的地址
     
     retain/create/copy 需要 release,最好 option + click
     */
    unsigned int count = 0;
    objc_property_t *proList = class_copyPropertyList([self class], &count);
    
    NSLog(@"屬性的數(shù)量 %d", count);

釋放數(shù)組的方法free(proList);

這個時候,我們打印count,會發(fā)現(xiàn)count=2;有圖有真相,請看下面打印

3EE4DEAB-9577-4D12-B2FE-79BA6791AB17.png

這個時候,我們雖然獲取到了2,但是我們是想要具體的內(nèi)容,而不是這個,所以還需要繼續(xù)向下走

  • 第二步:遍歷數(shù)組,拿到我們想要的東西
    這里面有兩個主要的方法
    • objc_property_t pty = proList[i];
    • const char *cName = property_getName(pty);
 // 遍歷所有的屬性
    for (unsigned int i = 0; i < count; i++) {
        
        // 1. 從數(shù)組中取得屬性
        /**
         C 語言的結(jié)構(gòu)體指針,通常不需要 `*`
         */
        objc_property_t pty = proList[i];
        
        // 2. 從 pty 中獲得屬性的名稱
        const char *cName = property_getName(pty);
        
        NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
        
        NSLog(@"%@", name);
    }

打印結(jié)果


66C705D1-5904-4B91-840F-133D439D1978.png
  • 第三步:獲取關(guān)聯(lián)對象,動態(tài)添加屬性,當(dāng)Person對象屬性已經(jīng)獲取的時候,就直接返回,防止多次調(diào)用運(yùn)行時方法提高效率
    需要用到的兩個方法

  • objc_getAssociatedObject

  • objc_setAssociatedObject

const char * kPropertiesListKey = "CZPropertiesListKey";
 // --- 1. 從`關(guān)聯(lián)對象`中獲取對象屬性,如果有,直接返回!
    /**
     獲取關(guān)聯(lián)對象 - 動態(tài)添加的屬性
     
     參數(shù):
     1. 對象 self
     2. 動態(tài)屬性的 key
     
     返回值
     動態(tài)添加的`屬性值`
     */
    NSArray *ptyList = objc_getAssociatedObject(self, kPropertiesListKey);
    if (ptyList != nil) {
        return ptyList;
    }


// --- 2. 到此為止,對象的屬性數(shù)組已經(jīng)獲取完畢,利用關(guān)聯(lián)對象,動態(tài)添加屬性
    /**
     參數(shù)
     
     1. 對象 self [OC 中 class 也是一個特殊的對象]
     2. 動態(tài)添加屬性的 key,獲取值的時候使用
     3. 動態(tài)添加的屬性值
     4. 對象的引用關(guān)系
     */
    objc_setAssociatedObject(self, kPropertiesListKey, arrayM.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
動態(tài)獲取類的屬性,方法講解完畢,大家感覺如何,下面是完整的代碼
{
    const char * kPropertiesListKey = "CZPropertiesListKey";
    // --- 1. 從`關(guān)聯(lián)對象`中獲取對象屬性,如果有,直接返回!
    /**
     獲取關(guān)聯(lián)對象 - 動態(tài)添加的屬性
     
     參數(shù):
     1. 對象 self
     2. 動態(tài)屬性的 key
     
     返回值
     動態(tài)添加的`屬性值`
     */
    NSArray *ptyList = objc_getAssociatedObject(self, kPropertiesListKey);
    if (ptyList != nil) {
        return ptyList;
    }
    
    // 調(diào)用運(yùn)行時方法,取得類的屬性列表
    // Ivar 成員變量
    // Method 方法
    // Property 屬性
    // Protocol 協(xié)議
    /**
     參數(shù)
     1. 要獲取的類
     2. 類屬性的個數(shù)指針
     
     返回值
     所有屬性的`數(shù)組`,C 語言中,數(shù)組的名字,就是指向第一個元素的地址
     
     retain/create/copy 需要 release,最好 option + click
     */
    unsigned int count = 0;
    objc_property_t *proList = class_copyPropertyList([self class], &count);
    
    NSLog(@"屬性的數(shù)量 %d", count);
    // 創(chuàng)建數(shù)組
    NSMutableArray *arrayM = [NSMutableArray array];
    
    // 遍歷所有的屬性
    for (unsigned int i = 0; i < count; i++) {
        
        // 1. 從數(shù)組中取得屬性
        /**
         C 語言的結(jié)構(gòu)體指針,通常不需要 `*`
         */
        objc_property_t pty = proList[i];
        
        // 2. 從 pty 中獲得屬性的名稱
        const char *cName = property_getName(pty);
        
        NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
        
//        NSLog(@"%@", name);
        // 3. 屬性名稱添加到數(shù)組
        [arrayM addObject:name];
    }
    
    // 釋放數(shù)組
    free(proList);
    
    // --- 2. 到此為止,對象的屬性數(shù)組已經(jīng)獲取完畢,利用關(guān)聯(lián)對象,動態(tài)添加屬性
    /**
     參數(shù)
     
     1. 對象 self [OC 中 class 也是一個特殊的對象]
     2. 動態(tài)添加屬性的 key,獲取值的時候使用
     3. 動態(tài)添加的屬性值
     4. 對象的引用關(guān)系
     */
    objc_setAssociatedObject(self, kPropertiesListKey, arrayM.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    return arrayM.copy;
}

2、字典轉(zhuǎn)模型

上面那一個方法可以使我們獲取Person對象的所有屬性,那么字典轉(zhuǎn)模型使用一個KVC就可以了。
具體方法

// 所有字典轉(zhuǎn)模型框架,核心算法!
+ (instancetype)objWithDict:(NSDictionary *)dict {
    // 實(shí)例化對象
    id object = [[self alloc] init];
    
    // 使用字典,設(shè)置對象信息
    // 1> 獲得 self 的屬性列表
    NSArray *proList = [self getPersonArray];
    
    // 2> 遍歷字典
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        NSLog(@"key %@ --- value %@", key, obj);
        // 3> 判斷 key 是否在 proList 中
        if ([proList containsObject:key]) {
            //  說明屬性存在,可以使用 `KVC` 設(shè)置數(shù)值
            [object setValue:obj forKey:key];
        }
    }];
    
    return object;
}

3、交叉方法

進(jìn)行時之所以那么被廣大的iOS程序員所敬仰,很大的一部分原因就是因?yàn)檫@個交叉方法,我們可以用這個方法更改任意代碼,交叉方法也被稱為黑魔法。但是,我們平時不到萬不得已最好不要用這個黑魔法,就像斗地主一樣,你上來就王炸,那么以后的路肯定就不會好走了。
援引一段AFNetWorking作者的一段話

最后,請記住僅在不得已的情況下使用 Objective-C runtime。隨便修改基礎(chǔ)框架或所使用的三方代碼是毀掉你的應(yīng)用的絕佳方法哦。請務(wù)必要小心哦。

原文鏈接

交叉方法一般使用的地方
  • 我們使用第三方框架時,我們發(fā)現(xiàn)了一些錯誤,我們不用修改第三方的代碼
  • 第三方框架或者系統(tǒng)原生方法十分不夠我們的使用,我們強(qiáng)烈希望增加一些功能
這里面有三個常用的方法
  • 獲取類方法
Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
  • 替換原方法實(shí)現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  • 交換兩個方法
method_exchangeImplementations(oriMethod, cusMethod);

這里我給imageView做了一個交換方法,調(diào)整圖像尺寸
代碼如下

// 在類被加載到運(yùn)行時的時候,就會執(zhí)行
+ (void)load {
   
   // 1. 獲取 UIImageView 類的 實(shí)例方法 `setImage:`
   Method originalMethod = class_getInstanceMethod([self class], @selector(setImage:));
   // 2. 獲取 UIImageView 類的 實(shí)例方法 `cz_setImage:`,本身定義在分類中
   Method swizzledMethod = class_getInstanceMethod([self class], @selector(cz_setImage:));

   // 3. 交換方法 setImage 和 cz_setImage,交換完成之后
   // 1> 調(diào)用 setImage 相當(dāng)于調(diào)用 cz_setImage
   // 2> 調(diào)用 cz_setImage 相當(dāng)于調(diào)用 setImage
   method_exchangeImplementations(originalMethod, swizzledMethod);
}

///  1. 當(dāng)在其他位置調(diào)用 `setImage` 方法時,`自動`調(diào)用 cz_setImage: 方法
- (void)cz_setImage:(UIImage *)image {
   NSLog(@"%s %@", __FUNCTION__, image);
   
   // 1. 根據(jù) imageView 的大小,重新調(diào)整 image 的大小
   // 使用 `CG` 重新生成一張和目標(biāo)尺寸相同的圖片
   UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0);
   
   // 繪制圖像
   [image drawInRect:self.bounds];
   
   // 取得結(jié)果
   UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
   
   // 關(guān)閉上下文
   UIGraphicsEndImageContext();
   
   // 調(diào)用系統(tǒng)默認(rèn)的 setImage 方法
   [self cz_setImage:result];
}

4、給分類添加屬性

給分類添加屬性其實(shí)就是獲取關(guān)聯(lián)對象,然后添加
內(nèi)容比較簡單,直接上代碼

.h文件

//分類的頭文件
@interface ClassName (CategoryName)
@property (nonatomic, strong) NSString *str;
@end
.m文件

//實(shí)現(xiàn)文件
#import "ClassName + CategoryName.h"
#import <objc/runtime.h>

static void *strKey = &strKey;

@implementation ClassName (CategoryName) 
-(void)setStr:(NSString *)str  
{  
    objc_setAssociatedObject(self, & strKey, str, OBJC_ASSOCIATION_COPY);  
}  

-(NSString *)str  
{  
    return objc_getAssociatedObject(self, &strKey);  
}
@end

5、NSClassFromString(根據(jù)一個字符串生成一個類)

使用NSClassFromString 使用NSClassFromString可以直接從字符串初始化出對象出來,即使不引用頭文件也沒關(guān)系。
這個方法判斷類是否存在,如果存在就動態(tài)加載的,不存為就返回一個空對象;
簡單使用方法

 id myObj = [[NSClassFromString(@"MySpecialClass") alloc] init];

  正常情況下等價于:

id myObj = [[MySpecialClass alloc] init];

      但是,如果你的程序中并不存在MySpecialClass這個類,下面的寫法會出錯,而上面的寫法只是返回一個空對象而已。

其中對這個方法有一個比較經(jīng)典的用法,iOS 萬能跳轉(zhuǎn)界面方法 (runtime實(shí)用篇一)
想要了解的小伙伴們可以點(diǎn)進(jìn)去看一看作者的思路。

最后在給大家推薦幾篇比較好的文章,有興趣的同學(xué)可以看一看

runtime詳解
OC最實(shí)用的runtime總結(jié),面試、工作你看我就足夠了!
Runtime 10種用法(沒有比這更全的了)
iOS 萬能跳轉(zhuǎn)界面方法 (runtime實(shí)用篇一)
button防止被重復(fù)點(diǎn)擊的相關(guān)方法(詳細(xì)版)

講解的東西都是超級基礎(chǔ)的東西,小伙伴們要是對進(jìn)行時不太了解的可以看一看,看過以后,在看一看大神們都是怎么靈活運(yùn)用的。想來那樣應(yīng)該就可以應(yīng)付工作上的一些需求了。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,107評論 2 375

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,753評論 0 9
  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運(yùn)行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,730評論 7 64
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,579評論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,148評論 0 9
  • 若同時追兩只兔子........ ..................你一只也抓不到。(-俄羅斯諺語) 連續(xù)兩周不...
    夏花de解憂雜貨鋪閱讀 339評論 14 22