標籤:
前面幾篇基本介紹了runtime中的大部分功能,包括對類與對象、成員變數與屬性、方法與訊息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。
本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏的地方做些補充。當然這並不能包含所有的內容。runtime還有許多內容,需要讀者去研究發現。
super
在Objective-C中,如果我們需要在類的方法中調用父類的方法時,通常都會用到super,如下所示:
@interface MyViewController: UIViewController@end@implementation MyViewController- (void)viewDidLoad { [super viewDidLoad]; // do something ...}@end
如何使用super我們都知道。現在的問題是,它是如何工作的呢?
首先我們需要知道的是super與self不同。self是類的一個隱藏參數,每個方法的實現的第一個參數即為self。而super並不是隱藏參數,它實際上只是一個”編譯器標示符”,它負責告訴編譯器,當調用viewDidLoad方法時,去調用父類的方法,而不是本類中的方法。而它實際上與self指向的是相同的訊息接收者。為了理解這一點,我們先來看看super的定義:
struct objc_super { id receiver; Class superClass; };
這個結構體有兩個成員:
- receiver:即訊息的實際接收者
- superClass:指標當前類的父類
當我們使用super來接收訊息時,編譯器會產生一個objc_super結構體。就上面的例子而言,這個結構體的receiver就是MyViewController對象,與self相同;superClass指向MyViewController的父類UIViewController。
接下來,發送訊息時,不是調用objc_msgSend函數,而是調用objc_msgSendSuper函數,其聲明如下:
id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );
該函數第一個參數即為前面產生的objc_super結構體,第二個參數是方法的selector。該函數實際的操作是:從objc_super結構體指向的superClass的方法列表開始尋找viewDidLoad的selector,找到後以objc->receiver去調用這個selector,而此時的操作流程就是如下方式了
objc_msgSend(objc_super->receiver, @selector(viewDidLoad))
由於objc_super->receiver就是self本身,所以該方法實際與下面這個調用是相同的:
objc_msgSend(self, @selector(viewDidLoad))
為了便於理解,我們看以下執行個體:
@interface MyClass : NSObject@end@implementation MyClass- (void)test { NSLog(@"self class: %@", self.class); NSLog(@"super class: %@", super.class);}@end
調用MyClass的test方法後,其輸出是:
2014-11-08 15:55:03.256 [824:209297] self class: MyClass2014-11-08 15:55:03.256 [824:209297] super class: MyClass
從上例中可以看到,兩者的輸出都是MyClass。大家可以自行用上面介紹的內容來梳理一下。
庫相關操作
庫相關的操作主要是用於擷取由系統提供的庫相關的資訊,主要包含以下函數:
// 擷取所有載入的Objective-C架構和動態庫的名稱const char ** objc_copyImageNames ( unsigned int *outCount );// 擷取指定類所在動態庫const char * class_getImageName ( Class cls );// 擷取指定庫或架構中所有類的類名const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );
通過這幾個函數,我們可以瞭解到某個類所有的庫,以及某個庫中包含哪些類。如下代碼所示:
NSLog(@"擷取指定類所在動態庫");NSLog(@"UIView‘s Framework: %s", class_getImageName(NSClassFromString(@"UIView")));NSLog(@"擷取指定庫或架構中所有類的類名");const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);for (int i = 0; i < outCount; i++) { NSLog(@"class name: %s", classes[i]);}
其輸出結果如下:
2014-11-08 12:57:32.689 [747:184013] 擷取指定類所在動態庫2014-11-08 12:57:32.690 [747:184013] UIView‘s Framework: /System/Library/Frameworks/UIKit.framework/UIKit2014-11-08 12:57:32.690 [747:184013] 擷取指定庫或架構中所有類的類名2014-11-08 12:57:32.691 [747:184013] class name: UIKeyboardPredictiveSettings2014-11-08 12:57:32.691 [747:184013] class name: _UIPickerViewTopFrame2014-11-08 12:57:32.691 [747:184013] class name: _UIOnePartImageView2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewSelectionBar2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerWheelView2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewTestParameters......
塊操作
我們都知道block給我們帶到極大的方便,蘋果也不斷提供一些使用block的新的API。同時,蘋果在runtime中也提供了一些函數來支援針對block的操作,這些函數包括:
// 建立一個指標函數的指標,該函數調用時會調用特定的blockIMP imp_implementationWithBlock ( id block );// 返回與IMP(使用imp_implementationWithBlock建立的)相關的blockid imp_getBlock ( IMP anImp );// 解除block與IMP(使用imp_implementationWithBlock建立的)的關聯關係,並釋放block的拷貝BOOL imp_removeBlock ( IMP anImp );
● imp_implementationWithBlock函數:參數block的簽名必須是method_return_type ^(id self, method_args …)形式的。該方法能讓我們使用block作為IMP。如下代碼所示:
@interface MyRuntimeBlock : NSObject@end@implementation MyRuntimeBlock@end// 測試代碼IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) { NSLog(@"%@", str);});class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "[email protected]:@");MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
輸出結果是
2014-11-09 14:03:19.779 [1172:395446] hello world!
弱引用操作
// 載入弱引用指標引用的對象並返回id objc_loadWeak ( id *location );// 儲存__weak變數的新值id objc_storeWeak ( id *location, id obj );
● objc_loadWeak函數:該函數載入一個弱指標引用的對象,並在對其做retain和autoreleasing操作後返回它。這樣,對象就可以在調用者使用它時保持足夠長的生命週期。該函數典型的用法是在任何有使用__weak變數的運算式中使用。
● objc_storeWeak函數:該函數的典型用法是用於__weak變數做為賦值對象時。
這兩個函數的具體實施在此不舉例,有興趣的小夥伴可以參考《Objective-C進階編程:iOS與OS X多線程和記憶體管理》中對__weak實現的介紹。
宏定義
在runtime中,還定義了一些宏定義供我們使用,有些值我們會經常用到,如表示BOOL值的YES/NO;而有些值不常用,如OBJC_ROOT_CLASS。在此我們做一個簡單的介紹。
布爾值
#define YES (BOOL)1#define NO (BOOL)0
這兩個宏定義定義了表示布爾值的常量,需要注意的是YES的值是1,而不是非0值。
空值
#define nil __DARWIN_NULL#define Nil __DARWIN_NULL
其中nil用於空的執行個體對象,而Nil用於空類對象。
分發函數原型
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
該宏指明分發函數是否必須轉換為合適的函數指標類型。當值為0時,必須進行轉換
Objective-C根類
#define OBJC_ROOT_CLASS
如果我們定義了一個Objective-C根類,則編譯器會報錯,指明我們定義的類沒有指定一個基類。這種情況下,我們就可以使用這個宏定義來避過這個編譯錯誤。該宏在iOS 7.0後可用。
其實在NSObject的聲明中,我們就可以看到這個宏的身影,如下所示:
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)OBJC_ROOT_CLASSOBJC_EXPORT@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY;}
我們可以參考這種方式來定義我們自己的根類。
局部變數儲存時間長度
#define NS_VALID_UNTIL_END_OF_SCOPE
該宏表明儲存在某些局部變數中的值在最佳化時不應該被編譯器強制釋放。
我們將局部變數標記為id類型或者是指向ObjC物件類型的指標,以便儲存在這些局部變數中的值在最佳化時不會被編譯器強制釋放。相反,這些值會在變數再次被賦值之前或者局部變數的範圍結束之前都會被儲存。
關聯對象行為
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403};
這幾個值在前面已介紹過,在此不再重複。
總結
至此,本系列對runtime的整理已完結。當然這隻是對runtime的一些基礎知識的歸納,力圖起個拋磚引玉的作用。還有許多關於runtime有意思東西還需要讀者自己去探索發現。
註:如有不對之處,還請指正,歡迎加QQ好友:1318202110(南峰子)
參考
- Objective-C Runtime Reference
- iOS:Objective-C中Self和Super詳解
- Objective-C的動態特性
http://southpeak.github.io/blog/2014/11/09/objective-c-runtime-yun-xing-shi-zhi-liu-:shi-yi/
Objective-C Runtime 運行時之六:拾遺