C/C++ 屬于靜態的語言;而ObjC屬于動態語言。
什么是靜態語言?
就是在編譯器編譯后就調用函數地址,代碼結構就固定了,無法在運行的時候改變
什么是動態語言?
該語言將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理,比如: 在運行的時候才知道方法的具體實現在什么地方,程序在運行時可以改變其結構,
什么原因造成了oc 是動態語言?
答案:就是運行時
什么又是運行時?
Objective-C Runtime是一個運行時庫, 包含兩大組件——編譯器和Runtime API ,他們是匯編和c語言編寫的, 運行時為c語言提供了面向對象的功能,進而創造了OC 這門語言[ OC運行時本質上就是給OC面向對象編程提供了可能 ] , 也就是說運行時需要提供加載對象, 方法分發/轉發等功能
一: runtime編譯器
作用: 把任何 Objective-C 的代碼編譯為 Runtime 的 C 代碼。這個過程中,會把 Objective-C 的數據結構編譯為 Runtime 的 C 的數據結構。把 Objective-C 的消息傳遞編譯為 Runtime 的 C 函數調用。
下面看一下Objective_c中各種元素在Runtime 中對應的是什么類型【數據結構】?
1: 對象
Runtime 的核心數據結構就是對象,對象在 Runtime 中是一個名為 objc_object 的結構體。也就是說Objective-C 中的任何對象都是用 objc_object 來表示的。
struct objc_object {
private:
isa_t isa;
public:
// ...
}
objc_object 中最關鍵的部分是一個 isa_t 類型的 isa 變量。isa_t 類型使用了 Tagged Pointer 技術來減小內存空間的占用。isa 指針主要保存了對象關聯的類的信息。
id 的定義:
typedef struct objc_object *id;
id 是一個指向 objc_object 類型的指針,因此 id 可以代表任何對象。
2: 類
類在runtime 中表示為objc_class 類型
struct objc_class {
Class isa;
}
其中: 類的 isa 指針指向它的元類。
Class 的定義:
typedef struct objc_class *Class;
也就是說類和他的元類都是同一種類型
3: 元類
元類中保存了這個類的類方法的地址。元類的 isa 指針指向元類對應的類的父類的元類。
提個問題,類方法是通過元類找到, 一般對象方法是根據啥找到? 后續解答
4: 方法
方法在 Runtime 中用 Method 類型來表示,從 Runtime 的源碼可以看到,Method 類型是一個指向 method_t 結構體類型的指針。
typedef struct method_t *Method;
struct method_t {
SEL name; // 稱為方法子,也就是封裝了方法名的數據結構objc_selector
const char *types; //types – 表示該方法參數的類型 比如
IMP imp; // IMP 是一個函數指針,也就是函數實現地址
// ...
};
其中:
typedef struct objc_selector *SEL;
typedef id (*IMP)(id, SEL, …);
IMP 是一個函數指針,這個函數指針包含一個接收消息的對象id(self 指針), 調用方法的選標 SEL (方法名),以及不定個數的方法參數,并返回一個id,我們可以通過NSObject 類中的methodForSelector:方法就是這樣一個獲取指向方法實現IMP 的指針,
例如:
void (*setter)(id, SEL, BOOL);
setter = (void(*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
函數調用:
setter(targetList[i], @selector(setFilled:), YES);
注意,methodForSelector:是Cocoa運行時系統的提供的功能,而不是Objective-C語言本身的功能
一般我們源碼中不使用IMP,SEL ,但是在源碼中使用也是可以的,比如:
id target = getTheReceiver();
SEL method = getTheMethod();
[target performSelector:method];
5: 實例變量
typedef struct ivar_t *Ivar;
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// ...
};
6 屬性
typedef struct property_t *objc_property_t;
struct property_t {
const char *name;
const char *attributes;
};
7:協議
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
// ...
};
8:Objective-C 方法和隱含參數
對于每一個 Objective-C 的方法,例如:
- (void)printCount:(NSInteger)count {
// ...
}
編譯器都會將其編譯成一個 C 函數,上面的方法會被編譯成:
void foo(id self, SEL _cmd, int count) {
// ...
}
這個 C 函數的第一個和第二個參數就是隱含參數,在 Objective-C 的方法體中,也是可以直接使用的。編譯為 C 函數后,需要在函數聲明中明確的聲明。
9: 消息傳遞
[receiver message];
編譯器會把它編譯成對 C 函數 objc_msgSend 的調用。
objc_msgSend(receiver, @selector(message));
上述objc_class是簡單描述,下面具體描述一下:
1》 類 具體數據結構類型
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; /*協議鏈表*/
};// 存放類的結構的對象 isa 也稱為元類對象
二: Runtime API
API 主要有下面的類型:
objc_
class_
object_
method_
property_
protocol_
ivar_ ,sel_ ,imp_
1.objc_xxx 系列函數
函數名稱 函數作用
objc_getClass 獲取Class對象
objc_getProtocol 獲取某個協議
objc_getMetaClass 獲取MetaClass對象
objc_copyProtocolList 拷貝在運行時中注冊過的協議列表
objc_allocateClassPair 分配空間,創建類(僅在 創建之后,注冊之前 能夠添加成員變量)
objc_registerClassPair 注冊一個類(注冊后方可使用該類創建對象)
objc_disposeClassPair 注銷某個類
objc_allocateProtocol 開辟空間創建協議
objc_registerProtocol 注冊一個協議
objc_constructInstance 構造一個實例對象(ARC下無效)
objc_destructInstance 析構一個實例對象(ARC下無效)
objc_setAssociatedObject 為實例對象關聯對象
objc_getAssociatedObject 獲取實例對象的關聯對象
objc_removeAssociatedObjects 清空實例對象的所有關聯對象
objc_msgSend 發送ObjC消息
objc_ 系列函數關注于宏觀使用,如類與協議的空間分配,注冊,注銷等操作
2.class_xxx 系列函數
函數名稱 函數作用
class_addIvar 為類添加實例變量
class_addProperty 為類添加屬性
class_addMethod 為類添加方法
class_addProtocol 為類遵循協議
class_replaceMethod 替換類某方法的實現
class_getName 獲取類名
class_isMetaClass 判斷是否為元類
class_getSuperclass 獲取某類的父類
class_setSuperclass 設置某類的父類
class_getProperty 獲取某類的屬性
class_getInstanceVariable 獲取實例變量
class_getClassVariable 獲取類變量
class_getInstanceMethod 獲取實例方法
class_getClassMethod 獲取類方法
class_getMethodImplementation 獲取方法的實現
class_getInstanceSize 獲取類的實例的大小
class_respondsToSelector 判斷類是否實現某方法
class_conformsToProtocol 判斷類是否遵循某協議
class_createInstance 創建類的實例
class_copyIvarList 拷貝類的實例變量列表
class_copyMethodList 拷貝類的方法列表
class_copyProtocolList 拷貝類遵循的協議列表
class_copyPropertyList 拷貝類的屬性列表
class_系列函數關注于類的內部,如實例變量,屬性,方法,協議等相關問題
3.object_xxx 系列函數
函數名稱 函數作用
object_copy 對象copy(ARC無效)
object_dispose 對象釋放(ARC無效)
object_getClassName 獲取對象的類名
object_getClass 獲取對象的Class
object_setClass 設置對象的Class
object_getIvar 獲取對象中實例變量的值
object_setIvar 設置對象中實例變量的值
object_getInstanceVariable 獲取對象中實例變量的值 (ARC中無效,使用object_getIvar)
object_setInstanceVariable 設置對象中實例變量的值 (ARC中無效,使用object_setIvar)
objcet_系列函數關注于對象的角度,如實例變量
4.method_xxx 系列函數
函數名稱 函數作用
method_getName 獲取方法名
method_getImplementation 獲取方法的實現
method_getTypeEncoding 獲取方法的類型編碼
method_getNumberOfArguments 獲取方法的參數個數
method_copyReturnType 拷貝方法的返回類型
method_getReturnType 獲取方法的返回類型
method_copyArgumentType 拷貝方法的參數類型
method_getArgumentType 獲取方法的參數類型
method_getDescription 獲取方法的描述
method_setImplementation 設置方法的實現
method_exchangeImplementations 替換方法的實現
method_系列函數關注于方法內部,如果方法的參數及返回值類型和方法的實現
5.property_xxx 系列函數
函數名稱 函數作用
property_getName 獲取屬性名
property_getAttributes 獲取屬性的特性列表
property_copyAttributeList 拷貝屬性的特性列表
property_copyAttributeValue 拷貝屬性中某特性的值
property_系類函數關注與屬性*內部,如屬性的特性等
6.protocol_xxx 系列函數
函數名稱 函數作用
protocol_conformsToProtocol 判斷一個協議是否遵循另一個協議
protocol_isEqual 判斷兩個協議是否一致
protocol_getName 獲取協議名稱
protocol_copyPropertyList 拷貝協議的屬性列表
protocol_copyProtocolList 拷貝某協議所遵循的協議列表
protocol_copyMethodDescriptionList 拷貝協議的方法列表
protocol_addProtocol 為一個協議遵循另一協議
protocol_addProperty 為協議添加屬性
protocol_getProperty 獲取協議中的某個屬性
protocol_addMethodDescription 為協議添加方法描述
protocol_getMethodDescription 獲取協議中某方法的描述
7.ivar_xxx 系列函數
函數名稱 函數作用
ivar_getName 獲取Ivar名稱
ivar_getTypeEncoding 獲取類型編碼
ivar_getOffset 獲取偏移量
8.sel_xxx 系列函數
函數名稱 函數作用
sel_getName 獲取名稱
sel_getUid 注冊方法
sel_registerName 注冊方法
sel_isEqual 判斷方法是否相等
9.imp_xxx 系列函數
函數名稱 函數作用
imp_implementationWithBlock 通過代碼塊創建IMP
imp_getBlock 獲取函數指針中的代碼塊
imp_removeBlock 移除IMP中的代碼塊
還有一些方便的函數
我們可以通過NSObject的一些方法獲取運行時信息或動態執行一些消息:
isKindOfClass 和 isMemberOfClass檢查對象是否在指定的類繼承體系中;
respondsToSelector 檢查對象能否相應指定的消息;
conformsToProtocol 檢查對象是否實現了指定協議類的方法;
methodForSelector 返回指定方法實現的地址。
performSelector:withObject 執行SEL 所指代的方法。
函數調用舉例:
三: 代碼編譯實例
因為 Objective-C 的源代碼都會被編譯成 Runtime 代碼來運行,我們一樣可以通過直接編寫 Runtime 代碼的方式來編寫程序。
舉例:
我們有個類叫 ClassA:
@interface ClassA : NSObject
@property (nonatomic, assign) NSInteger count;
- (void)printCount;
@end
@implementation ClassA
-
(void)printCount {
NSLog(@"count = %@", @(self.count));
}
@end
然后來執行調用:
ClassA *a = [[ClassA alloc] init];
a.count = 100;
[a printCount];
下面來看看下面代碼轉成 Runtime 怎么寫【自己寫的】?
// 獲取到 ClassA 的 Class 對象
Class ClassA = objc_getClass("ClassA");
// 發送 alloc 和 init 消息來創建和初始化實例對象
id a = objc_msgSend(ClassA, @selector(alloc));
a = objc_msgSend(a, @selector(init));
// 獲取到屬性 count 背后的實例變量
Ivar countIvar = class_getInstanceVariable(ClassA, "_count");
assert(countIvar);
// 通過實例對象首地址 + 實例變量的地址偏移量得到實例變量的指針地址,然后通過 * 取指針值操作符修改指針指向的地址的值。
CFTypeRef aRef = CFBridgingRetain(a);
int *countIvarPtr = (int *)((uint8_t *)aRef + ivar_getOffset(countIvar));
*countIvarPtr = 100;
CFBridgingRelease(aRef);
// 給 a 對象發送 printCount 消息,打印 count 屬性的值
objc_msgSend(a, @selector(printCount));
再看看通過clang編譯后輸出的和我們寫的有什么區別?
執行命令: clang -rewrite-objc main.m
結果如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 一次性發送消息init和alloc 進而得到ClassA的對象
ClassA *a = ((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("alloc")), sel_registerName("init"));
// 執行setCount 方法進行賦值
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)a, sel_registerName("setCount:"), (NSInteger)100);
// 執行printCount 方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("printCount"));
}
return 0;
}