runtime淺談(二)概念與補充

1、Runtime

      Runtime有兩種,一個 Modern Runtime 和一個 Legacy Runtime。

      Modern Runtime(現代 Runtime): 覆蓋了64位的Mac OS X 應用 和所有 iOS 應用,

      Legacy Runtime(過時 Runtime): 是早期用來給32位 Mac OS X Apps 用.


2、Basic types of Methods

      Instance Method 和 Class Method。

      instance method (實例方法):就是帶“-”號的,需要實例化才能用的, 

      Class Method (類方法): 就是帶“+”號的,類似於靜態方法可以直接調用。

      方法(Methods)和 C 的函數很像,是一組代碼,執行一個小的任務。


3 、Selector

     Selector 事實上是一個 C 數據的結構體,表示的是一個方法。

<pre name="code" class="objc">typedef struct objc_selector  *SEL;
SEL sel = @select(title);


消息:
[target getTitleForObject:obj];

       消息是方括號 ‘[]’ 中的那部分,由你要向其發送消息的對象(target),你想要在上面執行的方法(method)還有你發送的參數(arguments)組成。Objective-C 的消息和 C 函數調用是不同的。事實上,你向一個對象發送消息並不意味着它會執行它。Object (對象)會檢查消息的發送者,基於這點再決定是執行一個不同的方法還是轉發消息到另一個目標對象上。


4、class 在runtime(一)已經提到。

<span style="font-size:12px;">
typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;</span>

       所有的 objc_object 對象結構體都有一個 isa 指針,這個 isa 指向它所屬的類,在運行時就靠這個指針來檢測這個對象是否可以響應一個 selector。我們看到最後有一個 id 指針。這個指針其實就只是用來代表一個 ObjC 對象,當你拿到一個 id 指針之後,就可以獲取這個對象的類,並且可以檢測其是否響應一個 selector。這就是對一個 delegate 常用的調用方式。

附:LLVM/Clang 的文檔對 Blocks 的定義:

<span style="font-size:14px;"><span style="font-size:12px;"></span>
<span style="font-size:14px;">struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
 unsigned long int reserved; // NULL
     unsigned long int size;  // sizeof(struct Block_literal_1)
 // optional helper functions
     void (*copy_helper)(void *dst, void *src);
     void (*dispose_helper)(void *src); 
    } *descriptor;
    // imported variables
};</span><span style="font-size:12px;">
</span></span>

可以看到一個 block 是被設計成一個對象的,擁有一個 isa 指針,所以你可以對一個 block 使用 retain, release, copy 這些方法。


5、IMP (Method Implementations)

      typedef id (*IMP)(id self,SEL _cmd,...); 

       IMP 只想方法實現的函數指針,這是由編譯器生成的,當你發起一個 ObjC 消息之後,最終它會執行的那個代碼,就是由這個函數指針指定的。


6、Objective-C Classes

     ObjC 類同時也是一個對象,爲了處理類和對象的關係,runtime 庫創建了一種叫做 元類(Meta Class)的東西。

例:

<span style="font-size:14px;"><span style="font-size:12px;">[NSObject alloc];</span></span>

      你事實上是把這個消息發給了一個類對象(Class Object),runtime通過創建Meta Classes 處理這些。因此這個類對象需要的是一個 Meta Class 的實例,一個 Meta Class 同時也是root MetaClass 的實例。當你繼承了 NSObject 成爲其子類的時候,你的類指針就會指向 NSObject 爲其父類。但是 Meta Class 不太一樣,所有的 Meta Class 都指向root Meta Class 作爲自己的superclass。一個 Meta Class 持有所有能響應的方法。所以當 [NSObject alloc] 這條消息發出的時候,objc_msgSend() 這個方法會去 NSObject 它的 Meta Class 裏面去查找是否有響應這個 selector 的方法,然後對 NSObject 這個類對象執行方法調用。

        對於完全的 O-C 來說,類也是個對象,類是類類型(MetaClass)的實例,所以類的類型描述就是 meta class)。


7、爲什麼要繼承apple Classes

     當我們繼承自 NSObject 然後開始寫一些代碼,享受了很多繼承自蘋果的類所帶來的便利,有一件事你從未意識到,就是我們的對象被設置爲使用 Objective-C 的 runtime

<span style="font-size:14px;"><span style="font-size:12px;">MyObject *object = [[MyObject alloc] init];</span></span>

       這個語句用來初始化一個實例,類似於 C++ 的 new 關鍵字。這個語句首先會執行 MyObject 這個類的 +alloc 方法,Apple 的官方文檔是這樣說的:

                   The isa instance variable of the new instance is initialized to a data structure that describes the class; memory for all other instance variables is set to 0.

                   新建的實例中,isa 成員變量會變初始化成一個數據結構體,用來描述所指向的類。其他的成員變量的內存會被置爲0.

       所以繼承 Apple 的類我們不僅是獲得了很多很好用的屬性,也繼承了能在內存中輕鬆分配內存的能力和在內存中創建滿足 runtime 期望的對象結構(設置 isa 指針指向我們的類)。


8、Class Cache(objc_cache *cache)

runtime 裏面有一個指針叫 objc_cache *cache,這是用來緩存方法調用的。每次你調用過一個方法,之後,這個方法就會被存到這個 cache 列表裏面去,下次調用的時候 runtime 會優先去 cache 裏面查找,提高了調用的效率。

<span style="font-size:14px;">MyObject *obj = [[MyObject alloc] init]; // MyObject 的父類是 NSObject</span>

<span style="font-size:14px;">@implementation MyObject
-(id)init {
    if(self = [super init]){
        [self setVarA:@”blah”];
    }
    return self;
}
@end</span>

這段代碼是這樣執行的:

  1. >  [MyObject alloc] 先被執行。但是由於 MyObject 這個類沒有 +alloc 這個方法,於是去父類 NSObject 查找。
  2. >  檢測 NSObject 是否響應 +alloc 方法,發現響應,於是檢測 MyObject 類,根據其所需的內存空間大小開始分配內存空間,然後把 isa 指針指向 MyObject 類。那麼 +alloc 就被加進 cache 列表裏面了。
  3. >  完了執行 -init 方法,因爲 MyObject 響應該方法,直接加入 cache。
  4. >  執行 self = [super init] 語句。這裏直接通過 super 關鍵字調用父類的 init 方法,確保父類初始化成功,然後再執行自己的初始化邏輯。

       這就是一個很簡單的初始化過程,但是,ObjC 特性允許你的 alloc 和 init 返回的值不同,也就是說,你可以在你的 init 函數裏面做一些很複雜的初始化操作,但是返回出去一個簡單的對象,這就隱藏了類的複雜性。

<span style="font-size:14px;"><span style="font-size:12px;">#import < Foundation/Foundation.h>

@interface MyObject : NSObject
{
 NSString *aString;
}

@property(retain) NSString *aString;

@end

@implementation MyObject

-(id)init
{
 if (self = [super init]) {
  [self setAString:nil];
 }
 return self;
}

@synthesize aString;

@end</span></span>

<span style="font-size:14px;"><span style="font-size:12px;">nt main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

 id obj1 = [NSMutableArray alloc];
 id obj2 = [[NSMutableArray alloc] init];

 id obj3 = [NSArray alloc];
 id obj4 = [[NSArray alloc] initWithObjects:@"Hello",nil];

 NSLog(@"obj1 class is %@",NSStringFromClass([obj1 class]));
 NSLog(@"obj2 class is %@",NSStringFromClass([obj2 class]));

 NSLog(@"obj3 class is %@",NSStringFromClass([obj3 class]));
 NSLog(@"obj4 class is %@",NSStringFromClass([obj4 class]));

 id obj5 = [MyObject alloc];
 id obj6 = [[MyObject alloc] init];

 NSLog(@"obj5 class is %@",NSStringFromClass([obj5 class]));
 NSLog(@"obj6 class is %@",NSStringFromClass([obj6 class]));

 [pool drain];
    return 0;
}</span></span>


輸出結果:

<span style="font-size:14px;"><span style="font-size:12px;">obj1 class is __NSPlaceholderArray
obj2 class is NSCFArray
obj3 class is __NSPlaceholderArray
obj4 class is NSCFArray
obj5 class is MyObject
obj6 class is MyObject</span></span>

       這是因爲 ObjC 是允許運行 +alloc 返回一個特定的類,而 init 方法又返回一個不同的類的。可以看到 NSMutableArray 是對普通數組的封裝,內部實現是複雜的,但是對外隱藏了複雜性。


9、objc_msgSend 

在編譯的時候,你定義的方法比如

-<span style="font-size:14px;">(int)doComputeWithNum:(int)aNum</span> 

會編譯成:

int aClass_doComputeWithNum(aClass *self,SEL _cmd,int aNum) 

然後由 runtime 去調用指向你的這個方法的函數指針。那麼之前我們說你發起消息其實不是對方法的直接調用,其實 Cocoa 還是提供了可以直接調用的方法的

<span style="font-size:14px;">// 首先定義一個 C 語言的函數指針
int (computeNum *)(id,SEL,int);</span>

<span style="font-size:14px;">// 使用 methodForSelector 方法獲取對應與該 selector 的杉樹指針,跟 objc_msgSend 方法拿到的是一樣的
// **methodForSelector 這個方法是 Cocoa 提供的,不是 ObjC runtime 庫提供的**
computeNum = (int (*)(id,SEL,int))[target methodForSelector:@selector(doComputeWithNum:)];</span>

<span style="font-size:14px;">// 現在可以直接調用該函數了,跟調用 C 函數是一樣的
computeNum(obj,@selector(doComputeWithNum:),aNum); </span>

如果你需要的話,你可以通過這種方式你來確保這個方法一定會被調用。


        
相關鏈接:http://www.justinyan.me/post/1624






發佈了24 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章