Objective-C Runtime 運行時之六:拾遺

前面幾篇基本介紹了runtime中的大部分功能,包括對類與對象、成員變量與屬性、方法與消息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。html

本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏的地方作些補充。固然這並不能包含全部的內容。runtime還有許多內容,須要讀者去研究發現。ios

super

在Objective-C中,若是咱們須要在類的方法中調用父類的方法時,一般都會用到super,以下所示:git

@interface MyViewController: UIViewController

@end

@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // do something
    ...
}

@end

如何使用super咱們都知道。如今的問題是,它是如何工做的呢?github

首先咱們須要知道的是super與self不一樣。self是類的一個隱藏參數,每一個方法的實現的第一個參數即爲self。而super並非隱藏參數,它實際上只是一個」編譯器標示符」,它負責告訴編譯器,當調用viewDidLoad方法時,去調用父類的方法,而不是本類中的方法。而它實際上與self指向的是相同的消息接收者。爲了理解這一點,咱們先來看看super的定義:objective-c

struct objc_super { id receiver; Class superClass; };

這個結構體有兩個成員:編程

  1. receiver:即消息的實際接收者
  2. superClass:指針當前類的父類

當咱們使用super來接收消息時,編譯器會生成一個objc_super結構體。就上面的例子而言,這個結構體的receiver就是MyViewController對象,與self相同;superClass指向MyViewController的父類UIViewController。多線程

接下來,發送消息時,不是調用objc_msgSend函數,而是調用objc_msgSendSuper函數,其聲明以下:app

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: MyClass
2014-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/UIKit
2014-11-08 12:57:32.690 [747:184013] 獲取指定庫或框架中全部類的類名
2014-11-08 12:57:32.691 [747:184013] class name: UIKeyboardPredictiveSettings
2014-11-08 12:57:32.691 [747:184013] class name: _UIPickerViewTopFrame
2014-11-08 12:57:32.691 [747:184013] class name: _UIOnePartImageView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewSelectionBar
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerWheelView
2014-11-08 12:57:32.692 [747:184013] class name: _UIPickerViewTestParameters
......

塊操做

咱們都知道block給咱們帶到極大的方便,蘋果也不斷提供一些使用block的新的API。同時,蘋果在runtime中也提供了一些函數來支持針對block的操做,這些函數包括:

// 建立一個指針函數的指針,該函數調用時會調用特定的block
IMP imp_implementationWithBlock ( id block );

// 返回與IMP(使用imp_implementationWithBlock建立的)相關的block
id 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, "v@:@");

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_CLASS
OBJC_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(南峯子)

參考

  1. Objective-C Runtime Reference
  2. iOS:Objective-C中Self和Super詳解
  3. Objective-C的動態特性

http://southpeak.github.io/blog/2014/11/09/objective-c-runtime-yun-xing-shi-zhi-liu-:shi-yi/

相關文章
相關標籤/搜索