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去對應的類中查詢方法
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];
}
複製程式碼