Objective-C中的Runtime(一) 文章總結(jié)

第一篇:摘抄自 Objective-C Runtime的那點事兒一消息機制

RunTime簡稱運行時。就是系統(tǒng)在運行的時候的一些機制,其中最主要的是消息機制。對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)。編譯完成之后直接順序執(zhí)行,無任何二義性。OC的函數(shù)調(diào)用成為消息發(fā)送。屬于動態(tài)調(diào)用過程。在編譯的時候并不能決定真正調(diào)用哪個函數(shù)(事實證明,在編譯階段,OC可以調(diào)用任何函數(shù),即使這個函數(shù)并未實現(xiàn),只要申明過就不會報錯。而C語言在編譯階段就會報錯)。只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用。

那OC是怎么實現(xiàn)動態(tài)調(diào)用的呢?下面我們來看看OC通過發(fā)送消息來達到動態(tài)調(diào)用的秘密。假如在OC中寫了這樣的一個代碼:

 [obj makeText];

其中obj是一個對象,makeText是一個函數(shù)名稱。對于這樣一個簡單的調(diào)用。在編譯時RunTime會將上述代碼轉(zhuǎn)化成

objc_msgSend(obj,@selector(makeText));

obj解釋

首先看obj這個對象,iOS中的obj都繼承于NSObject。我們看到在NSObject中存在一個Class的isa指針:

  @interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
  }

isa的類型為結(jié)構(gòu)體

  //An opaque type that represents an Objective-C class.
  typedef struct objc_class *Class;
-->
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;  //指向metaclass

#if !__OBJC2__
    Class super_class;      //指向其父類
    const char *name;      //類名
    long version;    //類的版本信息,初始化默認為0,可以通過runtime函數(shù)class_setVersion 和 class_getVersion 進行修改、讀取
    long info;   //一些標(biāo)識信息,如CLS_CLASS(0x1L)表示該類為普通class,其中包含對象方法和成員變量;CLS_META(0x2L)表示該類為 metaclass,其中包含類方法
    long instance_size; // 該類的實例變量大小(包括從父類繼承下來的實例變量)
    struct objc_ivar_list *ivars; //用于存儲每個成員變量的地址
    struct objc_method_list **methodLists; //與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲對象方法,如CLS_META (0x2L),則存儲類方法
    struct objc_cache *cache;   // 指向最近使用的方法的指針,用于提升效率;
    struct objc_protocol_list *protocols;  // 存儲該類遵守的協(xié)議
#endif
} OBJC2_UNAVAILABLE;

解釋:
Class isa : 指向metaclass,也就是靜態(tài)的Class。
一般<u>Object對象中的isa指向普通的Class</u>,這個Class中存儲普通成員變量和對象方法(- 開頭的方法),<u>普通Class中的isa指針指向靜態(tài)Class</u>,靜態(tài)Class中存儲static類型成員變量和類方法(+ 開頭的方法)。

Class super_class: 指向父類,如果這個類是根類,則為NULL。

注意:
所有metaclass中isa指針都指向根metaclass,而根metaclass則指向自身。

if !__OBJC2__ endif標(biāo)記的屬性是Ojective-C 2.0不支持的,但實際上可以用響應(yīng)的函數(shù)獲取這些屬性,例如:如果想要獲取Class的name屬性,可以按如下方法獲取:

Class classPerson = Person.class;
// printf("%s\n", classPerson->name); //用這種方法已經(jīng)不能獲取name了 因為OBJC2_UNAVAILABLE
const char *cname  = class_getName(classPerson);
printf("%s", cname); // 輸出:Person

@selector(makeText) :

這是一個SEL方法選擇器。SEL主要作用是快速地通過方法名字(makeText)查找對應(yīng)方法的函數(shù)指針,然后調(diào)用其函數(shù)。

SEL本身是一個Int類型的一個地址,地址中存放著方法的名字。在一個類中,每個方法對應(yīng)著一個SEL,所以不能存在多個名稱相同的方法,即使參數(shù)類型不同。因為SEL是根據(jù)方法名字生成的,相同的方法名稱只能對應(yīng)一個SEL。

那么消息發(fā)送之后是怎么來動態(tài)查找對應(yīng)的方法的?

-->首先編譯器會將 [obj makeText]; 轉(zhuǎn)化為 objc_msgSend(obj, @selector(makeText));
然后在 objc_msgSend函數(shù)中,首先通過obj的isa指針找到obj對應(yīng)的class。在Class中先去cache中通過SEL查找對應(yīng)函數(shù)method(猜測cache中的method列表是以SEL為key通過hash表來存儲的,這樣能提高函數(shù)查找速度),若cache中沒有找到,則到methodLists中查找,若找不到,則取superClass中查找。若找到了,則將method加入到cache中,以便下次查找,并通過method中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行。


第二篇:iOS~runtime理解

我們寫的代碼在程序運行過程中都會被轉(zhuǎn)化成runtime的C代碼執(zhí)行,例如[target doSomething];會被轉(zhuǎn)化成objc_msgSend(target, @selector(doSomething));。

相關(guān)的定義

/// An opaque type that represents a method in a class definition.
//類中的一個方法
typedef struct objc_method *Method;

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

/// An opaque type that represents a category.
// 類別 Category
typedef struct objc_category *Category;

/// An opaque type that represents an Objective-C declared property.
// 類中聲明的屬性
typedef struct objc_property *objc_property_t;

獲取各種列表的方法:

//獲取列表
- (void)getList{
    unsigned int count;
    
    //獲取屬性列表
    objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
    for (unsigned int i=0; i < count; i++) {
        objc_property_t property = propertyList[i];
        const char *propertyName = property_getName(property);
        NSLog(@"property ----> %@",[NSString stringWithUTF8String:propertyName]);
    }
    
    //獲取方法列表
    Method *methodList = class_copyMethodList([Person class], &count);
    for (unsigned int i=0; i < count; i++) {
        Method method = methodList[i];
        NSLog(@"method----> %@",NSStringFromSelector(method_getName(method)));
    }

    //獲取成員變量列表
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char * ivarName = ivar_getName(ivar);
        NSLog(@"Ivar ----> %@",[NSString stringWithUTF8String:ivarName]);
    }
    
    //獲取協(xié)議列表
    __unsafe_unretained Protocol ** protocolList = class_copyProtocolList([Person class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }
}

方法調(diào)用過程

如果用實例對象調(diào)用實例方法,會到實例的isa指針指向的對象(也就是類對象)操作。
如果調(diào)用的是類方法,就會到類對象的isa指針指向的對象(也就是元類對象)中操作。

--> 1.首先,在相應(yīng)操作的對象中的緩存方法列表中找調(diào)用的方法,如果找到,轉(zhuǎn)向相應(yīng)實現(xiàn)并執(zhí)行。

--> 2.如果沒找到,在相應(yīng)操作的對象中的方法列表中找調(diào)用的方法,如果找到,轉(zhuǎn)向相應(yīng)實現(xiàn)并執(zhí)行。

--> 3.如果沒找到,去父類指針?biāo)赶虻膶ο笾袌?zhí)行1,2。

--> 4.依次類推,如果一直到根類還沒找到,轉(zhuǎn)向攔截調(diào)用。

--> 5.如果沒有重寫攔截調(diào)用的方法,程序報錯。

以上的過程給我?guī)淼膯l(fā):

  • 重寫父類的方法,并沒有覆蓋掉父類的方法,只是在當(dāng)前類對象中找到了這個方法后就不會再去父類中找了。
  • 如果想調(diào)用已經(jīng)重寫過的方法的父類的實現(xiàn),只需使用super這個編譯器標(biāo)識,它會在運行時跳過在當(dāng)前的類對象中尋找方法的過程。

-->攔截調(diào)用

攔截調(diào)用就是,在找不到調(diào)用的方法程序崩潰之前,你有機會通過重寫NSObject的四個方法來處理。

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個方法需要轉(zhuǎn)發(fā)到其他的類處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一個方法是當(dāng)你調(diào)用一個不存在的類方法的時候,會調(diào)用這個方法。默認返回NO,可以加上自己的處理后返回YES。
  • 第二個方法和第一個方法相似,只不過處理的是實例方法。
  • 第三個方法是將你調(diào)用的不存在的方法重定向到一個其他聲明了這個方法的類,只需要你返回一個有這個方法的target。
  • 第四個方法是將你調(diào)用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理后,調(diào)用invokeWithTarget : 方法讓某個target觸發(fā)這個方法。

動態(tài)添加方法

重寫攔截調(diào)用的方法并且返回YES -> 我們是怎么實現(xiàn)的呢?

<u>有一個辦法是根據(jù)傳進來的SEL類型的selector動態(tài)添加一個方法:</u>

     //隱式調(diào)用方法
     [target performSelector:@selector(resolveAdd:) withObject:@"test"];

然后,在target對象內(nèi)部重寫攔截調(diào)用的方法,動態(tài)添加方法。

    void runAddMethod(id self, SEL _cmd, NSString *string){
        NSLog(@"add C IMP  %@",string);
    }
    + (BOOL)resolveInstanceMethod:(SEL) sel{
        //給本類動態(tài)添加一個方法
        if([NSStringFromSelector(sel)  isEqualToString:@"resolveAdd:"]){
              class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
         }
    }

其中 class_addMethod 的四個參數(shù)分別是:

1、Class cls 給哪個類添加方法,本例中是self
2、SEL name添加的方法,本例中是重寫的攔截調(diào)用傳進來的selector。
3、IMP imp 方法的實現(xiàn),C方法的方法實現(xiàn)可以直接獲得。如果是OC方法,可以用 +(IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實現(xiàn)。
4、"v@:*"方法的簽名,代表有一個參數(shù)的方法。

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

現(xiàn)在你準(zhǔn)備用一個系統(tǒng)的類,但是系統(tǒng)的類并不能滿足你的需求,你需要額外添加一個屬性。
這種情況的一般解決辦法就是繼承。
但是,只增加一個屬性,就去繼承一個類,總是覺得太麻煩類。
這個時候,runtime的關(guān)聯(lián)屬性就發(fā)揮它的作用了。

    //首先定義一個全局變量,用它的地址作為關(guān)聯(lián)對象的key
    static char associatedObjectKey;
    //設(shè)置關(guān)聯(lián)對象
    objc_setAssociatedObject(target, $associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC);    
    //獲取關(guān)聯(lián)對象
    NSString *string = objc_getAssociatedObject(target, & associatedObjectKey);
    NSLog(@"AssociatedObject = %@", string);

objc_setAssociatedObject的四個參數(shù):

  1. id object 給誰設(shè)置關(guān)聯(lián)對象。
    2.const void *key 關(guān)聯(lián)對象唯一的key,獲取時會用到。
    3.id value 關(guān)聯(lián)對象。
    4.objc_AssociationPolicy關(guān)聯(lián)策略,有以下幾種策略:
如果你熟悉OC,看名字應(yīng)該知道這幾種策略的意思了吧。
  enum {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403 
};

objc_getAssociatedObject的兩個參數(shù)
1.id object 獲取誰的關(guān)聯(lián)對象。
2.const void *key 根據(jù)這個唯一的key獲取關(guān)聯(lián)對象。

其實,你還可以把添加和獲取關(guān)聯(lián)對象的方法寫在你需要用到這個功能的類的類別中,方便使用。

//添加關(guān)聯(lián)對象
- (void)addAssociatedObject:(id)object{
    objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//關(guān)聯(lián)策略依情況而定
}
//獲取關(guān)聯(lián)對象
- (id)getAssociatedObject{
    return objc_getAssociatedObject(self, _cmd);
}

注意:這里面我們把getAssociatedObject方法的地址作為唯一的key,
     _cmd代表當(dāng)前調(diào)用方法的地址。

方法交換 (詳見原文)

方法交換,顧名思義,就是將兩個方法的實現(xiàn)交換。例如,將A方法和B方法交換,調(diào)用A方法的時候,就會執(zhí)行B方法中的代碼,反之亦然。
話不多說,這是參考Mattt大神在NSHipster上的文章自己寫的代碼。

方法交換對于我來說更像是實現(xiàn)一種思想的最佳技術(shù):AOP面向切面編程。
既然是切面,就一定不要忘記,交換完再調(diào)回自己。
一定要保證只交換一次,否則就會很亂。
最后,據(jù)說這個技術(shù)很危險,謹慎使用。

最后編輯于
?著作權(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)容