Runtime 執行時(未完待續)

碼路芽子發表於2018-07-04

Runtime簡介

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

Runtime各種方法使用

Runtime訊息機制

  • 介紹
  • 方法呼叫本質: 就是傳送一個訊息, 用Runtime傳送訊息, OC底層是通過Runtime實現

    匯入標頭檔案 `#import <objc/message.h>`

    id objc = [NSObject alloc];
    objc = [objc init];
    /**
     * 最終生成訊息機制, 編輯器做的事情
     * 最終程式碼, 需要把當前程式碼重新編譯, 用xcode編譯器, clang
     * clang -rewrite-objc main.m 檢視最終生成程式碼
     * Runtime都有個字首, 誰的事情就使用誰
     */
     /**
     * id: 誰傳送訊息
     * SEL: 傳送什麼訊息
     */

    
    /**
     * xcode6之前, 蘋果可以使用objc_msgSend, 而且有引數提示
     * xcode6之後不推薦使用Runtime
     * 解決方法: 找到build setting -> 搜尋msg, 改成NO, 不用嚴格檢查
            這樣Runtime就正常了
     */
    
    /**
     * id objc = [NSObject alloc];
     * objc = [objc init];.
     
     * 用Runtime寫這兩句
       *太麻煩 id objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)([NSObject class], @selector(alloc));
     */
     id objc = objc_msgSend([NSObject class], @selector(alloc));
    objc = objc_msgSend(objc, @selector(init));
    
    
    ******************
    //TODO: 什麼時候呼叫Runtime, 方法呼叫流程
    /** 最底層寫法 */
    Person *per = [Person alloc];
    objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    per = [per init];
    objc_msgSend(per, sel_registerName("init"));

    /** 呼叫物件方法 */
    objc_msgSend(per, @selector(eat));
複製程式碼

什麼時候使用runtime

不得不用runtime訊息機制, 可以呼叫私有方法, 因為正常的沒有宣告的方法, oc不能使用

//TODO: 什麼時候呼叫Runtime, 方法呼叫流程
    /** 最底層寫法 */
    Person *per = [Person alloc];
    objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    per = [per init];
    objc_msgSend(per, sel_registerName("init"));
  
    /** 呼叫物件方法 */
    objc_msgSend(per, @selector(eat));
    objc_msgSend(per, @selector(run:), 20);
複製程式碼

開發中使用場景

  • Runtime的方法都是有字首的, 誰的事情誰開頭

  • 方法呼叫流程

  • 怎麼去呼叫eat方法, 物件方法: 類物件的方法列表中 類方法: 元類中方法列表

  • 1.通過isa去對應的類中查詢方法

    QQ20160923-0.png
    2.註冊方法編號 3.根據方法編號去查詢對應方法 4.找到的只是最終函式實現地址, 根據地址去方法區呼叫對應函式

  • 記憶體的5大區

    1. 棧區 -> 不需要手動管理記憶體, 自動管理
    2. 堆 -> 需要手動管理記憶體, 根據地址去方法去呼叫
    3. 靜態區
    4. 常量區
    5. 方法區

二 交換方法

案例

/** 把類記載進記憶體的時候只呼叫一次 */
+ (void)load
{
    /** 獲取方法
       * imageNamed
       * wdy_imageNamed
     */
    /** 獲取那個類方法, 獲取那個方法 */
    Method imageNamedMethod = class_getInstanceMethod(self, @selector(imageNamed:));
    Method wdy_imagenameMethod = class_getInstanceMethod(self, @selector(wdy_imageNamed:));
    
    /** Runtime交換方法 */
    method_exchangeImplementations(imageNamedMethod, wdy_imagenameMethod);
    
}

/** 呼叫多次  */
+ (void)initialize
{
    
}

/** 載入圖片 */
/** 判斷是否載入成功 */
+ (UIImage *)wdy_imageNamed:(NSString *)name
{
    UIImage *image = [self wdy_imageNamed:name];
    if (image) {
        NSLog(@"載入成功");
    } else {
        NSLog(@"載入失敗");
    }
    return image;
}
複製程式碼
/**
 * Runtime(交換方法):
 * 需求:讓UIImage載入圖片, 告訴我是否成功
    解決方法: 
    1.自定義UIimage
        弊端:
        *.每次使用, 都需要匯入
        *.專案大了, 沒辦法實現
    2.用UIimage方法
        給系統imageNamed新增功能, 只能用Runtime
 
    解決方法: 
        1.給系統新增方法分類
        2.自己實現一個帶有擴充套件功能的方法
        3.交換方法, 只需要交換一次    
 *
 */


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /** 麻煩的方法 */
    UIImage *image = [UIImage imageNamed:@"1.png"]; 
}

複製程式碼

三 動態新增方法

  • 描述: 新增一個類, 想要在適當的時候新增一個方法, 例如當使用者成為會員的時候, 進行一些操作
  • 問題: Runtime動態新增方法:OC中都是懶載入機制, 只要有個方法實現了, 就立馬新增到方法列表中 美團有個面試題 有沒有使用過 self performSelector:<#(SEL)#>, 什麼時候使用?動態新增方法的時候使用過. 怎麼動態新增方法?用Runtime新增. 為什麼要動態新增?

*/

  • 準備: 在ViewDidLoad中建立一個類, 並且新增一個方法

      - (void)viewDidLoad {
        [super viewDidLoad];
         
        Person *per = [[Person alloc] init];
        [per performSelector:@selector(eat)];
    
        [per performSelector:@selector(run:) withObject:@10];
    }
    
    複製程式碼

在類中新增一個方法

  • 這個方法在一個地方呼叫了未實現的方法就會呼叫: + (BOOL)resolveInstanceMethod:(SEL)sel

/** 沒有返回值沒有引數 */
void aaa(id self, SEL _cmd) {
   NSLog(@"吃東西啊啊 啊啊啊啊!");
}

void run(id self, SEL _cmd, NSNumber *num) {
   NSLog(@"跑了%@", num);
}


/**
* 解決新增的方法
* 什麼時候呼叫方法: 只要一個物件呼叫了一個未實現的方法就會呼叫這個方法, 進行處理 
* 作用: 動態新增方法, 處理未實現
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
   /** 任何方法都有兩個隱士:sel, cmd->方法編號 */
   if (sel == NSSelectorFromString(@"eat")) {
       /**
        * 給誰新增方法
        * SEL: 新增那個方法
        * IMP: 方法實現
        * type: 方法型別
        */
       class_addMethod(self, sel, (IMP)aaa, "v@:");
   }
   
   if (sel == NSSelectorFromString(@"run:")) {
       class_addMethod(self, sel, (IMP)run, "v@:@");
   }

 return [super resolveInstanceMethod:sel];
}

複製程式碼

相關文章