一、前言
OC是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。即說明OC需要一個編譯器和一個運行時系統來編譯代碼,這個運行時系統即Runtime,它是用C和匯編寫的,并使C擁有了面向對象的能力。其優勢在于:靈活性,比如我們可以把消息轉發給我們想要的對象
runtime源碼下載地址:https://opensource.apple.com/tarballs/objc4/
Runtime庫主要做了以下事情:
1、封裝: 在這個庫中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝后,我們就可以在程序運行時創建,檢查,修改類、對象和它們的方法了
2、找出方法的最終執行代碼:當程序執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。
二、類與對象的數據結構
1、Class
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針。查看objc/runtime.h中objc_class結構體的定義如下
struct objc_class {
? ? struct objc_class *isa; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //is a指針
? ? struct objc_class *super_class; ? ? ? ? ? ? ? ?//父類
? ? const char *name; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //類名
? ? long version;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 類的版本信息,默認為0
? ? long info;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 類信息,供運行期使用的一些位標識
? ? long instance_size; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 該類的實例變量大小
? ? struct objc_ivar_list *ivars;? ? ? ? ? ? ? ? ? ? ? // 該類的成員變量鏈表
#if defined(Release3CompatibilityBuild)
? ? struct objc_method_list *methods;? ? ? ? // 方法定義的鏈表
#else
? ? struct objc_method_list **methodLists; // 方法定義的鏈表
#endif
? ? struct objc_cache *cache;? ? ? ? ? ? ? ? ? ? ? // 方法緩存
? ? struct objc_protocol_list *protocols;? ? ? // 協議鏈表
};
在以上的定義中,有幾個字段是需要注意的:
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、在后期的操作中,如果再以`[[NSArrayalloc] init]`這種方式來創建數組,則會直接從cache中取出相應的方法,直接調用。
這里專門說下objc_object:
objc_object是表示一個類的實例的結構體,它的定義如下:
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 */?
? ? unsigned int occupied;
? ? Method buckets[1];
};
該結構體的字段描述如下:
mask:當前能達到的最大index(從0開始的),所以緩存的size(total)是mask+1。
occupied:一個整數,指定實際占用的緩存bucket的總數。因為緩存是以散列表的形式存在的,所以會有空槽,而occupied表示當前被占用的數目
buckets:指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續的。這個數組可能會隨著時間而增長。
2、元類(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指針是指向它自己。這樣就形成了一個完美的閉環。
類與對象操作函數
runtime提供了大量的函數來操作類與對象。類的操作方法大部分是以class_為前綴的,而對象的操作方法大部分是以objc_或object_為前綴。下面我們將根據這些方法的用途來分類討論這些方法的使用。
類相關操作函數
我們可以回過頭去看看objc_class的定義,runtime提供的操作類的方法主要就是針對這個結構體中的各個字段的。下面我們分別介紹這一些的函數。并在最后以實例來演示這些函數的具體用法。
父類(super_class)和元類(meta-class)
父類和元類操作的函數主要有:
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );
成員變量(ivars)及屬性
在objc_class中,所有的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個數組,數組中每個元素是指向Ivar(變量信息)的指針。runtime提供了豐富的函數來操作這一字段。大體上可以分為以下幾類:
1.成員變量操作函數,主要包含以下函數:
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls,unsigned int *outCount );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char* name, size_t size, uint8_t alignment, const char *types );
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls,const char *name );
class_getInstanceVariable函數,它返回一個指向包含name指定的成員變量信息的objc_ivar結構體的指針(Ivar)。
class_copyIvarList函數,它返回一個指向成員變量信息的數組,數組中每個元素是指向該成員變量信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變量。outCount指針返回數組的大小。需要注意的是,我們必須使用free()來釋放這個數組。
Objective-C不支持往已存在的類中添加實例變量,因此不管是系統庫提供的類,還是我們自定義的類,都無法動態添加成員變量。但如果我們通過運行時來創建一個類的話,又應該如何給它添加成員變量呢?這時我們就可以使用class_addIvar函數了。不過需要注意的是,這個方法只能在objc_allocateClassPair函數與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。
與成員變量不同的是,我們可以為類動態添加方法,不管這個類是否已存在。另外,參數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)
協議相關的操作包含以下函數:
// 添加協議
BOOLclass_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現指定的協議
BOOLclass_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現的協議列表
Protocol * class_copyProtocolList ( Class cls,unsignedint*outCount );
class_conformsToProtocol函數可以使用NSObject類的conformsToProtocol:方法來替代。
class_copyProtocolList函數返回的是一個數組,在使用后我們需要使用free()手動釋放
舉個例子
TestClass.h文件
@interface TestClass : NSObject
@property (nonatomic, strong) NSArray *dataArray;
@property (nonatomic, copy) NSString *string;
- (void)test1;
- (void)test2;
+ (void)classTest1;
@end
TestClass.m文件
#import "TestClass.h"
@interface TestClass(){
NSInteger innerInteger;
NSString? *innerString;
}
@property(nonatomic,assign) NSInteger integer;
- (void)test3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation TestClass
+ (void)classTest1{
NSLog(@"call classTest1");
}
- (void)test1{
NSLog(@"call test1");
}
- (void)test2{
NSLog(@"call test2");
}
- (void)test3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
#import <Foundation/Foundation.h>
#import "TestClass.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestClass *myClass = [[TestClass 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(@"%s is %@ a meta-class",class_getName(cls), (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(@"%s is %@ a meta-class",class_getName(meta_class), (class_isMetaClass(meta_class) ? @"" : @"not"));
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(@"get? instace variable by name,name is %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: %@",NSStringFromSelector( method_getName(method)));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector(test1));
if (method1 != NULL) {
? ? NSLog(@"method %@", NSStringFromSelector(method_getName(method1)));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
if (classMethod != NULL) {
? ? NSLog(@"class method : %@", NSStringFromSelector(method_getName(classMethod)));
}
NSLog(@"MyClass is%@ responsd to selector: test3WithArg1:arg2:", class_respondsToSelector(cls, @selector(test3WithArg1:arg2:)) ? @"" : @" not");
IMP imp = class_getMethodImplementation(cls, @selector(test1));
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;
}
輸出內容:
2017-08-23 10:43:32.859851+0800 RuntimeTest[69340:1847703] class name: TestClass
2017-08-23 10:43:32.859886+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.859904+0800 RuntimeTest[69340:1847703] super class name: NSObject
2017-08-23 10:43:32.859913+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.859947+0800 RuntimeTest[69340:1847703] TestClass is not a meta-class
2017-08-23 10:43:32.859956+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.859965+0800 RuntimeTest[69340:1847703] TestClass's meta-class is TestClass
2017-08-23 10:43:32.859981+0800 RuntimeTest[69340:1847703] TestClass is? a meta-class
2017-08-23 10:43:32.859988+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.859995+0800 RuntimeTest[69340:1847703] instance size: 48
2017-08-23 10:43:32.860002+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.862882+0800 RuntimeTest[69340:1847703] instance variable's name: innerInteger at index: 0
2017-08-23 10:43:32.862911+0800 RuntimeTest[69340:1847703] instance variable's name: innerString at index: 1
2017-08-23 10:43:32.862920+0800 RuntimeTest[69340:1847703] instance variable's name: _dataArray at index: 2
2017-08-23 10:43:32.862929+0800 RuntimeTest[69340:1847703] instance variable's name: _string at index: 3
2017-08-23 10:43:32.862936+0800 RuntimeTest[69340:1847703] instance variable's name: _integer at index: 4
2017-08-23 10:43:32.862968+0800 RuntimeTest[69340:1847703] instace variable _string
2017-08-23 10:43:32.862977+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.862988+0800 RuntimeTest[69340:1847703] property's name: integer
2017-08-23 10:43:32.862997+0800 RuntimeTest[69340:1847703] property's name: dataArray
2017-08-23 10:43:32.863005+0800 RuntimeTest[69340:1847703] property's name: string
2017-08-23 10:43:32.863846+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.864015+0800 RuntimeTest[69340:1847703] method's signature: test1
2017-08-23 10:43:32.864060+0800 RuntimeTest[69340:1847703] method's signature: test2
2017-08-23 10:43:32.864123+0800 RuntimeTest[69340:1847703] method's signature: test3WithArg1:arg2:
2017-08-23 10:43:32.864154+0800 RuntimeTest[69340:1847703] method's signature: setDataArray:
2017-08-23 10:43:32.864169+0800 RuntimeTest[69340:1847703] method's signature: .cxx_destruct
2017-08-23 10:43:32.864212+0800 RuntimeTest[69340:1847703] method's signature: string
2017-08-23 10:43:32.864234+0800 RuntimeTest[69340:1847703] method's signature: setString:
2017-08-23 10:43:32.864244+0800 RuntimeTest[69340:1847703] method's signature: dataArray
2017-08-23 10:43:32.864252+0800 RuntimeTest[69340:1847703] method's signature: integer
2017-08-23 10:43:32.864261+0800 RuntimeTest[69340:1847703] method's signature: setInteger:
2017-08-23 10:43:32.864270+0800 RuntimeTest[69340:1847703] method test1
2017-08-23 10:43:32.864287+0800 RuntimeTest[69340:1847703] MyClass is responsd to selector: test3WithArg1:arg2:
2017-08-23 10:43:32.864295+0800 RuntimeTest[69340:1847703] call test1
2017-08-23 10:43:32.864350+0800 RuntimeTest[69340:1847703] ==========================================================
2017-08-23 10:43:32.864375+0800 RuntimeTest[69340:1847703] protocol name: NSCopying
2017-08-23 10:43:32.864384+0800 RuntimeTest[69340:1847703] protocol name: NSCoding
2017-08-23 10:43:32.864392+0800 RuntimeTest[69340:1847703] MyClass is responsed to protocol NSCoding
2017-08-23 10:43:32.864399+0800 RuntimeTest[69340:1847703] ==========================================================
動態創建類和對象
runtime真正強大之處在于可以在運行時的創建類和對象。
動態創建類
動態創建類涉及到以下幾個函數:
// 創建一個新類和元類
Class objc_allocateClassPair ( Class superclass,const char *name, size_t extraBytes );
// 銷毀一個類及其相關聯的類
voidobjc_disposeClassPair ( Class cls );
// 在應用中注冊由objc_allocateClassPair創建的類
voidobjc_registerClassPair ( Class cls );
objc_allocateClassPair函數:如果我們要創建一個根類,則superclass指定為Nil。extraBytes通常指定為0,該參數是分配給類和元類對象尾部的索引ivars的字節數。
為了創建一個新類,我們需要調用objc_allocateClassPair。然后使用諸如class_addMethod,class_addIvar等函數來為新創建的類添加方法、實例變量和屬性等。完成這些后,我們需要調用objc_registerClassPair函數來注冊類,之后這個新類就可以在程序中使用了。實例方法和實例變量應該添加到類自身上,而類方法應該添加到類的元類上。
objc_disposeClassPair函數用于銷毀一個類,不過需要注意的是,如果程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。
void submethod(id self, SEL _cmd){
NSLog(@"new submethod");
}
void replaceMethod(id self, SEL _cmd){
NSLog(@"replace test1");
}
Class cls = objc_allocateClassPair(TestClass.class, "TestSubClass", 0);
//添加方法
class_addMethod(cls, @selector(submethod1), (IMP)submethod, "v@:");
//替換父類方法
class_replaceMethod(cls, @selector(test1), (IMP)replaceMethod, "v@:");
//添加參數
class_addIvar(cls, "_name", sizeof(NSString *), log(sizeof(NSString *)), "i");
//添加屬性
objc_property_attribute_t type = {"T", "@\"NSString\""};? ? //type
objc_property_attribute_t ownership = { "C", "" };? ? ? ? ? // C = copy
objc_property_attribute_t backingivar = { "V", "_property2"};? // V = variable name
//? ? ? ? objc_property_attribute_t ownership = { "N", "" };? ? ? ? // N = nonatomic
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(test1)];
小結
在這一章中我們介紹了Runtime運行時中與類和對象相關的數據結構,通過這些數據函數,我們可以管窺Objective-C底層面向對象實現的一些信息。另外,通過豐富的操作函數,可以靈活地對這些數據進行操作。