1. 程式人生 > >OC執行時機制之Runtime

OC執行時機制之Runtime

一、runtime簡介

  • RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。
  • 對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式
  • 對於OC的函式,屬於動態呼叫過程,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行的時候才會根據函式的名稱找到對應的函式來呼叫。
  • 事實證明:
    • 在編譯階段,OC可以呼叫任何函式,即使這個函式並未實現,只要宣告過就不會報錯。
    • 在編譯階段,C語言呼叫未實現的函式就會報錯。

二、runtime作用

1.傳送訊息

  • 方法呼叫的本質,就是讓物件傳送訊息。
  • objc_msgSend,只有物件才能傳送訊息,因此以objc開頭.
  • 使用訊息機制
    前提,必須匯入#import <objc/message.h>
  • 訊息機制簡單使用
    // 建立person物件
    Person *p = [[Person alloc] init];

    // 呼叫物件方法
    [p eat];

    // 本質:讓物件傳送訊息
    objc_msgSend(p, @selector(eat));

    // 呼叫類方法的方式:兩種
    // 第一種通過類名呼叫
    [Person eat];
    // 第二種通過類物件呼叫
    [[Person class] eat];

    // 用類名呼叫類方法,底層會自動把類名轉換成類物件呼叫
    // 本質:讓類物件傳送訊息
    objc_msgSend([Person class], @selector(eat));
  • 訊息機制原理:物件根據方法編號SEL去對映表查詢對應的方法實現

2.交換方法

  • 開發使用場景:系統自帶的方法功能不夠,給系統自帶的方法擴充套件一些功能,並且保持原有的功能。
  • 方式一:繼承系統的類,重寫方法.
  • 方式二:使用runtime,交換方法.
@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // 需求:給imageNamed方法提供功能,每次載入圖片就判斷下圖片是否載入成功。
    // 步驟一:先搞個分類,定義一個能載入圖片並且能列印的方法+ (instancetype)imageWithName:(NSString *)name;
    // 步驟二:交換imageNamed和imageWithName的實現,就能呼叫imageWithName,間接呼叫imageWithName的實現。
    UIImage *image = [UIImage imageNamed:@"123"];

}

@end


@implementation UIImage (Image)
// 載入分類到記憶體的時候呼叫
+ (void)load
{
    // 交換方法

    // 獲取imageWithName方法地址
    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));

    // 獲取imageWithName方法地址
    Method imageName = class_getClassMethod(self, @selector(imageNamed:));

    // 交換方法地址,相當於交換實現方式
    method_exchangeImplementations(imageWithName, imageName);


}

// 不能在分類中重寫系統方法imageNamed,因為會把系統的功能給覆蓋掉,而且分類中不能呼叫super.

// 既能載入圖片又能列印
+ (instancetype)imageWithName:(NSString *)name
{

    // 這裡呼叫imageWithName,相當於呼叫imageName
    UIImage *image = [self imageWithName:name];

    if (image == nil) {
        NSLog(@"載入空的圖片");
    }

    return image;
}


@end
  • 交換原理:
    • 交換之前: 

    • 交換之後: 

3.動態新增方法

  • 開發使用場景:如果一個類方法非常多,載入類到記憶體的時候也比較耗費資源,需要給每個方法生成對映表,可以使用動態給某個類,新增方法解決。
  • 簡單使用
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    Person *p = [[Person alloc] init];

    // 預設person,沒有實現eat方法,可以通過performSelector呼叫,但是會報錯。
    // 動態新增方法就不會報錯
    [p performSelector:@selector(eat)];

}


@end


@implementation Person
// void(*)()
// 預設方法都有兩個隱式引數,
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當一個物件呼叫未實現的方法,會呼叫這個方法處理,並且會把對應的方法列表傳過來.
// 剛好可以用來判斷,未實現的方法是不是我們想要動態新增的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{

    if (sel == @selector(eat)) {
        // 動態新增eat方法

        // 第一個引數:給哪個類新增方法
        // 第二個引數:新增方法的方法編號
        // 第三個引數:新增方法的函式實現(函式地址)
        // 第四個引數:函式的型別,(返回值+引數型別) v:void @:物件->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "[email protected]:");

    }

    return [super resolveInstanceMethod:sel];
}
@end

4.給分類新增屬性

  • 原理:給一個類宣告屬性,其實本質就是給這個類新增關聯,並不是直接把這個值的記憶體空間新增到類存空間。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 給系統NSObject類動態新增屬性name

    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"圖靈";
    NSLog(@"%@",objc.name);

}


@end


// 定義關聯的key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name
{
    // 根據關聯的key,獲取關聯的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // 第一個引數:給哪個物件新增關聯
    // 第二個引數:關聯的key,通過這個key獲取
    // 第三個引數:關聯的value
    // 第四個引數:關聯的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

5.字典轉模型

  • 設計模型:字典轉模型的第一步
    • 模型屬性,通常需要跟字典中的key一一對應
    • 問題:一個一個的生成模型屬性,很慢?
    • 需求:能不能自動根據一個字典,生成對應的屬性。
    • 解決:提供一個分類,專門根據字典生成對應的屬性字串。
    @implementation NSObject (Log)


// 自動列印屬性字串
+ (void)resolveDict:(NSDictionary *)dict{

    // 拼接屬性字串程式碼
    NSMutableString *strM = [NSMutableString string];

    // 1.遍歷字典,把字典中的所有key取出來,生成對應的屬性程式碼
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

        // 型別經常變,抽出來
         NSString *type;

        if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
            type = @"NSString";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
            type = @"NSArray";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
            type = @"int";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
            type = @"NSDictionary";
        }


        // 屬性字串
        NSString *str;
        if ([type containsString:@"NS"]) {
            str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
        }else{
            str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
        }

        // 每生成屬性字串,就自動換行。
        [strM appendFormat:@"\n%@\n",str];

    }];

    // 把拼接好的字串打印出來,就好了。
    NSLog(@"%@",strM);

}


@end
  • 字典轉模型的方式一:KVC
@implementation Status


+ (instancetype)statusWithDict:(NSDictionary *)dict
{
    Status *status = [[self alloc] init];

    [status setValuesForKeysWithDictionary:dict];

    return status;

}

@end
  • KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。
    • 如果不一致,就會呼叫[<Status 0x7fa74b545d60> setValue:forUndefinedKey:] 報key找不到的錯。
    • 分析:模型中的屬性和字典的key不一一對應,系統就會呼叫setValue:forUndefinedKey:報錯。
    • 解決:重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋, 就能繼續使用KVC,字典轉模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{

}
  • 字典轉模型的方式二:Runtime
    • 思路:利用執行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查詢key,取出對應的值,給模型的屬性賦值。
    • 步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類轉。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 解析Plist檔案
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];

    NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];

    // 獲取字典陣列
    NSArray *dictArr = statusDict[@"statuses"];

    // 自動生成模型的屬性字串
//    [NSObject resolveDict:dictArr[0][@"user"]];


    _statuses = [NSMutableArray array];

    // 遍歷字典陣列
    for (NSDictionary *dict in dictArr) {

        Status *status = [Status modelWithDict:dict];

        [_statuses addObject:status];

    }

    // 測試資料
    NSLog(@"%@ %@",_statuses,[_statuses[0] user]);


}

@end

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 思路:遍歷模型中所有屬性-》使用執行時

    // 0.建立對應的物件
    id objc = [[self alloc] init];

    // 1.利用runtime給物件中的成員屬性賦值

    // class_copyIvarList:獲取類中的所有成員屬性
    // Ivar:成員屬性的意思
    // 第一個引數:表示獲取哪個類中的成員屬性
    // 第二個引數:表示這個類有多少成員屬性,傳入一個Int變數地址,會自動給這個變數賦值
    // 返回值Ivar *:指的是一個ivar陣列,會把所有成員屬性放在一個數組中,通過返回的陣列就能全部獲取到。
    /* 類似下面這種寫法

     Ivar ivar;
     Ivar ivar1;
     Ivar ivar2;
     // 定義一個ivar的陣列a
     Ivar a[] = {ivar,ivar1,ivar2};

     // 用一個Ivar *指標指向陣列第一個元素
     Ivar *ivarList = a;

     // 根據指標訪問陣列第一個元素
     ivarList[0];

     */
    unsigned int count;

    // 獲取類中的所有成員屬性
    Ivar *ivarList = class_copyIvarList(self, &count);

    for (int i = 0; i < count; i++) {
        // 根據角標,從陣列取出對應的成員屬性
        Ivar ivar = ivarList[i];

        // 獲取成員屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 處理成員屬性名->字典中的key
        // 從第一個角標開始擷取
        NSString *key = [name substringFromIndex:1];

        // 根據成員屬性名去字典中查詢對應的value
        id value = dict[key];

        // 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型
        // 判斷下value是否是字典
        if ([value isKindOfClass:[NSDictionary class]]) {
            // 字典轉模型
            // 獲取模型的類物件,呼叫modelWithDict
            // 模型的類名已知,就是成員屬性的型別

            // 獲取成員屬性型別
           NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
          // 生成的是這種@"@\"User\"" 型別 -》 @"User"  在OC字串中 \" -> ",\是轉義的意思,不佔用字元
            // 裁剪型別字串
            NSRange range = [type rangeOfString:@"\""];

           type = [type substringFromIndex:range.location + range.length];

            range = [type rangeOfString:@"\""];

            // 裁剪到哪個角標,不包括當前角標
          type = [type substringToIndex:range.location];


            // 根據字串類名生成類物件
            Class modelClass = NSClassFromString(type);


            if (modelClass) { // 有對應的模型才需要轉

                // 把字典轉模型
                value  =  [modelClass modelWithDict:value];
            }


        }

        // 三級轉換:NSArray中也是字典,把陣列中的字典轉換成模型.
        // 判斷值是否是陣列
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應類有沒有實現字典陣列轉模型陣列的協議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 轉換成id型別,就能呼叫任何物件的方法
                id idSelf = self;

                // 獲取陣列中字典對應的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
               Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典陣列,生成模型陣列
                for (NSDictionary *dict in value) {
                    // 字典轉模型
                  id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型陣列賦值給value
                value = arrM;

            }
        }


        if (value) { // 有值,才需要給模型的屬性賦值
            // 利用KVC給模型中的屬性賦值
            [objc setValue:value forKey:key];
        }

    }

    return objc;
}

@end

相關推薦

OC執行機制Runtime

一、runtime簡介 RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式。對於OC的函式,屬於動態呼叫過程,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行的時候才會根據函式的名稱找到對應的函式來呼

runtime 執行機制

runtime是一套比較底層的純C語言API, 屬於1個C語言庫, 包含了很多底層的C語言API。 在我們平時編寫的OC程式碼中, 程式執行過程時, 其實最終都是轉成了runtime的C語言程式碼, runtime算是OC的幕後工作者 比如說,下面一個建立物件的方法中, 舉例: OC : [[MJP

自定義註解執行註解(RetentionPolicy.RUNTIME)

前面有提到註解按生命週期來劃分可分為3類: 1、RetentionPolicy.SOURCE:註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄; 2、RetentionPolicy.CLASS:註解被保留到class檔案,但jvm載入class檔案

詳解Runtime執行機制

簡介 Runtime 又叫執行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 程式碼,底層都是基於它來實現的。比如: [receiver message]; // 底層執行時會被編譯器轉化為: objc_msgSend(receive

iOS開發執行程式設計(Runtime Programming)淺讀

  什麼是執行時(Objective-C runtime)?       簡單的來說,Objective-C runtime是一個實現 Objective-C語言的庫。物件可以用C語言的結構體表示,而方法(methods) 可以用C函式實現。       事實上,他們也差不

關於執行異常(Runtime Exception)和受檢查的異常(Checked Exception)以及系統異常和普通異常的一些總結

Exception 表示程式還能夠克服和恢復的問題,Exception 類又分為執行時異常(Runtime Exception)和受檢查的異常(Checked Exception),所謂執行時異常就是開發人員編寫程式碼時不會報紅,但是執行不得當的話會執行出錯,也

第三回 執行類資訊(Runtime Class Information)

為清晰起見,省略了行末的\ ) #define DECLARE_CLASS(clss) public:                                     //類資訊,派生自基類CClass     class CClass_##clss:public CClass      

Android 執行資源替換----Runtime Resource Overlay

       先拋一個問題:現在有一個第三方應用,沒有程式碼,只有編譯好的apk,在不去改動這個apk的前提下,如果想改變這個應用中的一些資源顯示效果,比如改變一個button的文字,一個imagev

[執行]Objective-C的執行程式設計(Runtime Programming)

以前還真沒了解過Objective-C的執行時程式設計(Runtime Programming),今天特意在網上搜了下,原來這麼深奧啊 表示現在理解不了,先轉走了再說,之前轉載的文章都是大神們總結的綜合,轉載地址忘記註明了 ,抱歉。 --  [1] 版本和平臺   -

JAVA多執行機制暫停、恢復和停止

在JDK1.2以前的版本如果要實現執行緒的暫停、恢復和停止的方法分別是suspend()、resume()、stop()。但是從JDK1.2以後這些方法已經被遺棄,因為它們有可能造成嚴重的系統錯誤和異常。 首先suspend()方法不會釋放執行緒所佔用的資源。

VS的執行庫(Runtime lIB)

在開發window程式是經常會遇到編譯好好的程式拿到另一臺機器上面無法執行的情況,這一般是由於另一臺機器上面沒有安裝響應的執行時庫導致的,那麼這個與編譯選項MT、MTd、MD、MDd有什麼關係呢?這是msdn上面的解釋: MT:mutithread,多執行緒庫

OC執行獲取物件的所有屬性、所有方法

一、建立NSObject的Category檔案。引入runtime標頭檔案。 Runtime各種方法屬性參見:http://blog.csdn.net/sharktoping/article/details/59486347 #import <objc/runt

OC執行簡單運用(2)

在開發過程中,如果經常會遇到想要給現有的類(閉源類)新增自己的方法或者屬性,但是繼承又會顯得麻煩和臃腫,但是category又不支援,這時候我們就可以利用執行時的特性來解決這一問題。 (1)假設我們要

iOS Runtime 執行三:訊息處理機制

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別

Java中Runtime執行環境機制總結

最近由於在編碼中需要在java程式碼中執行linux命令,使用到了Runtime類的一些方法,也出現幾個小bug,所以趁這個機會對Runtime也就是執行時環境這個類進行總結。 Runtime.getRuntime()能得到一個Runtime物件例項,也就是當前執行時環境例

Objective-C Runtime 執行五:協議與分類

Objective-C中的分類允許我們通過給一個類新增方法來擴充它(但是通過category不能新增新的例項變數),並且我們不需要訪問類中的程式碼就可以做到。 Objective-C中的協議是普遍存在的介面定義方式,即在一個類中通過@protocol定義介面,在另外

Objective-C Runtime 執行六:拾遺

前面幾篇基本介紹了runtime中的大部分功能,包括對類與物件、成員變數與屬性、方法與訊息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。 本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏

Objective-C Runtime 執行二:成員變數與屬性

在前面一篇文章中,我們介紹了Runtime中與類和物件相關的內容,從這章開始,我們將討論類實現細節相關的內容,主要包括類中成員變數,屬性,方法,協議與分類的實現。 本章的主要內容將聚集在Runtime對成員變數與屬性的處理。在討論之前,我們先介紹一個重要的概念:型別

Objective-C Runtime 執行三:方法與訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

Objective-C Runtime 執行四:Method Swizzling

理解Method Swizzling是學習runtime機制的一個很好的機會。在此不多做整理,僅翻譯由Mattt Thompson發表於nshipster的Method Swizzling一文。 Method Swizzling是改變一個selector的實際實現的