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

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

本章的內(nèi)容并不算重點,主要針對前文中對Objective-C Runtime Reference內(nèi)容遺漏的地方做些補充。當(dāng)然這并不能包含所有的內(nèi)容。runtime還有許多內(nèi)容,需要讀者去研究發(fā)現(xiàn)。

super

在Objective-C中,如果我們需要在類的方法中調(diào)用父類的方法時,通常都會用到super,如下所示:

@interface MyViewController: UIViewController



@end

@implementation MyViewController



- (void)viewDidLoad {

    [super viewDidLoad];



    // do something

    ...

}



@end

如何使用super我們都知道?,F(xiàn)在的問題是,它是如何工作的呢?

首先我們需要知道的是super與self不同。self是類的一個隱藏參數(shù),每個方法的實現(xiàn)的第一個參數(shù)即為self。而super并不是隱藏參數(shù),它實際上只是一個”編譯器標(biāo)示符”,它負(fù)責(zé)告訴編譯器,當(dāng)調(diào)用viewDidLoad方法時,去調(diào)用父類的方法,而不是本類中的方法。而它實際上與self指向的是相同的消息接收者。為了理解這一點,我們先來看看super的定義:

struct objc_super { id receiver; Class superClass; };

這個結(jié)構(gòu)體有兩個成員:

  1. receiver:即消息的實際接收者
  1. superClass:指針當(dāng)前類的父類

當(dāng)我們使用super來接收消息時,編譯器會生成一個objc_super結(jié)構(gòu)體。就上面的例子而言,這個結(jié)構(gòu)體的receiver就是MyViewController對象,與self相同;superClass指向MyViewController的父類UIViewController。

接下來,發(fā)送消息時,不是調(diào)用objc_msgSend函數(shù),而是調(diào)用objc_msgSendSuper函數(shù),其聲明如下:

id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );

該函數(shù)第一個參數(shù)即為前面生成的objc_super結(jié)構(gòu)體,第二個參數(shù)是方法的selector。該函數(shù)實際的操作是:從objc_super結(jié)構(gòu)體指向的superClass的方法列表開始查找viewDidLoad的selector,找到后以objc->receiver去調(diào)用這個selector,而此時的操作流程就是如下方式了

objc_msgSend(objc_super->receiver, @selector(viewDidLoad))

由于objc_super->receiver就是self本身,所以該方法實際與下面這個調(diào)用是相同的:

objc_msgSend(self, @selector(viewDidLoad))

為了便于理解,我們看以下實例:

@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)容來梳理一下。

庫相關(guān)操作

庫相關(guān)的操作主要是用于獲取由系統(tǒng)提供的庫相關(guān)的信息,主要包含以下函數(shù):

// 獲取所有加載的Objective-C框架和動態(tài)庫的名稱

const char ** objc_copyImageNames ( unsigned int *outCount );



// 獲取指定類所在動態(tài)庫

const char * class_getImageName ( Class cls );



// 獲取指定庫或框架中所有類的類名

const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );

通過這幾個函數(shù),我們可以了解到某個類所有的庫,以及某個庫中包含哪些類。如下代碼所示:

NSLog(@"獲取指定類所在動態(tài)庫");

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]);
}

其輸出結(jié)果如下:

2016-04-22 10:14:56.575 LSRuntimeCategory[1025:50047] 獲取指定類所在動態(tài)庫
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] 獲取指定庫或框架中所有類的類名
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。同時,蘋果在runtime中也提供了一些函數(shù)來支持針對block的操作,這些函數(shù)包括:

// 創(chuàng)建一個指針函數(shù)的指針,該函數(shù)調(diào)用時會調(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

// 測試代碼
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!

弱引用操作

// 加載弱引用指針引用的對象并返回

id objc_loadWeak ( id *location );



// 存儲__weak變量的新值

id objc_storeWeak ( id *location, id obj );
  • objc_loadWeak函數(shù):該函數(shù)加載一個弱指針引用的對象,并在對其做retain和autoreleasing操作后返回它。這樣,對象就可以在調(diào)用者使用它時保持足夠長的生命周期。該函數(shù)典型的用法是在任何有使用__weak變量的表達(dá)式中使用。
  • objc_storeWeak函數(shù):該函數(shù)的典型用法是用于__weak變量做為賦值對象時。

這兩個函數(shù)的具體實施在此不舉例,有興趣的小伙伴可以參考《Objective-C高級編程:iOS與OS X多線程和內(nèi)存管理》中對__weak實現(xiàn)的介紹。

宏定義

在runtime中,還定義了一些宏定義供我們使用,有些值我們會經(jīng)常用到,如表示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用于空類對象。

分發(fā)函數(shù)原型

#define OBJC_OLD_DISPATCH_PROTOTYPES  1

該宏指明分發(fā)函數(shù)是否必須轉(zhuǎn)換為合適的函數(shù)指針類型。當(dāng)值為0時,必須進(jìn)行轉(zhuǎn)換

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 

該宏表明存儲在某些局部變量中的值在優(yōu)化時不應(yīng)該被編譯器強制釋放。

我們將局部變量標(biāo)記為id類型或者是指向ObjC對象類型的指針,以便存儲在這些局部變量中的值在優(yōu)化時不會被編譯器強制釋放。相反,這些值會在變量再次被賦值之前或者局部變量的作用域結(jié)束之前都會被保存。

關(guān)聯(lián)對象行為

enum {

   OBJC_ASSOCIATION_ASSIGN  = 0,

   OBJC_ASSOCIATION_RETAIN_NONATOMIC  = 1,

   OBJC_ASSOCIATION_COPY_NONATOMIC  = 3,

   OBJC_ASSOCIATION_RETAIN  = 01401,

   OBJC_ASSOCIATION_COPY  = 01403

};

這幾個值在前面已介紹過,在此不再重復(fù)。

總結(jié)

至此,本系列對runtime的整理已完結(jié)。當(dāng)然這只是對runtime的一些基礎(chǔ)知識的歸納,力圖起個拋磚引玉的作用。還有許多關(guān)于runtime有意思東西還需要讀者自己去探索發(fā)現(xiàn)。

參考

Objective-C Runtime Reference

iOS:Objective-C中Self和Super詳解

Objective-C的動態(tài)特性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容