Objective-C Runtime 運行時之一:類與對象

来源:http://www.cnblogs.com/Mr-Lin/archive/2016/08/15/5771969.html
-Advertisement-
Play Games

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。 這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運行時系統來執行 ...


Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時更具靈活性,如我們可以把消息轉發給我們想要的對象,或者隨意交換一個方法的實現等。

這種特性意味著Objective-C不僅需要一個編譯器,還需要一個運行時系統來執行編譯的代碼。對於Objective-C來說,這個運行時系統就像一個操作系統一樣:它讓所有的工作可以正常的運行。這個運行時系統即Objc Runtime。Objc Runtime其實是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。

Runtime庫主要做下麵幾件事:

  1. 封裝:在這個庫中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,我們就可以在程式運行時創建,檢查,修改類、對象和它們的方法了。
  2. 找出方法的最終執行代碼:當程式執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。這將在後面詳細介紹。

Objective-C runtime目前有兩個版本:Modern runtime和Legacy runtime。Modern Runtime 覆蓋了64位的Mac OS X Apps,還有 iOS Apps,Legacy Runtime 是早期用來給32位 Mac OS X Apps 用的,也就是可以不用管就是了。

在這一系列文章中,我們將介紹runtime的基本工作原理,以及如何利用它讓我們的程式變得更加靈活。在本文中,我們先來介紹一下類與對象,這是面向對象的基礎,我們看看在Runtime中,類是如何實現的。


類與對象基礎數據結構

Class

Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針。它的定義如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
 
/// Represents an instance of a class.
struct objc_object {
    Class isa;
};
 
/// A pointer to an instance of a class.
typedef struct objc_object *id;

由此可見,Class是一個指向objc_class結構體的指針,而id是一個指向objc_object結構體的指針,其成員isa是一個指向objec_class結構體的指針。

查看objc/runtime.h中objc_class結構體的定義如下:

struct objc_class {

    Class isa  OBJC_ISA_AVAILABILITY;



#if !__OBJC2__

    Class super_class                       OBJC2_UNAVAILABLE;  // 父類

    const char *name                        OBJC2_UNAVAILABLE;  // 類名

    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,預設為0

    long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標識

    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變數大小

    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變數鏈表

    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表

    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存

    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協議鏈表

#endif



} OBJC2_UNAVAILABLE;

  

在這個定義中,下麵幾個欄位是我們感興趣的

  1. isa:需要註意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class裡面也有一個isa指針,它指向metaClass(元類),我們會在後面介紹它。
  2. super_class:指向該類的父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
  3. cache:用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對於那些經常用到的方法的調用,但提高了調用的效率。
  4. version:我們可以使用這個欄位來提供類的版本信息。這對於對象的序列化非常有用,它可是讓我們識別出不同類定義版本中實例變數佈局的改變。

針對cache,我們用下麵例子來說明其執行過程:

NSArray *array = [[NSArray alloc] init];

  

其流程是:

  1. [NSArray alloc]先被執行。因為NSArray沒有+alloc方法,於是去父類NSObject去查找。
  2. 檢測NSObject是否響應+alloc方法,發現響應,於是檢測NSArray類,並根據其所需的記憶體空間大小開始分配記憶體空間,然後把isa指針指向NSArray類。同時,+alloc也被加進cache列表裡面。
  3. 接著,執行-init方法,如果NSArray響應該方法,則直接將其加入cache;如果不響應,則去父類查找。
  4. 在後期的操作中,如果再以[[NSArray alloc] init]這種方式來創建數組,則會直接從cache中取出相應的方法,直接調用。

objc_object與id

objc_object是表示一個類的實例的結構體,它的定義如下(objc/objc.h):

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};

typedef struct objc_object *id;

  

可以看到,這個結構體只有一個字體,即指向其類的isa指針。這樣,當我們向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應的selector指向的方法。找到後即運行這個方法。

當創建一個特定類的實例對象時,分配的記憶體包含一個objc_object數據結構,然後是類的實例變數的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。

另外還有我們常見的id,它是一個objc_object結構類型的指針。它的存在可以讓我們實現類似於C++中泛型的一些操作。該類型的對象可以轉換為任何一種對象,有點類似於C語言中void *指針類型的作用。

objc_cache

上面提到了objc_class結構體中的cache欄位,它用於緩存調用過的方法。這個欄位是一個指向objc_cache結構體的指針,其定義如下:

struct objc_cache {

    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

    unsigned int occupied                                    OBJC2_UNAVAILABLE;

    Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

  

該結構體的欄位描述如下:

  1. mask:一個整數,指定分配的緩存bucket的總數。在方法查找過程中,Objective-C runtime使用這個欄位來確定開始線性查找數組的索引位置。指向方法selector的指針與該欄位做一個AND位操作(index = (mask & selector))。這可以作為一個簡單的hash散列演算法。
  2. occupied:一個整數,指定實際占用的緩存bucket的總數。
  3. buckets:指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要註意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續的。這個數組可能會隨著時間而增長。

元類(Meta Class)

在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發送消息(即調用類方法)。如:

NSArray *array = [NSArray array];

  這個例子中,+array消息發送給了NSArray類,而這個NSArray也是一個對象。既然是對象,那麼它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那麼這些就有一個問題了,這個isa指針指向什麼呢?為了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念

meta-class是一個類對象的類。

  

當我們向一個對象發送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發送消息時,會在這個類的meta-class的方法列表中查找。

meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同

再深入一下,meta-class也是一個類,也可以向它發送一個消息,那麼它的isa又是指向什麼呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環。

通過上面的描述,再加上對objc_class結構體中super_class指針的分析,我們就可以描繪出類及相應meta-class類的一個繼承體系了,如下圖所示:

對於NSObject繼承體系來說,其實例方法對體系中的所有實例、類和meta-class都是有效的;而類方法對於體系內的所有類和meta-class都是有效的。

講了這麼多,我們還是來寫個例子吧:

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()

@end
void TextMetaCLass(id self,SEL _cmd);
@implementation ViewController
void TextMetaCLass(id self,SEL _cmd){
    NSLog(@"This Object is %p",self);
    NSLog(@"Class is %@, super class is %@",[self class],[self superclass]);
    
    Class currentClass = [self class];
    
    for (int i = 0; i < 4; i++) {
        
        NSLog(@"Following the isa pointer %d times gives %p", i ,currentClass);
        
        /**
         *  獲取類對象
         *
         *  @param object 想要獲取的類
         *
         *  @return 類對象或nil
         */
        currentClass = objc_getClass((__bridge void *)currentClass);
    }
    
    NSLog(@"NSObject's class is %p", [NSError class]);
    
    NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSError class]));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     *  創建一個新的類
     *
     *  @param superclass       作為新類的父類,若為空,則為根類
     *  @param name             新類的名字
     *  @param extraBytes       為類或元類對象分配位元組數,通常都是為0
     *
     *  @return 新類或為空nil(如果創建不成功:新的類名已經存在)
     */
    Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);
    
    /**
     *  為新類添加新方法(註意:不可同名)
     *
     *  @param newClass         要添加方法的類
     *  @param testMetaClass    將要添加的方法名字
     *  @param imp              函數方法的聲明 ,且該函數至少有兩個參數對象,分別為self 和 _cmd.
     *  @param types            字元數組用於描述方法中的參數類型,因為方法中必須有self 和 _cmd 這兩個參數,所以第二個跟第三個字元必須是“@:”
     *  @return YES 添加方法成功 NO 添加方法失敗
     */
    class_addMethod(newClass, @selector(testMetaClass), (IMP)TextMetaCLass, "v@:");
    
    /**
     *  為類添加新的實例變數(註意:不支持為現有的類、元類添加實例變數)
     *
     *  @param cls          要添加實例變數的類對象
     *  @param name         變數名字
     *  @param size         為變數分配記憶體空間
     *  @param alignment
     *  @param types        變數的類型
     *
     *  @return YES 添加實例變數成功 NO 添加實例變數失敗
     */
    //class_addIvar(<#__unsafe_unretained Class cls#>, <#const char *name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char *types#>)
    
    /**
     *  註冊通過方法objc_allocateClassPair創建的類
     *
     *  @param cls  即開發者創建的類
     */
    objc_registerClassPair(newClass);
    
    
    
    id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];
    
    [instance performSelector:@selector(testMetaClass)];
    
}

  

這個例子是在運行時創建了一個NSError的子類TestClass,然後為這個子類添加一個方法testMetaClass,這個方法的實現是TestMetaClass函數。

運行後,列印結果是

2016-08-11 14:47:55.559 Runtime1-類與對象[27720:1858642] This Object is 0x7fcbd8d4dc20
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] Class is TestClass, super class is NSError
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] Following the isa pointer 0 times gives 0x7fcbd8d276d0
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] Following the isa pointer 1 times gives 0x0
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] Following the isa pointer 2 times gives 0x0
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] Following the isa pointer 3 times gives 0x0
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] NSObject's class is 0x106854a88
2016-08-11 14:47:55.560 Runtime1-類與對象[27720:1858642] NSObject's meta class is 0x0

我們在for迴圈中,我們通過objc_getClass來獲取對象的isa,並將其列印出來,依此一直回溯到NSObject的meta-class。分析列印結果,可以看到最後指針指向的地址是0x0,即NSObject的meta-class的類地址。

這裡需要註意的是:我們在一個類對象調用class方法是無法獲取meta-class,它只是返回類而已。


類與對象操作函數

runtime提供了大量的函數來操作類與對象。類的操作方法大部分是以class為首碼的,而對象的操作方法大部分是以objc或object_為首碼。下麵我們將根據這些方法的用途來分類討論這些方法的使用。

類相關操作函數

我們可以回過頭去看看objc_class的定義,runtime提供的操作類的方法主要就是針對這個結構體中的各個欄位的。下麵我們分別介紹這一些的函數。併在最後以實例來演示這些函數的具體用法。

類名(name)

類名操作的函數主要有:

// 獲取類的類名

const char * class_getName ( Class cls );

  

● 對於class_getName函數,如果傳入的cls為Nil,則返回一個字字元串。

父類(super_class)和元類(meta-class)

父類和元類操作的函數主要有:

// 獲取類的父類

Class class_getSuperclass ( Class cls );



// 判斷給定的Class是否是一個元類

BOOL class_isMetaClass ( Class cls );

  

● class_getSuperclass函數,當cls為Nil或者cls為根類時,返回Nil。不過通常我們可以使用NSObject類的superclass方法來達到同樣的目的。

● class_isMetaClass函數,如果是cls是元類,則返回YES;如果否或者傳入的cls為Nil,則返回NO。

實例變數大小(instance_size)

實例變數大小操作的函數有:

// 獲取實例大小

size_t class_getInstanceSize ( Class cls );

  

成員變數(ivars)及屬性

在objc_class中,所有的成員變數、屬性的信息是放在鏈表ivars中的。ivars是一個數組,數組中每個元素是指向Ivar(變數信息)的指針。runtime提供了豐富的函數來操作這一欄位。大體上可以分為以下幾類:

1.成員變數操作函數,主要包含以下函數:

// 獲取類中指定名稱實例成員變數的信息

Ivar class_getInstanceVariable ( Class cls, const char *name );

// 獲取類成員變數的信息

Ivar class_getClassVariable ( Class cls, const char *name );



// 添加成員變數

BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );



// 獲取整個成員變數列表

Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

  

● class_getInstanceVariable函數,它返回一個指向包含name指定的成員變數信息的objc_ivar結構體的指針(Ivar)。

● class_getClassVariable函數,目前沒有找到關於Objective-C中類變數的信息,一般認為Objective-C不支持類變數。註意,返回的列表不包含父類的成員變數和屬性。

● Objective-C不支持往已存在的類中添加實例變數,因此不管是系統庫提供的類,還是我們自定義的類,都無法動態添加成員變數。但如果我們通過運行時來創建一個類的話,又應該如何給它添加成員變數呢?這時我們就可以使用class_addIvar函數了。不過需要註意的是,這個方法只能在objc_allocateClassPair函數與objc_registerClassPair之間調用。另外,這個類也不能是元類。成員變數的按位元組最小對齊量是1<<alignment。這取決於ivar的類型和機器的架構。如果變數的類型是指針類型,則傳遞log2(sizeof(pointer_type))。

● class_copyIvarList函數,它返回一個指向成員變數信息的數組,數組中每個元素是指向該成員變數信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變數。outCount指針返回數組的大小。需要註意的是,我們必須使用free()來釋放這個數組。

2.屬性操作函數,主要包含以下函數:

// 獲取指定的屬性

objc_property_t class_getProperty ( Class cls, const char *name );



// 獲取屬性列表

objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );



// 為類添加屬性

BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );



// 替換類的屬性

void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

  

這一種方法也是針對ivars來操作,不過只操作那些是屬性的值。我們在後面介紹屬性時會再遇到這些函數。

3.在MAC OS X系統中,我們可以使用垃圾回收器。runtime提供了幾個函數來確定一個對象的記憶體區域是否可以被垃圾回收器掃描,以處理strong/weak引用。這幾個函數定義如下:

const uint8_t * class_getIvarLayout ( Class cls );

void class_setIvarLayout ( Class cls, const uint8_t *layout );

const uint8_t * class_getWeakIvarLayout ( Class cls );

void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );

  

但通常情況下,我們不需要去主動調用這些方法;在調用objc_registerClassPair時,會生成合理的佈局。在此不詳細介紹這些函數。

方法(methodLists)

方法操作主要有以下函數:

// 添加方法

BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );



// 獲取實例方法

Method class_getInstanceMethod ( Class cls, SEL name );



// 獲取類方法

Method class_getClassMethod ( Class cls, SEL name );



// 獲取所有方法的數組

Method * class_copyMethodList ( Class cls, unsigned int *outCount );



// 替代方法的實現

IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );



// 返回方法的具體實現

IMP class_getMethodImplementation ( Class cls, SEL name );

IMP class_getMethodImplementation_stret ( Class cls, SEL name );



// 類實例是否響應指定的selector

BOOL class_respondsToSelector ( Class cls, SEL sel );

class_addMethod的實現會覆蓋父類的方法實現,但不會取代本類中已存在的實現,如果本類中包含一個同名的實現,則函數會返回NO。如果要修改已存在實現,可以使用method_setImplementation。一個Objective-C方法是一個簡單的C函數,它至少包含兩個參數—self和_cmd。所以,我們的實現函數(IMP參數指向的函數)至少需要兩個參數,如下所示:

void myMethodIMP(id self, SEL _cmd)

{

    // implementation ....

}

 

與成員變數不同的是,我們可以為類動態添加方法,不管這個類是否已存在。

另外,參數types是一個描述傳遞給方法的參數類型的字元數組,這就涉及到類型編碼,我們將在後面介紹。

● class_getInstanceMethod、class_getClassMethod函數,與class_copyMethodList不同的是,這兩個函數都會去搜索父類的實現。

● class_copyMethodList函數,返回包含所有實例方法的數組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類裡面)。該列表不包含父類實現的方法。outCount參數返回方法的個數。在獲取到列表後,我們需要使用free()方法來釋放它。

● class_replaceMethod函數,該函數的行為可以分為兩種:如果類中不存在name指定的方法,則類似於class_addMethod函數一樣會添加方法;如果類中已存在name指定的方法,則類似於method_setImplementation一樣替代原方法的實現。

● class_getMethodImplementation函數,該函數在向類實例發送消息時會被調用,並返回一個指向方法實現函數的指針。這個函數會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數指針可能是一個指向runtime內部的函數,而不一定是方法的實際實現。例如,如果類實例無法響應selector,則返回的函數指針將是運行時消息轉發機制的一部分。

● class_respondsToSelector函數,我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達到相同目的。

協議(objc_protocol_list)

協議相關的操作包含以下函數:

// 添加協議

BOOL class_addProtocol ( Class cls, Protocol *protocol );



// 返回類是否實現指定的協議

BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );



// 返回類實現的協議列表

Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

 class_conformsToProtocol函數可以使用NSObject類的conformsToProtocol:方法來替代。

● class_copyProtocolList函數返回的是一個數組,在使用後我們需要使用free()手動釋放。

版本(version)

版本相關的操作包含以下函數:

// 獲取版本號

int class_getVersion ( Class cls );



// 設置版本號

void class_setVersion ( Class cls, int version );

其它

runtime還提供了兩個函數來供CoreFoundation的tool-free bridging使用,即:

Class objc_getFutureClass ( const char *name );

void objc_setFutureClass ( Class cls, const char *name );

通常我們不直接使用這兩個函數。

實例(Example)

上面列舉了大量類操作的函數,下麵我們寫個實例,來看看這些函數的實例效果:

//-----------------------------------------------------------

// MyClass.h



@interface MyClass : NSObject <NSCopying, NSCoding>



@property (nonatomic, strong) NSArray *array;



@property (nonatomic, copy) NSString *string;



- (void)method1;



- (void)method2;



+ (void)classMethod1;



@end



//-----------------------------------------------------------

// MyClass.m



#import "MyClass.h"

@interface MyClass () {

    NSInteger       _instance1;



    NSString    *   _instance2;

}



@property (nonatomic, assign) NSUInteger integer;



- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;



@end



@implementation MyClass



+ (void)classMethod1 {



}



- (void)method1 {

    NSLog(@"call method method1");

}



- (void)method2 {



}



- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {



    NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);

}



@end

//-----------------------------------------------------------

// main.h



#import "MyClass.h"

#import "MySubClass.h"

#import <objc/runtime.h>



int main(int argc, const char * argv[]) {

    @autoreleasepool {



        MyClass *myClass = [[MyClass alloc] init];

        unsigned int outCount = 0;



        Class cls = myClass.class;



        // 類名

        NSLog(@"class name: %s", class_getName(cls));



        NSLog(@"==========================================================");



        // 父類

        NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));

        NSLog(@"==========================================================");



        // 是否是元類

        NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));

        NSLog(@"==========================================================");



        Class meta_class = objc_getMetaClass(class_getName(cls));

        NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));

        NSLog(@"==========================================================");



        // 變數實例大小

        NSLog(@"instance size: %zu", class_getInstanceSize(cls));

        NSLog(@"==========================================================");



        // 成員變數

        Ivar *ivars = class_copyIvarList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            Ivar ivar = ivars[i];

            NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);

        }



        free(ivars);



        Ivar string = class_getInstanceVariable(cls, "_string");

        if (string != NULL) {

            NSLog(@"instace variable %s", ivar_getName(string));

        }



        NSLog(@"==========================================================");



        // 屬性操作

        objc_property_t * properties = class_copyPropertyList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            objc_property_t property = properties[i];

            NSLog(@"property's name: %s", property_getName(property));

        }



        free(properties);



        objc_property_t array = class_getProperty(cls, "array");

        if (array != NULL) {

            NSLog(@"property %s", property_getName(array));

        }



        NSLog(@"==========================================================");



        // 方法操作

        Method *methods = class_copyMethodList(cls, &outCount);

        for (int i = 0; i < outCount; i++) {

            Method method = methods[i];

            NSLog(@"method's signature: %s", method_getName(method));

        }



        free(methods);



        Method method1 = class_getInstanceMethod(cls, @selector(method1));

        if (method1 != NULL) {

            NSLog(@"method %s", method_getName(method1));

        }



        Method classMethod = class_getClassMethod(cls, @selector(classMethod1));

        if (classMethod != NULL) {

            NSLog(@"class method : %s", method_getName(classMethod));

        }



        NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");



        IMP imp = class_getMethodImplementation(cls, @selector(method1));

        imp();



        NSLog(@"==========================================================");



        // 協議

        Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);

        Protocol * protocol;

        for (int i = 0; i < outCount; i++) {

            protocol = protocols[i];

            NSLog(@"protocol name: %s", protocol_getName(protocol));

        }



        NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));



        NSLog(@"==========================================================");

    }

    return 0;

}

這段程式的輸出如下:

2014-10-22 19:41:37.452 RuntimeTest[3189:156810] class name: MyClass

2014-10-22 19:41:37.453 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.454 RuntimeTest[3189:156810] super class name: NSObject

2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass is not a meta-class

2014-10-22 19:41:37.454 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.454 RuntimeTest[3189:156810] MyClass's meta-class is MyClass

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance size: 48

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance1 at index: 0

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _instance2 at index: 1

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _array at index: 2

2014-10-22 19:41:37.455 RuntimeTest[3189:156810] instance variable's name: _string at index: 3

2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instance variable's name: _integer at index: 4

2014-10-22 19:41:37.463 RuntimeTest[3189:156810] instace variable _string

2014-10-22 19:41:37.463 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: array

2014-10-22 19:41:37.463 RuntimeTest[3189:156810] property's name: string

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property's name: integer

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] property array

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method1

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method2

2014-10-22 19:41:37.464 RuntimeTest[3189:156810] method's signature: method3WithArg1:arg2:

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: integer

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setInteger:

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: array

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: string

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setString:

2014-10-22 19:41:37.465 RuntimeTest[3189:156810] method's signature: setArray:

2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method's signature: .cxx_destruct

2014-10-22 19:41:37.466 RuntimeTest[3189:156810] method method1

2014-10-22 19:41:37.466 RuntimeTest[3189:156810] class method : classMethod1

2014-10-22 19:41:37.466 RuntimeTest[3189:156810] MyClass is responsd to selector: method3WithArg1:arg2:

2014-10-22 19:41:37.467 RuntimeTest[3189:156810] call method method1

2014-10-22 19:41:37.467 RuntimeTest[3189:156810] ==========================================================

2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCopying

2014-10-22 19:41:37.467 RuntimeTest[3189:156810] protocol name: NSCoding

2014-10-22 19:41:37.467 RuntimeTest[3189:156810] MyClass is responsed to protocol NSCoding

2014-10-22 19:41:37.468 RuntimeTest[3189:156810] ==========================================================

  

動態創建類和對象

runtime的強大之處在於它能在運行時創建類和對象。

動態創建類

動態創建類涉及到以下幾個函數:

// 創建一個新類和元類

Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );



// 銷毀一個類及其相關聯的類

void objc_disposeClassPair ( Class cls );



// 在應用中註冊由objc_allocateClassPair創建的類

void objc_registerClassPair ( Class cls );

  

 objc_allocateClassPair函數:如果我們要創建一個根類,則superclass指定為Nil。extraBytes通常指定為0,該參數是分配給類和元類對象尾部的索引ivars的位元組數。

為了創建一個新類,我們需要調用objc_allocateClassPair。然後使用諸如class_addMethod,class_addIvar等函數來為新創建的類添加方法、實例變數和屬性等。完成這些後,我們需要調用objc_registerClassPair函數來註冊類,之後這個新類就可以在程式中使用了。

實例方法和實例變數應該添加到類自身上,而類方法應該添加到類的元類上。

● objc_disposeClassPair函數用於銷毀一個類,不過需要註意的是,如果程式運行中還存在類或其子類的實例,則不能調用針對類調用該方法。

在前面介紹元類時,我們已經有接觸到這幾個函數了,在此我們再舉個實例來看看這幾個函數的使用。

Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);

  

class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");

objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};

class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);

id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];

程式的輸出如下:

2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1

2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1

  

動態創建對象

動態創建對象的函數如下:

// 創建類實例

id class_createInstance ( Class cls, size_t extraBytes );



// 在指定位置創建類實例

id objc_constructInstance ( Class cls, void *bytes );



// 銷毀類實例

void * objc_destructInstance ( id obj );

  

 class_createInstance函數:創建實例時,會在預設的記憶體區域為類分配記憶體。extraBytes參數表示分配的額外位元組數。這些額外的位元組可用於存儲在類定義中所定義的實例變數之外的實例變數。該函數在ARC環境下無法使用。

調用class_createInstance的效果與+alloc方法類似。不過在使用class_createInstance時,我們需要確切的知道我們要用它來做什麼。在下麵的例子中,我們用NSString來測試一下該函數的實際效果:

id theObject = class_createInstance(NSString.class, sizeof(unsigned));

 

id str1 = [theObject init];

NSLog(@"%@", [str1 class]);

id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);

輸出結果是:

2014-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString

2014-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString

  

可以看到,使用class_createInstance函數獲取的是NSString實例,而不是類簇中的預設占位符類__NSCFConstantString。

● objc_constructInstance函數:在指定的位置(bytes)創建類實例。

● objc_destructInstance函數:銷毀一個類的實例,但不會釋放並移除任何與其相關的引用。

實例操作函數

實例操作函數主要是針對我們創建的實例對象的一系列操作函數,我們可以使用這組函數來從實例對象中獲取我們想要的一些信息,如實例對象中變數的值。這組函數可以分為三小類:

1.針對整個對象進行操作的函數,這類函數包含

// 返回指定對象的一份拷貝

id object_copy ( id obj, size_t size );



// 釋放指定對象占用的記憶體

id object_dispose ( id obj );

有這樣一種場景,假設我們有類A和類B,且類B是類A的子類。類B通過添加一些額外的屬性來擴展類A。現在我們創建了一個A類的實例對象,並希望在運行時將這個對象轉換為B類的實例對象,這樣可以添加數據到B類的屬性中。這種情況下,我們沒有辦法直接轉換,因為B類的實例會比A類的實例更大,沒有足夠的空間來放置對象。此時,我們就要以使用以上幾個函數來處理這種情況,如下代碼所示:

NSObject *a = [[NSObject alloc] init];

id newB = object_copy(a, class_getInstanceSize(MyClass.class));

object_setClass(newB, MyClass.class);

object_dispose(a);

2.針對對象實例變數進行操作的函數,這類函數包含

// 修改類實例的實例變數的值

Ivar object_setInstanceVariable ( id obj, const char *name, void *value );



// 獲取對象實例變數的值

Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );



// 返回指向給定對象分配的任何額外位元組的指針

void * object_getIndexedIvars ( id obj );



// 返回對象中實例變數的值

id object_getIvar ( id obj, Ivar ivar );



// 設置對象中實例變數的值

void object_setIvar ( id obj, Ivar ivar, id value );

  

如果實例變數的Ivar已經知道,那麼調用object_getIvar會比object_getInstanceVariable函數快,相同情況下,object_setIvar也比object_setInstanceVariable快。

3.針對對象的類進行操作的函數,這類函數包含:

// 返回給定對象的類名

const char * object_getClassName ( id obj );



// 返回對象的類

Class object_getClass ( id obj );



// 設置對象的類

Class object_setClass ( id obj, Class cls );

  

獲取類定義

Objective-C動態運行庫會自動註冊我們代碼中定義的所有的類。我們也可以在運行時創建類定義並使用objc_addClass函數來註冊它們。runtime提供了一系列函數來獲取類定義相關的信息,這些函數主要包括:

// 獲取已註冊的類定義的列表

int objc_getClassList ( Class *buffer, int bufferCount );



// 創建並返回一個指向所有已註冊類的指針列表

Class * objc_copyClassList ( unsigned int *outCount );



// 返回指定類的類定義

Class objc_lookUpClass ( const char *name );

Class objc_getClass ( const char *name );

Class objc_getRequiredClass ( const char *name );



// 返回指定類的元類

Class objc_getMetaClass ( const char *name );

  

objc_getClassList函數:獲取已註冊的類定義的列表。我們不能假設從該函數中獲取的類對象是繼承自NSObject體系的,所以在這些類上調用方法是,都應該先檢測一下這個方法是否在這個類中實現。

下麵代碼演示了該函數的用法:

int numClasses;

Class * classes = NULL;

  

numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
    classes = malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    NSLog(@"number of classes: %d", numClasses);

    for (int i = 0; i < numClasses; i++) {

        Class cls = classes[i];
        NSLog(@"class name: %s", class_getName(cls));
    }

    free(classes);
}

輸出結果如下:

2014-10-23 16:20:52.589 RuntimeTest[8437:188589] number of classes: 1282

2014-10-23 16:20:52.589 RuntimeTest[8437:188589] class name: DDTokenRegexp

2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: _NSMostCommonKoreanCharsKeySet

2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: OS_xpc_dictionary

2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSFileCoordinator

2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSAssertionHandler

2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: PFUbiquityTransactionLogMigrator

2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSNotification

2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSKeyValueNilSetEnumerator

2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: OS_tcp_connection_tls_session

2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: _PFRoutines

......還有大量輸出

  

 獲取類定義的方法有三個:objc_lookUpClass, objc_getClass和objc_getRequiredClass。如果類在運行時未註冊,則objc_lookUpClass會返回nil,而objc_getClass會調用類處理回調,並再次確認類是否註冊,如果確認未註冊,再返回nil。而objc_getRequiredClass函數的操作與objc_getClass相同,只不過如果沒有找到類,則會殺死進程。

● objc_getMetaClass函數:如果指定的類沒有註冊,則該函數會調用類處理回調,並再次確認類是否註冊,如果確認未註冊,再返回nil。不過,每個類定義都必須有一個有效的元類定義,所以這個函數總是會返回一個元類定義,不管它是否有效。

 

本文轉自 http://southpeak.github.io/blog/2014/10/25/objective-c-runtime-yun-xing-shi-zhi-lei-yu-dui-xiang/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CSS製作三角形和按鈕 用上一篇博文中關於邊框樣式的知識點,能製作出三角形和按鈕。 我先說如何製作三角形吧,相信大家在平時逛網站的時候都會看到一些導航欄中的三角形吧,比如說: 網易首頁的頭部菜單欄中,也會有這樣的三角形 當滑鼠經過時,三角形會垂直翻轉,如下 現在我分享製作三角形的做法,主要是利用邊框 ...
  • 對AngularJS的作用域做深入剖析,該隨筆主要分為兩大板塊:JavaScript原型鏈、AngularJS作用域。 ...
  • 對於CSS的學習到此就告一段落了,其中自己感覺在CSS的學習中最有效的方法還是去進行大量的案例仿寫,這樣我們可以學習別的人是怎樣佈局的,不會不要緊,可以多去學習借鑒一下別人的經驗啊。 這兩天又接觸了onmouseover事件和onmouseout事件,一直以為它們只是簡單的分別實現滑鼠指針移動到元素 ...
  • 寫在前面本人才是開始學習前端的菜鳥有很多不懂的忘大神指點 html全名Hyper TextMarkup Language 下麵說說常用的到的html的元素標簽 h1 h2 h3 h4 h5 h6 6級的標題標簽 ul 無序列表標簽 子元素用 li ol 有序列表標簽 子元素用 li a 超鏈接標簽 ...
  • 1. 說明 管道用來轉換模板顯示的內容,應用程式中經常出現獲取數據,轉換數據,顯示數據的邏輯。管道就是用來在轉換數據階段起作用的。主要存在兩種類型的管道,pure pipe和impure pipe 2. Pure Pipe Pure Pipe,stateless,關註於純粹對象的變更,檢測到輸入值發 ...
  • 理解Method Swizzling是學習runtime機制的一個很好的機會。在此不多做整理,僅翻譯由Mattt Thompson發表於nshipster的Method Swizzling一文。 Method Swizzling是改變一個selector的實際實現的技術。通過這一技術,我們可以在運行 ...
  • 1、TableView頭視圖不隨視圖移動,頭視圖出現錯位 錯誤原因:tableView的 UITableViewStyle 沒有明確的聲明 解決方法:在tableView聲明的時候明確為 UITableViewStyleGrouped 2、分組表視圖頂部空白高度調整 實現方式: 方式一(推薦使用): ...
  • + (instancetype)createSark { return [self new];}// callerSark *sark = [Sark createSark];編譯器改寫成了形如下麵的代碼:+ (instancetype)createSark { id tmp = [self new... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...