Objective-C RunTime機制(1)

Objective-C 是面相運行時的語言(runtime oriented language),就是說它會儘可能的把編譯和鏈接時要執行的邏輯延遲到運行時。這就給了你很大的靈活性,你可以按需要把消息重定向給合適的對象,你甚 至可以交換方法的實現,等等(譯註:在 Objective-C 中調用一個對象的方法可以看成向一個對象發送消息, Method Swizzling 具體實現可以參看 jrswizzle )。這就需要使用 runtime。Runtime系統來動態創建類和對象,進行消息發送和轉發。

OC 類處理
參考 :
http://blog.csdn.net/lpstudy/article/details/21954711?utm_source=tuicool&utm_medium=referral

runtime是開源的。可以去down:http://opensource.apple.com/tarballs/objc4/objc4-437.1.tar.gz
我們可以Object.h從看對象的定義的實現 其實是一個結構體


/*
    Object.h
    Copyright 1988-1996 NeXT Software, Inc.

    DEFINED AS: A common class
    HEADER FILES:   <objc/Object.h>
    #if __OBJC2__

@interface Object
{
    Class isa;  /* A pointer to the instance's class structure */
}

+class;
-(BOOL) isEqual:anObject;

@end

#else

@interface Object
{
    Class isa;  /* A pointer to the instance's class structure */
}

/* Initializing classes and instances */


*/

isa 是一個指向實例的類的指針,
然後接下來使一些方法,包括

/* Creating, copying, and freeing instances */
/* Identifying classes */
/* Identifying and comparing instances */
/* Testing inheritance relationships */

等還有指向父類的指針
其實Class 中的 *isa 本身就是一個指針類型

在 Objective-C 中的對象的一個重要的特性是,你可以向它們發送消息:當你向一個 Objective-C 的對象發送消息的時候,runtime 沿着對象的 isa 指針找到了這個對象的 Class結構體。 Class 結構體中包含了一個這個類的方法列表和一個指向父類的指針,用於查找繼承的方法。

Class本身就是一個指針類型,是一個指向objc_class結構體指針。

lobjc_class是什麼?就是類的定義
typedef  struct objc_class *Class;
struct objc_class

  {

      struct objc_class* isa;

      struct objc_class*super_class;

      const char*name;

      long version;

      long info;

      long instance_size;

      struct objc_ivar_list* ivars;

      struct objc_method_list** methodLists;

      struct objc_cache* cache;

      struct objc_protocol_list* protocols;

  };

同樣的, struct objc_class 中讓我們在 Class 上調用一個方法,Class 的 isa 指針必須指向一個 Class 結構體,並且那個 Class 結構體必須包含我們可以在那個 Class 上調用的方法的列表
這就引出了 meta-class 的定義:meta-class 是 Class 對象的類(the meta-class is the class for a Class object)。

cache用來緩存經常訪問的方法,它指向objc_cache結構體,後面會重點講到。
protocols表示類遵循哪些協議。
ivars表示多個成員變量,它指向objc_ivar_list結構體。在runtime.h可以看到它的定義

struct objc_ivar_list {
  int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
  /* variable length structure */
  struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}

methodLists表示方法列表,它指向objc_method_list結構體的二級指針,可以動態修改*methodLists的值來添加成員方法,也是Category實現原理,同樣也解釋Category不能添加屬性的原因。在runtime.h可以看到它的定義:

struct objc_method_list {
  struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

  int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
  /* variable length structure */
  struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

同理,objc_method_list也是一個鏈表,存儲多個objc_method,而objc_method結構體存儲類的某個方法的信息。

Ivar
Ivar其實就是一個指向objc_ivar結構體指針,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息。
Ivar表示類中的實例變量,在runtime.h文件中找到它的定義:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

Cache

顧名思義,Cache主要用來緩存,那它緩存什麼呢?我們先在runtime.h文件看看它的定義:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

ache其實就是一個存儲Method的鏈表,主要是爲了優化方法調用的性能。當對象receiver調用方法message時,首先根據對象receiver的isa指針查找到它對應的類,然後在類的methodLists中搜索方法,如果沒有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調用方法。如果沒有找到,有可能消息轉發,也可能忽略它。但這樣查找方式效率太低,因爲往往一個類大概只有20%的方法經常被調用,佔總調用次數的80%。所以使用Cache來緩存經常調用的方法,當調用方法時,優先在Cache查找,如果沒有找到,再到methodLists查找。
這裏寫圖片描述
meta-class 是 Class 對象的類。每個 Class 都有個不同的自己的 meta-class(因此每個 Class 都可以有一個自己不同的方法列表)。也就是說每個類的 Class 不完全相同。

meta-class 總是會保證 Class 對象會有從基類繼承的所有的的實例和類方法,加上之後繼承的類方法。如從 NSObject 繼承的類,就意味着在所有的 Class(和 meta-class)對象中定義了所有從 NSObject 繼承的實例和協議方法。

所有的 meta-class 使用基類的 meta-class(NSObject 的 meta-class 用於繼承自 NSObject 的類)作爲他們自己的類,包括在運行時自己定義的基礎的 meta-class。

簡單來說:
- 當你向一個對象發送消息,就在那個對象的方法列表中查找那個消息( 就是 -(typename) methodname 。。。 聲明的實力方法
- 當你想一個類發送消息,就再那個類的 meta-class 中查找那個消息。( + (typename) methodname 。。。聲明的類方法。
- 並且meta-class 是必須的,因爲它爲一個 Class 存儲類方法。每個類都必須有一個唯一的 meta-class,因爲每個 Class 都有一個可能不一樣的類方法。
和 Class 以 super_class 指針指向它的父類的方法一樣,meta-class 以 super_class 指針指向 Class 的 super_class 的 meta-class。(譯註:這句話有點繞,就是 super-class 一個指向 Class 的父類,一個指向 meta-class 的父類。Class 是一般對象的類型,meta-class 是 Class 的類型。)

消息機制
參考:
http://www.cocoachina.com/bbs/read.php?tid=189505
在runtime中Message也是一個結構體

struct objc_method {
    SEL method_name                                         OBJC2 _UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}   
/*OBJC2_UNAVAILABLE是一個Apple對Objc系統運行版本進行約束的宏定義,主要爲了兼容非Objective-C 2.0的遺留版本,但我們仍能從中獲取一些有用信息。 */ 

IMP

在上面講Method時就說過,IMP本質上就是一個函數指針,指向方法的實現,在objc.h找到它的定義:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif
id objc_msgSend ( id self, SEL op, ... ); 

首先 SEL 類裏面的方法都是被轉換成SEL變量進行存儲的。SEL其本身是一個Int類型的一個地址,地址中存放着方法的名字。對於一個類中。每一個方法對應着一個SEL。所以iOS類中不能存在2個名稱相同 的方法,即使參數類型不同,因爲SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。SEL 可以用@selector()取類方法的編號,他的行爲基本可以等同C語言的中函數指針,只不過C語言中,可以把函數名直接賦給一個函數指針,而Object-C的類不能直接應用函數指針,這樣只能做一個@selector語法來取.

id其實就是一個指向objc_object結構體指針,它包含一個Class isa成員,根據isa指針就可以順藤摸瓜找到對象所屬的類。

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

C/C++ 函數指針

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;
int (* funcp)(int, int);
int func(int a, int b)
{
    return a + b;
}
int main()
{
    funcp = &func;
    cout<<funcp(1, 2)<<endl;
    return 0;
}

object-c的選擇器

   SEL afun = @selector(someMethodName:::::);

在讓我們看一下objc_msgSend它具體是如何發送消息:

首先根據receiver對象的isa指針獲取它對應的class;
優先在class的cache查找message方法,如果找不到,再到methodLists查找;
如果沒有在class找到,再到super_class查找;
一旦找到message這個方法,就執行它實現的IMP。
這裏寫圖片描述

消息轉發機制

receiver message]調用方法時,如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎麼辦?一般情況下,程序在運行時就會Crash掉,拋出unrecognized selector sent to…類似這樣的異常信息。但在拋出異常之前,還有三次機會按以下順序讓你拯救程序。
這裏寫圖片描述
Method Resolution

首先Objective-C在運行時調用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實現。如果你添加方法並返回YES,那系統在運行時就會重新啓動一次消息發送的過程。

舉一個簡單例子,定義一個類Message,它主要定義一個方法sendMessage,下面就是它的設計與實現:

@interface Message : NSObject  
- (void)sendMessage:(NSString *)word;  
@end  
[cpp] view plaincopy
@implementation Message  
- (void)sendMessage:(NSString *)word  
{  
    NSLog(@"normal way : send message = %@", word);  
}  
@end  

如果我在viewDidLoad方法中創建Message對象並調用sendMessage方法:

- (void)viewDidLoad {  
    [super viewDidLoad];  
    Message *message = [Message new];  
    [message sendMessage:@"Sam Lau"];  
}  

控制檯會打印以下信息:

normal way : send message = Sam Lau
但現在我將原來sendMessage方法實現給註釋掉,覆蓋resolveInstanceMethod方法:

#pragma mark - Method Resolution  
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation  
+ (BOOL)resolveInstanceMethod:(SEL)sel  
{  
    if (sel == @selector(sendMessage:)) {  
        class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {  
            NSLog(@"method resolution way : send message = %@", word);  
        }), "v@*");  
    }  
    return YES;  
}  

控制檯就會打印以下信息:

method resolution way : send message = Sam Lau
注意到上面代碼有這樣一個字符串”v@*,它表示方法的參數和返回值,詳情請參考Type Encodings。

如果resolveInstanceMethod方法返回NO,運行時就跳轉到下一步:消息轉發(Message Forwarding)。

Fast Forwarding

如果目標對象實現- forwardingTargetForSelector:方法,系統就會在運行時調用這個方法,只要這個方法返回的不是nil或self,也會重啓消息發送的過程,把這消息轉發給其他對象來處理。否則,就會繼續Normal Fowarding。

繼續上面Message類的例子,將sendMessage和resolveInstanceMethod方法註釋掉,然後添加forwardingTargetForSelector方法的實現:

#pragma mark - Fast Forwarding  
- (id)forwardingTargetForSelector:(SEL)aSelector  
{  
    if (aSelector == @selector(sendMessage:)) {  
        return [MessageForwarding new];  
    }  
    return nil;  
}  

此時還缺一個轉發消息的類MessageForwarding,這個類的設計與實現如下:

@interface MessageForwarding : NSObject  
- (void)sendMessage:(NSString *)word;  
@end  
[cpp] view plaincopy
@implementation MessageForwarding  
- (void)sendMessage:(NSString *)word  
{  
    NSLog(@"fast forwarding way : send message = %@", word);  
}  
@end  

此時,控制檯會打印以下信息:

fast forwarding way : send message = Sam Lau
這裏叫Fast,是因爲這一步不會創建NSInvocation對象,但Normal Forwarding會創建它,所以相對於更快點。

Normal Forwarding

如果沒有使用Fast Forwarding來消息轉發,最後只有使用Normal Forwarding來進行消息轉發。它首先調用methodSignatureForSelector:方法來獲取函數的參數和返回值,如果返回爲nil,程序會Crash掉,並拋出unrecognized selector sent to instance異常信息。如果返回一個函數簽名,系統就會創建一個NSInvocation對象並調用-forwardInvocation:方法。

繼續前面的例子,將forwardingTargetForSelector方法註釋掉,添加methodSignatureForSelector和forwardInvocation方法的實現:

#pragma mark - Normal Forwarding  
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
{  
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];  
    if (!methodSignature) {  
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];  
    }  
    return methodSignature;  
}  
- (void)forwardInvocation:(NSInvocation *)anInvocation  
{  
    MessageForwarding *messageForwarding = [MessageForwarding new];  
    if ([messageForwarding respondsToSelector:anInvocation.selector]) {  
        [anInvocation invokeWithTarget:messageForwarding];  
    }  
}  

三種方法的選擇

Runtime提供三種方式來將原來的方法實現代替掉,那該怎樣選擇它們呢?

Method Resolution:由於Method Resolution不能像消息轉發那樣可以交給其他對象來處理,所以只適用於在原來的類中代替掉。
Fast Forwarding:它可以將消息處理轉發給其他對象,使用範圍更廣,不只是限於原來的對象。
Normal Forwarding:它跟Fast Forwarding一樣可以消息轉發,但它能通過NSInvocation對象獲取更多消息發送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects

當使用Category對某個類進行擴展時,有時需要存儲屬性,Category是不支持的,這時需要使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個API來向對象添加、獲取和刪除關聯值:

void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
id objc_getAssociatedObject (id object, const void *key )
void objc_removeAssociatedObjects (id object )

其中objc_AssociationPolicy是個枚舉類型,它可以指定Objc內存管理的引用計數機制。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {  
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */  
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.  
                                            *   The association is not made atomically. */  
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object. 
                                            *   The association is made atomically. */  
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied. 
                                            *   The association is made atomically. */  
}; 

個關於NSObject+AssociatedObject Category添加屬性associatedObject的示例代碼:

NSObject+AssociatedObject@interface NSObject (AssociatedObject)  
@property (strong, nonatomic) id associatedObject;  
@end  
NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)  
- (void)setAssociatedObject:(id)associatedObject  
{  
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  
}  
- (id)associatedObject  
{  
    return objc_getAssociatedObject(self, _cmd);  
}  
@end  

Associated Objects的key要求是唯一併且是常量,而SEL是滿足這個要求的,所以上面的採用隱藏參數_cmd作爲key。

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