溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

IOS Object-C中Runtime的示例分析

發(fā)布時(shí)間:2021-07-09 09:19:57 來(lái)源:億速云 閱讀:123 作者:小新 欄目:移動(dòng)開(kāi)發(fā)

小編給大家分享一下IOS Object-C中Runtime的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

什么是Runtime

我們雖然是用Objective-C寫(xiě)的代碼,其實(shí)在運(yùn)行過(guò)程中都會(huì)被轉(zhuǎn)化成C代碼去執(zhí)行。比如說(shuō)OC的方法調(diào)用都會(huì)轉(zhuǎn)成C函數(shù) id objc_msgSend ( id self, SEL op, … ); 而OC中的對(duì)象其實(shí)在Runtime中都會(huì)用結(jié)構(gòu)體來(lái)表示,這個(gè)結(jié)構(gòu)體中包含了類(lèi)名、成員變量列表、方法列表、協(xié)議列表、緩存等。

類(lèi)在Runtime中的表示:

struct objc_class {
 Class isa;//指針,顧名思義,表示是一個(gè)什么,
 //實(shí)例的isa指向類(lèi)對(duì)象,類(lèi)對(duì)象的isa指向元類(lèi)

#if !__OBJC2__
 Class super_class; //指向父類(lèi)
 const char *name; //類(lèi)名
 long version;
 long info;
 long instance_size
 struct objc_ivar_list *ivars //成員變量列表
 struct objc_method_list **methodLists; //方法列表
 struct objc_cache *cache;//緩存
 //一種優(yōu)化,調(diào)用過(guò)的方法存入緩存列表,下次調(diào)用先找緩存
 struct objc_protocol_list *protocols //協(xié)議列表
 #endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

整個(gè)Runtime機(jī)制其實(shí)可以挖的點(diǎn)很多,這里只是簡(jiǎn)單的介紹一些常見(jiàn)的用法,如果將其細(xì)細(xì)解析,相信一定會(huì)對(duì)OC的理解加深幾個(gè)層面。

獲取屬性/方法/協(xié)議列表

最直接的一種用法,就是獲取我們的結(jié)構(gòu)體中存儲(chǔ)的對(duì)象的屬性、方法、協(xié)議等列表,從而獲取其所有這些信息。

要獲取也比較簡(jiǎn)單,但是自己嘗試之前需要注意幾點(diǎn):

一定要自己給類(lèi)加幾個(gè)屬性、方法,遵循一些協(xié)議,否則當(dāng)然是看不到輸出信息的。

要使用這些獲取的方法,需要導(dǎo)入頭文件 #import

#import <objc/runtime.h>

// 輸出類(lèi)的一些信息
- (void)logInfo {
 unsigned int count;// 用于記錄列表內(nèi)的數(shù)量,進(jìn)行循環(huán)輸出

 // 獲取屬性列表
 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]);
 }

 // 獲取協(xié)議列表
 __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]);
 }
}

方法調(diào)用的過(guò)程

調(diào)用方法分為調(diào)用實(shí)例方法和調(diào)用類(lèi)方法,在結(jié)構(gòu)體我們可以看到第一個(gè)就是一個(gè) isa 指針,會(huì)指向類(lèi)對(duì)象或者元類(lèi),什么叫元類(lèi)呢?

每個(gè)實(shí)例對(duì)象有個(gè)isa的指針,他指向?qū)ο蟮念?lèi),而類(lèi)里也有個(gè)isa的指針, 指向meteClass(元類(lèi))。元類(lèi)保存了類(lèi)方法的列表。當(dāng)類(lèi)方法被調(diào)用時(shí),先會(huì)從本身查找類(lèi)方法的實(shí)現(xiàn),如果沒(méi)有,元類(lèi)會(huì)向他父類(lèi)查找該方法。同時(shí)注意的是:元類(lèi)(meteClass)也是類(lèi),它也是對(duì)象。元類(lèi)也有isa指針,它的isa指針最終指向的是一個(gè)根元類(lèi)(root meteClass)。根元類(lèi)的isa指針指向本身,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。

IOS Object-C中Runtime的示例分析

通過(guò)isa,就可以不斷往上方去回溯自己的父類(lèi)等,而方法的調(diào)用也利用了這個(gè)過(guò)程:

  1. 首先,當(dāng)然在對(duì)象自己緩存的方法列表中去找要調(diào)用的方法,找到了就直接執(zhí)行其實(shí)現(xiàn)。

  2. 緩存里沒(méi)找到,就去上面說(shuō)的它的方法列表里找,找到了就執(zhí)行其實(shí)現(xiàn)。

  3. 還沒(méi)找到,說(shuō)明這個(gè)類(lèi)自己沒(méi)有了,就會(huì)通過(guò)isa去向其父類(lèi)里執(zhí)行1、2。

  4. 如果找到了根類(lèi)還沒(méi)找到,那么就是沒(méi)有了,會(huì)轉(zhuǎn)向一個(gè)攔截調(diào)用的方法,我們可以自己在攔截調(diào)用方法里面做一些處理。

  5. 如果沒(méi)有在攔截調(diào)用里做處理,那么就會(huì)報(bào)錯(cuò)崩潰。

以上就是方法調(diào)用的過(guò)程。我們可以看到的是,所謂重寫(xiě)父類(lèi)方法,并不是抹除了父類(lèi)方法,父類(lèi)的方法還是存在的,只是我們?cè)谧宇?lèi)里面找到了就不會(huì)再去父類(lèi)里找了,是這個(gè)層面的“覆蓋”。而我們熟悉的 super 調(diào)用父類(lèi)的方法實(shí)現(xiàn),就是告知要去調(diào)用父類(lèi)實(shí)現(xiàn)的標(biāo)識(shí)。

攔截調(diào)用與動(dòng)態(tài)添加

上面說(shuō)到了攔截調(diào)用,就是在所有地方都找不到方法的實(shí)現(xiàn)的話,會(huì)出發(fā)攔截調(diào)用,可以自己去做一些處理避免程序崩潰。那在什么地方做處理呢?NSObject有四個(gè)方法可以用來(lái)做處理:

// 調(diào)用不存在的類(lèi)方法時(shí)觸發(fā),默認(rèn)返回NO,可以加上自己的處理后返回YES
+ (BOOL)resolveClassMethod:(SEL)sel;

// 調(diào)用不存在的實(shí)例方法時(shí)觸發(fā),默認(rèn)返回NO,可以加上自己的處理后返回YES
+ (BOOL)resolveInstanceMethod:(SEL)sel;

// 將調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類(lèi)里去,返回那個(gè)類(lèi)的target
- (id)forwardingTargetForSelector:(SEL)aSelector;

// 將調(diào)用的不存在的方法打包成 NSInvocation 給你,自己處理后調(diào)用 invokeWithTarget: 方法讓某個(gè)類(lèi)來(lái)觸發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation;

假設(shè)我們成功攔截下來(lái)了,那我們可以做什么處理呢?這個(gè)其實(shí)就是根據(jù)具體情況看你要怎么處理了。但這里有一個(gè)久聞其名的東西就可以派上用場(chǎng)了:動(dòng)態(tài)添加方法。

我們知道OC是動(dòng)態(tài)的,也就是說(shuō)很多東西它不是在編譯時(shí)去判斷,而是在運(yùn)行時(shí)去處理的,這其實(shí)帶來(lái)了很大的靈活性,比如這里我們對(duì)于一些不存在的方法的調(diào)用,就可以動(dòng)態(tài)去添加上一個(gè)方法來(lái)代替我們要調(diào)用的方法。

比如我們添加一個(gè)Button,點(diǎn)擊事件我們關(guān)聯(lián)到一個(gè)沒(méi)有具體實(shí)現(xiàn)的實(shí)例方法,這種情況下如果不做任何處理那么點(diǎn)擊Button后一定會(huì)崩潰,但是如果我們攔截并動(dòng)態(tài)添加一個(gè)方法來(lái)報(bào)警,就不會(huì)崩潰了:

@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;

- (void)notHas;// 要調(diào)用的實(shí)例方法,沒(méi)有具體實(shí)現(xiàn)
@end

- (void)viewDidLoad {
 [super viewDidLoad];

 // 添加按鈕
 self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
 [self.logBtn setTitle:@"測(cè) 試" forState:UIControlStateNormal];
 [self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
 // 添加沒(méi)有實(shí)現(xiàn)的點(diǎn)擊事件
 [self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
 [self.view addSubview:self.logBtn];

}

// 攔截對(duì)不存在的方法的調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
 NSLog(@"notFind!");
 // 給本類(lèi)動(dòng)態(tài)添加一個(gè)方法
 if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
 class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
 }
 // 注意要返回YES
 return YES;
}

// 要?jiǎng)討B(tài)添加的方法,這是一個(gè)C方法
 void runAddMethod(id self, SEL _cmd, NSString *string) {
 NSLog(@"動(dòng)態(tài)添加一個(gè)方法來(lái)提示");
}

按照上面的處理,點(diǎn)擊按鈕后就不會(huì)崩潰,而是轉(zhuǎn)到了對(duì) runAddMethod 方法的調(diào)用。其實(shí)更明確地說(shuō),應(yīng)該是重現(xiàn)了對(duì)要調(diào)用的方法的實(shí)現(xiàn),將原本要調(diào)用的方法的實(shí)現(xiàn),改為了一個(gè)新的實(shí)現(xiàn)。class_addMethod 方法的第二個(gè)參數(shù)是要重寫(xiě)的方法,這里用的就是傳進(jìn)來(lái)的參數(shù)sel,第三個(gè)參數(shù)就是重寫(xiě)后的實(shí)現(xiàn)。第四個(gè)參數(shù)是方法的簽名。

關(guān)聯(lián)對(duì)象

什么叫關(guān)聯(lián)對(duì)象?說(shuō)通俗一點(diǎn),我們都知道用Category類(lèi)別可以給一些已經(jīng)存在的,比如系統(tǒng)的類(lèi)添加方法,但是不能添加新屬性,那怎么添加屬性呢?一種直接的方法是繼承,但如果只是為了添加一個(gè)屬性就去做一次繼承,還是有點(diǎn)重,這時(shí)候,就可以用關(guān)聯(lián)對(duì)象的方法。

有兩個(gè)相關(guān)的方法:

objc_setAssociatedObject 方法用來(lái)給類(lèi)關(guān)聯(lián)一個(gè)屬性;
objc_getAssociatedObject 方法用來(lái)獲取之前關(guān)聯(lián)的屬性。

比如說(shuō)給自己這個(gè)類(lèi)關(guān)聯(lián)一個(gè)字符串:

 // 關(guān)聯(lián)對(duì)象
 static char associatedObjectKey;
 objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要關(guān)聯(lián)的字符串對(duì)象內(nèi)容", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey);
 NSLog(@"關(guān)聯(lián)對(duì)象:%@", theString);

我們先給self關(guān)聯(lián)了一個(gè)字符串內(nèi)容,然后通過(guò)get方法獲取了關(guān)聯(lián)的字符串內(nèi)容,并輸出。

從代碼中其實(shí)也可以猜到各個(gè)參數(shù)的意思,self的參數(shù)就是要處理的類(lèi);associatedObjectKey 的參數(shù)其實(shí)就類(lèi)似于key,用來(lái)標(biāo)識(shí)區(qū)分你要關(guān)聯(lián)的這個(gè)對(duì)象;第三個(gè)參數(shù)是要關(guān)聯(lián)的對(duì)象;第四個(gè)參數(shù)是關(guān)聯(lián)的策略,用命名就可以看出來(lái)全是在添加@property屬性時(shí)用到的一些修飾符,有五種策略:

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

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

當(dāng)然,你也可以和類(lèi)別一起用,創(chuàng)建兩個(gè)方法用來(lái)關(guān)聯(lián)和獲取對(duì)象,比如下面這樣:

//添加關(guān)聯(lián)對(duì)象
- (void)addAssociatedObject:(id)object{
 objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

//獲取關(guān)聯(lián)對(duì)象
- (id)getAssociatedObject{
 return objc_getAssociatedObject(self, _cmd);
}

這樣就既能通過(guò)Category類(lèi)別來(lái)添加方法,用一起順便提供了對(duì)屬性的添加了。

以上是“IOS Object-C中Runtime的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI