IOS Object-C 中Runtime詳解及例項程式碼

IOS Object-C 中Runtime詳解及例項程式碼
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

IOS Object-C 中Runtime詳解

最近了解了一下OC的Runtime,真的是OC中很強大的一個機制,看起來比較底層,但其實可以有很多活用的方式。

什麼是Runtime

我們雖然是用Objective-C寫的程式碼,其實在執行過程中都會被轉化成C程式碼去執行。比如說OC的方法呼叫都會轉成C函式 id objc_msgSend ( id self, SEL op, … ); 而OC中的物件其實在Runtime中都會用結構體來表示,這個結構體中包含了類名、成員變數列表、方法列表、協議列表、快取等。

類在Runtime中的表示:


struct objc_class {
Class isa;//指標,顧名思義,表示是一個什麼,
//例項的isa指向類物件,類物件的isa指向元類
#if !__OBJC2__
Class super_class; //指向父類
const char *name; //類名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成員變數列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//快取
//一種優化,呼叫過的方法存入快取列表,下次呼叫先找快取
struct objc_protocol_list *protocols //協議列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

整個Runtime機制其實可以挖的點很多,這裡只是簡單的介紹一些常見的用法,如果將其細細解析,相信一定會對OC的理解加深幾個層面。

獲取屬性/方法/協議列表

最直接的一種用法,就是獲取我們的結構體中儲存的物件的屬性、方法、協議等列表,從而獲取其所有這些資訊。

要獲取也比較簡單,但是自己嘗試之前需要注意幾點:

一定要自己給類加幾個屬性、方法,遵循一些協議,否則當然是看不到輸出資訊的。

要使用這些獲取的方法,需要匯入標頭檔案 #import


#import <objc/runtime.h>
// 輸出類的一些資訊
- (void)logInfo {
unsigned int count;// 用於記錄列表內的數量,進行迴圈輸出
// 獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i  ) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property --> %@", [NSString stringWithUTF8String:propertyName]);
}
// 獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i < count; i  ) {
Method method = methodList[i];
NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
}
// 獲取成員變數列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i < count; i  ) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar --> %@", [NSString stringWithUTF8String:ivarName]);
}
// 獲取協議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i < count; i  ) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol --> %@", [NSString stringWithUTF8String:protocolName]);
}
}

方法呼叫的過程

呼叫方法分為呼叫例項方法和呼叫類方法,在結構體我們可以看到第一個就是一個 isa 指標,會指向類物件或者元類,什麼叫元類呢?

每個例項物件有個isa的指標,他指向物件的類,而類裡也有個isa的指標, 指向meteClass(元類)。元類儲存了類方法的列表。當類方法被呼叫時,先會從本身查詢類方法的實現,如果沒有,元類會向他父類查詢該方法。同時注意的是:元類(meteClass)也是類,它也是物件。元類也有isa指標,它的isa指標最終指向的是一個根元類(root meteClass)。根元類的isa指標指向本身,這樣形成了一個封閉的內迴圈。

通過isa,就可以不斷往上方去回溯自己的父類等,而方法的呼叫也利用了這個過程:

首先,當然在物件自己快取的方法列表中去找要呼叫的方法,找到了就直接執行其實現。
快取裡沒找到,就去上面說的它的方法列表裡找,找到了就執行其實現。
還沒找到,說明這個類自己沒有了,就會通過isa去向其父類裡執行1、2。
如果找到了根類還沒找到,那麼就是沒有了,會轉向一個攔截呼叫的方法,我們可以自己在攔截呼叫方法裡面做一些處理。
如果沒有在攔截呼叫裡做處理,那麼就會報錯崩潰。

以上就是方法呼叫的過程。我們可以看到的是,所謂重寫父類方法,並不是抹除了父類方法,父類的方法還是存在的,只是我們在子類裡面找到了就不會再去父類裡找了,是這個層面的“覆蓋”。而我們熟悉的 super 呼叫父類的方法實現,就是告知要去呼叫父類實現的標識。

攔截呼叫與動態新增

上面說到了攔截呼叫,就是在所有地方都找不到方法的實現的話,會出發攔截呼叫,可以自己去做一些處理避免程式崩潰。那在什麼地方做處理呢?NSObject有四個方法可以用來做處理:


// 呼叫不存在的類方法時觸發,預設返回NO,可以加上自己的處理後返回YES
(BOOL)resolveClassMethod:(SEL)sel;
// 呼叫不存在的例項方法時觸發,預設返回NO,可以加上自己的處理後返回YES
(BOOL)resolveInstanceMethod:(SEL)sel;
// 將呼叫的不存在的方法重定向到一個其他宣告瞭這個方法的類裡去,返回那個類的target
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 將呼叫的不存在的方法打包成 NSInvocation 給你,自己處理後呼叫 invokeWithTarget: 方法讓某個類來觸發
- (void)forwardInvocation:(NSInvocation *)anInvocation;

假設我們成功攔截下來了,那我們可以做什麼處理呢?這個其實就是根據具體情況看你要怎麼處理了。但這裡有一個久聞其名的東西就可以派上用場了:動態新增方法。

我們知道OC是動態的,也就是說很多東西它不是在編譯時去判斷,而是在執行時去處理的,這其實帶來了很大的靈活性,比如這裡我們對於一些不存在的方法的呼叫,就可以動態去新增上一個方法來代替我們要呼叫的方法。

比如我們新增一個Button,點選事件我們關聯到一個沒有具體實現的例項方法,這種情況下如果不做任何處理那麼點選Button後一定會崩潰,但是如果我們攔截並動態新增一個方法來報警,就不會崩潰了:


@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;
- (void)notHas;// 要呼叫的例項方法,沒有具體實現
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 新增按鈕
self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
[self.logBtn setTitle:@"測 試" forState:UIControlStateNormal];
[self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
// 新增沒有實現的點選事件
[self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logBtn];
}
// 攔截對不存在的方法的呼叫
(BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"notFind!");
// 給本類動態新增一個方法
if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "[email protected]:*");
}
// 注意要返回YES
return YES;
}
// 要動態新增的方法,這是一個C方法
void runAddMethod(id self, SEL _cmd, NSString *string) {
NSLog(@"動態新增一個方法來提示");
}

按照上面的處理,點選按鈕後就不會崩潰,而是轉到了對 runAddMethod 方法的呼叫。其實更明確地說,應該是重現了對要呼叫的方法的實現,將原本要呼叫的方法的實現,改為了一個新的實現。class_addMethod 方法的第二個引數是要重寫的方法,這裡用的就是傳進來的引數sel,第三個引數就是重寫後的實現。第四個引數是方法的簽名。

關聯物件

什麼叫關聯物件?說通俗一點,我們都知道用Category類別可以給一些已經存在的,比如系統的類新增方法,但是不能新增新屬性,那怎麼新增屬性呢?一種直接的方法是繼承,但如果只是為了新增一個屬性就去做一次繼承,還是有點重,這時候,就可以用關聯物件的方法。

有兩個相關的方法:

objc_setAssociatedObject 方法用來給類關聯一個屬性;
objc_getAssociatedObject 方法用來獲取之前關聯的屬性。

比如說給自己這個類關聯一個字串:


// 關聯物件
static char associatedObjectKey;
objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要關聯的字串物件內容", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey);
NSLog(@"關聯物件:%@", theString);

我們先給self關聯了一個字串內容,然後通過get方法獲取了關聯的字串內容,並輸出。

從程式碼中其實也可以猜到各個引數的意思,self的引數就是要處理的類;associatedObjectKey 的引數其實就類似於key,用來標識區分你要關聯的這個物件;第三個引數是要關聯的物件;第四個引數是關聯的策略,用命名就可以看出來全是在新增@property屬性時用到的一些修飾符,有五種策略:


enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403 
};

熟悉@property屬性修飾符的應該能直接明白了,不熟悉的可以看這篇文章:傳送門:iOS中assign、retain、copy、weak、strong的區別以及nonatomic的含義

當然,你也可以和類別一起用,建立兩個方法用來關聯和獲取物件,比如下面這樣:


//新增關聯物件
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//獲取關聯物件
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}

這樣就既能通過Category類別來新增方法,用一起順便提供了對屬性的新增了。

以上是對Runtime的一點淺薄的理解和使用,Runtime的天地應該是很廣闊的,也能挖出很多高階的使用方法來,對於理解OC的執行機制是很有幫助的。

原始碼下載:http://xiazai.jb51.net/201703/yuanma/RuntimeDemo-master(jb51.net).rar

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支援!

您可能感興趣的文章:

iOS runtime forwardInvocation詳解及整理iOS runtime知識梳理iOS使用runtime修改文字框(TextField)的佔位文字顏色總結iOS中runtime的使用IOS 中runtime使用方法整理

相關文章

IOS開發 最新文章