前面幾篇基本介紹了runtime中的大部分功能蚓耽,包括對(duì)類與對(duì)象、成員變量與屬性旋炒、方法與消息步悠、分類與協(xié)議的處理。runtime大部分的功能都是圍繞這幾點(diǎn)來實(shí)現(xiàn)的瘫镇。
本章的內(nèi)容并不算重點(diǎn)鼎兽,主要針對(duì)前文中對(duì)Objective-C Runtime Reference內(nèi)容遺漏的地方做些補(bǔ)充。當(dāng)然這并不能包含所有的內(nèi)容铣除。runtime還有許多內(nèi)容谚咬,需要讀者去研究發(fā)現(xiàn)。
super
在Objective-C中尚粘,如果我們需要在類的方法中調(diào)用父類的方法時(shí)择卦,通常都會(huì)用到super,如下所示:
@interface MyViewController: UIViewController
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do something
...
}
@end
如何使用super我們都知道。現(xiàn)在的問題是秉继,它是如何工作的呢祈噪?
首先我們需要知道的是super與self不同。self是類的一個(gè)隱藏參數(shù)秕噪,每個(gè)方法的實(shí)現(xiàn)的第一個(gè)參數(shù)即為self钳降。而super并不是隱藏參數(shù),它實(shí)際上只是一個(gè)”編譯器標(biāo)示符”腌巾,它負(fù)責(zé)告訴編譯器遂填,當(dāng)調(diào)用viewDidLoad方法時(shí),去調(diào)用父類的方法澈蝙,而不是本類中的方法吓坚。而它實(shí)際上與self指向的是相同的消息接收者。為了理解這一點(diǎn)灯荧,我們先來看看super的定義:
struct objc_super { id receiver; Class superClass; };
這個(gè)結(jié)構(gòu)體有兩個(gè)成員:
- receiver:即消息的實(shí)際接收者
- superClass:指針當(dāng)前類的父類
當(dāng)我們使用super來接收消息時(shí)礁击,編譯器會(huì)生成一個(gè)objc_super結(jié)構(gòu)體。就上面的例子而言逗载,這個(gè)結(jié)構(gòu)體的receiver就是MyViewController對(duì)象哆窿,與self相同;superClass指向MyViewController的父類UIViewController厉斟。
接下來挚躯,發(fā)送消息時(shí),不是調(diào)用objc_msgSend函數(shù)擦秽,而是調(diào)用objc_msgSendSuper函數(shù)码荔,其聲明如下:
id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );
該函數(shù)第一個(gè)參數(shù)即為前面生成的objc_super結(jié)構(gòu)體,第二個(gè)參數(shù)是方法的selector感挥。該函數(shù)實(shí)際的操作是:從objc_super結(jié)構(gòu)體指向的superClass的方法列表開始查找viewDidLoad的selector缩搅,找到后以objc->receiver去調(diào)用這個(gè)selector,而此時(shí)的操作流程就是如下方式了
objc_msgSend(objc_super->receiver, @selector(viewDidLoad))
由于objc_super->receiver就是self本身触幼,所以該方法實(shí)際與下面這個(gè)調(diào)用是相同的:
objc_msgSend(self, @selector(viewDidLoad))
為了便于理解硼瓣,我們看以下實(shí)例:
@interface MyClass : NSObject
@end
@implementation MyClass
- (void)test {
NSLog(@"self class: %@", self.class);
NSLog(@"super class: %@", super.class);
}
@end
調(diào)用MyClass的test方法后,其輸出是:
2016-04-22 10:10:01.111 LSRuntimeCategory[1010:46543] self class: MyClass
2016-04-22 10:10:01.112 LSRuntimeCategory[1010:46543] super class: MyClass
從上例中可以看到域蜗,兩者的輸出都是MyClass巨双。大家可以自行用上面介紹的內(nèi)容來梳理一下。
庫(kù)相關(guān)操作
庫(kù)相關(guān)的操作主要是用于獲取由系統(tǒng)提供的庫(kù)相關(guān)的信息霉祸,主要包含以下函數(shù):
// 獲取所有加載的Objective-C框架和動(dòng)態(tài)庫(kù)的名稱
const char ** objc_copyImageNames ( unsigned int *outCount );
// 獲取指定類所在動(dòng)態(tài)庫(kù)
const char * class_getImageName ( Class cls );
// 獲取指定庫(kù)或框架中所有類的類名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );
通過這幾個(gè)函數(shù)筑累,我們可以了解到某個(gè)類所有的庫(kù),以及某個(gè)庫(kù)中包含哪些類丝蹭。如下代碼所示:
NSLog(@"獲取指定類所在動(dòng)態(tài)庫(kù)");
NSLog(@"UIView's Framework: %s", class_getImageName(NSClassFromString(@"UIView")));
NSLog(@"獲取指定庫(kù)或框架中所有類的類名");
const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"class name: %s", classes[i]);
}
其輸出結(jié)果如下:
2016-04-22 10:14:56.575 LSRuntimeCategory[1025:50047] 獲取指定類所在動(dòng)態(tài)庫(kù)
2016-04-22 10:14:56.577 LSRuntimeCategory[1025:50047] UIView's Framework: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework/UIKit
2016-04-22 10:14:56.577 LSRuntimeCategory[1025:50047] 獲取指定庫(kù)或框架中所有類的類名
2016-04-22 10:14:56.577 LSRuntimeCategory[1025:50047] class name: UIKeyboardUISettings
2016-04-22 10:14:56.578 LSRuntimeCategory[1025:50047] class name: _UIPickerViewTopFrame
2016-04-22 10:14:56.578 LSRuntimeCategory[1025:50047] class name: _UIOnePartImageView
2016-04-22 10:14:56.578 LSRuntimeCategory[1025:50047] class name: _UIPickerViewSelectionBar
2016-04-22 10:14:56.578 LSRuntimeCategory[1025:50047] class name: _UIPickerWheelView
2016-04-22 10:14:56.578 LSRuntimeCategory[1025:50047] class name: _UIPickerViewTestParameters
2016-04-22 10:14:56.579 LSRuntimeCategory[1025:50047] class name: UIPickerView
2016-04-22 10:14:56.579 LSRuntimeCategory[1025:50047] class name: _UIViewCALayerKeyValueMapper
2016-04-22 10:14:56.579 LSRuntimeCategory[1025:50047] class name: _UINavigationParallaxTransition
2016-04-22 10:14:56.579 LSRuntimeCategory[1025:50047] class name: _UINavigationInteractiveTransitionBase
2016-04-22 10:14:56.580 LSRuntimeCategory[1025:50047] class name: _UINavigationInteractiveTransition
2016-04-22 10:14:56.602 LSRuntimeCategory[1025:50047] class name: _UIParallaxDimmingView
2016-04-22 10:14:56.602 LSRuntimeCategory[1025:50047] class name: UIKeyboardEmojiCollectionInputView
2016-04-22 10:14:56.602 LSRuntimeCategory[1025:50047] class name: UIKeyboardEmojiCollectionView
2016-04-22 10:14:56.602 LSRuntimeCategory[1025:50047] class name: UIKBEmojiSnapshotSizingView
2016-04-22 10:14:56.603 LSRuntimeCategory[1025:50047] class name: _UIBackgroundTaskInfo
2016-04-22 10:14:56.603 LSRuntimeCategory[1025:50047] class name: _UIBackgroundHitTestWindow
2016-04-22 10:14:56.603 LSRuntimeCategory[1025:50047] class name: _EventPlaybackInfo
2016-04-22 10:14:56.603 LSRuntimeCategory[1025:50047] class name: _UICachedSceneProperties
......
塊操作
我們都知道block給我們帶到極大的方便慢宗,蘋果也不斷提供一些使用block的新的API殖氏。同時(shí)畏纲,蘋果在runtime中也提供了一些函數(shù)來支持針對(duì)block的操作磷瘤,這些函數(shù)包括:
// 創(chuàng)建一個(gè)指針函數(shù)的指針桨吊,該函數(shù)調(diào)用時(shí)會(huì)調(diào)用特定的block
IMP imp_implementationWithBlock ( id block );
// 返回與IMP(使用imp_implementationWithBlock創(chuàng)建的)相關(guān)的block
id imp_getBlock ( IMP anImp );
// 解除block與IMP(使用imp_implementationWithBlock創(chuàng)建的)的關(guān)聯(lián)關(guān)系,并釋放block的拷貝
BOOL imp_removeBlock ( IMP anImp );
- imp_implementationWithBlock函數(shù):參數(shù)block的簽名必須是method_return_type ^(id self, method_args …)形式的缅茉。該方法能讓我們使用block作為IMP嘴脾。如下代碼所示:
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 測(cè)試代碼
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
2016-04-22 10:20:42.009 LSRuntimeCategory[1046:53309] hello world!
弱引用操作
// 加載弱引用指針引用的對(duì)象并返回
id objc_loadWeak ( id *location );
// 存儲(chǔ)__weak變量的新值
id objc_storeWeak ( id *location, id obj );
- objc_loadWeak函數(shù):該函數(shù)加載一個(gè)弱指針引用的對(duì)象,并在對(duì)其做retain和autoreleasing操作后返回它蔬墩。這樣译打,對(duì)象就可以在調(diào)用者使用它時(shí)保持足夠長(zhǎng)的生命周期。該函數(shù)典型的用法是在任何有使用__weak變量的表達(dá)式中使用拇颅。
- objc_storeWeak函數(shù):該函數(shù)的典型用法是用于__weak變量做為賦值對(duì)象時(shí)奏司。
這兩個(gè)函數(shù)的具體實(shí)施在此不舉例,有興趣的小伙伴可以參考《Objective-C高級(jí)編程:iOS與OS X多線程和內(nèi)存管理》中對(duì)__weak實(shí)現(xiàn)的介紹樟插。
宏定義
在runtime中韵洋,還定義了一些宏定義供我們使用,有些值我們會(huì)經(jīng)常用到黄锤,如表示BOOL值的YES/NO搪缨;而有些值不常用,如OBJC_ROOT_CLASS鸵熟。在此我們做一個(gè)簡(jiǎn)單的介紹勉吻。
布爾值
#define YES (BOOL)1
#define NO (BOOL)0
這兩個(gè)宏定義定義了表示布爾值的常量,需要注意的是YES的值是1旅赢,而不是非0值。
空值
#define nil __DARWIN_NULL
#define Nil __DARWIN_NULL
其中nil用于空的實(shí)例對(duì)象惑惶,而Nil用于空類對(duì)象煮盼。
分發(fā)函數(shù)原型
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
該宏指明分發(fā)函數(shù)是否必須轉(zhuǎn)換為合適的函數(shù)指針類型。當(dāng)值為0時(shí)带污,必須進(jìn)行轉(zhuǎn)換
Objective-C根類
#define OBJC_ROOT_CLASS
如果我們定義了一個(gè)Objective-C根類僵控,則編譯器會(huì)報(bào)錯(cuò),指明我們定義的類沒有指定一個(gè)基類鱼冀。這種情況下报破,我們就可以使用這個(gè)宏定義來避過這個(gè)編譯錯(cuò)誤。該宏在iOS 7.0后可用千绪。
其實(shí)在NSObject的聲明中充易,我們就可以看到這個(gè)宏的身影,如下所示:
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
我們可以參考這種方式來定義我們自己的根類荸型。
局部變量存儲(chǔ)時(shí)長(zhǎng)
#define NS_VALID_UNTIL_END_OF_SCOPE
該宏表明存儲(chǔ)在某些局部變量中的值在優(yōu)化時(shí)不應(yīng)該被編譯器強(qiáng)制釋放盹靴。
我們將局部變量標(biāo)記為id類型或者是指向ObjC對(duì)象類型的指針,以便存儲(chǔ)在這些局部變量中的值在優(yōu)化時(shí)不會(huì)被編譯器強(qiáng)制釋放。相反稿静,這些值會(huì)在變量再次被賦值之前或者局部變量的作用域結(jié)束之前都會(huì)被保存梭冠。
關(guān)聯(lián)對(duì)象行為
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
這幾個(gè)值在前面已介紹過,在此不再重復(fù)改备。
總結(jié)
至此控漠,本系列對(duì)runtime的整理已完結(jié)。當(dāng)然這只是對(duì)runtime的一些基礎(chǔ)知識(shí)的歸納悬钳,力圖起個(gè)拋磚引玉的作用盐捷。還有許多關(guān)于runtime有意思東西還需要讀者自己去探索發(fā)現(xiàn)。