(一)運(yùn)行時(shí)(runtime)
-
運(yùn)行時(shí)(runtime)是一種面向?qū)ο蟮木幊陶Z言的運(yùn)行環(huán)境. -
運(yùn)行時(shí)(runtime)是Objective-C的核心,Objective-C就是基于運(yùn)行時(shí)(runtime)的. -
Objective-C是基于C語言加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機(jī)制的動(dòng)態(tài)語言. -
Objective-C需要一個(gè)編譯器,還需要Runtime系統(tǒng)來動(dòng)態(tài)創(chuàng)建類和對象,進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)。-
Objective-C最主要的特點(diǎn)就是在程序運(yùn)行時(shí), 以發(fā)送消息的方式調(diào)用方法.
-
-
C語言的函數(shù)調(diào)用方式是使用靜態(tài)綁定(static binding).在編譯期就能決定運(yùn)行時(shí)所應(yīng)調(diào)用的函數(shù). - 在
Objective-C中,如果向某對象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法。在底層,所有方法都是普通的C語言函數(shù)。 - 頭文件
#import <objc/runtime.h>#import <objc/message.h>...
1.OC方法調(diào)用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testDemo:nil];
}
- (void)testDemo:(id)param {
NSLog(@"%s",__func__);
}
2.分析 [self testDemo];
-
self叫做接收者(receiver) -
testDemo叫做選擇子(selector) -
選擇子與參數(shù)合起來稱為消息(message) - 編譯器看到此消息后,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用
- 所調(diào)用的函數(shù)是消息傳遞機(jī)制中的核心函數(shù),叫做
objc_msgSend()
3.運(yùn)行時(shí)(runtime)消息發(fā)送 == OC方法調(diào)用底層實(shí)現(xiàn)
-
運(yùn)行時(shí)(runtime)消息發(fā)送函數(shù)- 提示 :
OBJC2_UNAVAILABLE是一個(gè)Apple對Objc系統(tǒng)運(yùn)行版本進(jìn)行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本
- 提示 :
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 消息發(fā)送 : iOS8以后的特殊寫法
((void(*)(id,SEL,id))objc_msgSend)(self,@selector(testDemo:),nil);
}
- (void)testDemo:(id)param {
NSLog(@"%s",__func__);
}
(二)數(shù)據(jù)類型分析
1.SEL : 方法選擇器(指向objc_selector結(jié)構(gòu)體的指針)
typedef struct objc_selector *SEL;
- 可以通過
Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName()函數(shù)來獲取一個(gè)SEL類型的方法選擇器. - 如果知道
selector對應(yīng)的方法名是什么,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為OC字符串.
2.id : 對象(指向objc_object結(jié)構(gòu)體的指針)
// objc_object結(jié)構(gòu)體
struct objc_object {
// id的成員 : isa
Class isa OBJC_ISA_AVAILABILITY;
};
// 指向objc_object結(jié)構(gòu)體指針
typedef struct objc_object *id;
- 包含一個(gè)
Class isa成員. - 根據(jù)
isa指針就可以找到對象所屬的類.
3.Class : 對象所屬的類(指向objc_class結(jié)構(gòu)體的指針)
typedef struct objc_class *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;
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;
4.Method : 方法(指向objc_method結(jié)構(gòu)體的指針)
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
-
objc_method存儲(chǔ)了方法名(method_name)、方法類型(method_types)和方法實(shí)現(xiàn)(method_imp)等信息. -
method_imp的數(shù)據(jù)類型是IMP,它是一個(gè)函數(shù)指針.
5.IMP : 方法實(shí)現(xiàn)(指向方法實(shí)現(xiàn)的指針)
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
-
IMP本質(zhì)上就是一個(gè)函數(shù)指針,指向方法的實(shí)現(xiàn). - 當(dāng)向某個(gè)對象發(fā)送一條信息時(shí),可以由這個(gè)函數(shù)指針來指定方法的實(shí)現(xiàn),它最終就會(huì)執(zhí)行那段代碼.
6.Ivar : 實(shí)例變量(指向objc_ivar結(jié)構(gòu)體的指針)
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
}
7.Cache : 緩存(指向結(jié)構(gòu)體objc_cache的指針)
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;
}
- 其實(shí)就是一個(gè)存儲(chǔ)
Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能.

8.類關(guān)系圖

- 實(shí)例對象在運(yùn)行時(shí)被表示成
objc_object類型結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部有個(gè)isa指針指向objc_class結(jié)構(gòu)體。 -
objc_class內(nèi)部保存了類的變量和方法列表以及其他一些信息,并且還有一個(gè)isa指針。這個(gè)isa指針會(huì)指向metaClass(元類),元類里保存了這個(gè)類的類方法列表。 - 元類里也有一個(gè)isa指針,這個(gè)isa指針,指向的是根元類,根元類的isa指針指向自己
(三)objc_class中信息查看
1.Class : 對象所屬的類(指向objc_class結(jié)構(gòu)體的指針)
typedef struct objc_class *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;
long info OBJC2_UNAVAILABLE; // 位標(biāo)識(shí)
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;
類型編碼參考地址:http://www.itdecent.cn/p/f4129b5194c0
2.代碼演練
(1)獲取類名
/**
獲取類名
@param cls 要獲取類名的類
@return 類名
*/
+ (NSString *)getClassName:(Class)cls {
// 獲取類名(C語言類型)
const char *cName = class_getName(cls);
// OC類型類名
NSString *className = [NSString stringWithUTF8String:cName];
return className;
}
(2)獲取成員變量列表
/**
獲取成員變量列表(帶下劃線的都獲取)
@param cls 要獲取成員變量列表的類
@return 成員變量數(shù)組(成員變量名字和類型組合的字典數(shù)組)
*/
+ (NSArray *)getIvarList:(Class)cls {
// 成員變量個(gè)數(shù)
unsigned int count;
// 獲取所有的成員變量
Ivar *ivarList = class_copyIvarList(cls, &count);
// 準(zhǔn)備數(shù)組容器
NSMutableArray *ivarArrM = [NSMutableArray arrayWithCapacity:count];
// 遍歷成員變量
for (NSInteger i = 0; i < count; i++) {
// 獲取成員變量名字
const char *ivarName = ivar_getName(ivarList[i]);
// 獲取成員變量類型
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
// 成員變量名字和類型組合的字典容器
NSMutableDictionary *ivarDictM = [NSMutableDictionary dictionary];
ivarDictM[@"name"] = [NSString stringWithUTF8String:ivarName];
ivarDictM[@"type"] = [NSString stringWithUTF8String:ivarType];
// 添加到數(shù)組容器
[ivarArrM addObject:ivarDictM];
}
free(ivarList);
return ivarArrM.copy;
}
(3)獲取屬性列表
/**
獲取屬性列表(有setter和getter方法的屬性)
@param cls 要獲取屬性列表的類
@return 屬性數(shù)組(屬性名字和屬性描述的字典數(shù)組)
*/
+ (NSArray *)getPropertyList:(Class)cls {
// 成員屬性個(gè)數(shù)
unsigned int count;
// 獲取所有成員變量
objc_property_t *propertyList = class_copyPropertyList(cls, &count);
// 成員屬性容器
NSMutableArray *propertyArrM = [NSMutableArray arrayWithCapacity:count];
// 遍歷成員屬性
for (NSInteger i = 0; i < count; i++) {
// 獲取屬性名字和屬性的屬性描述
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
NSString *attr = [NSString stringWithUTF8String:property_getAttributes(property)];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"name"] = name;
dict[@"attr"] = attr;
// 添加到數(shù)組容器
[propertyArrM addObject:[dict copy]];
}
free(propertyList);
return propertyArrM.copy;
}
(4)類屬性中的屬性示例
@property (nonatomic,strong, setter=setPublicProperty:) NSArray *publicProperty01;
- property_getAttributes輸出為:
attr = "T@\"NSArray\",&,N,SsetPublicProperty:,V_publicProperty01";
name = publicProperty01;
其中 attr 的解釋為:
- T 代表類型標(biāo)識(shí)
- @ 代表為對象類型
- NSArray 表示其實(shí)際類型
- & 代表 retain 強(qiáng)引用(copy 用 C, weak 用 W)
- N 代表 nonatomic (代表 natomic)
- SsetPublicProperty: 前面大寫S代表指定了 setter,后面跟著代表具體方法
- V_publicProperty01 V代表其對應(yīng)的成員,后面為成員的名字
(5)獲取方法列表
/**
獲取類的實(shí)例方法 : 屬性的setter和getter方法,對象方法,不包括類方法
@param cls 要獲取類的實(shí)例方法的類
@return 方法數(shù)組
*/
+ (NSArray *)getMethodList:(Class)cls {
// 實(shí)例方法個(gè)數(shù)
unsigned int count;
// 獲取所有方法(不包括類方法)
Method *methodList = class_copyMethodList(cls, &count);
// 方法容器
NSMutableArray *methodArrM = [NSMutableArray arrayWithCapacity:count];
// 遍歷所有方法
for (NSInteger i = 0; i < count; i++) {
// 獲取數(shù)據(jù)
NSString *name = [NSString stringWithString:NSStringFromSelector(method_getName(method))];
NSString *type = [NSString stringWithUTF8String:method_getTypeEncoding(method)];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"name"] = name;
dict[@"type"] = type;
// 添加到數(shù)組
[methodArrM addObject:[dict copy]];
}
// 通過元類獲取類方法
Class metaCls = objc_getMetaClass(class_getName(cls));
methods = class_copyMethodList(metaCls, &count);
for (NSInteger i = 0; i < count; i++) {
Method method = methods[i];
// 獲取數(shù)據(jù)
NSString *name = [NSString stringWithString:NSStringFromSelector(method_getName(method))];
NSString *type = [NSString stringWithUTF8String:method_getTypeEncoding(method)];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"name"] = name;
dict[@"type"] = type;
// 添加到數(shù)組
[arrayM addObject:[dict copy]];
}
free(methodList);
return methodArrM.copy;
}
(6)方法類型編碼示例
/// 公有方法2
- (void)publicMethodd02:(NSString *)str append:(int)age;
- method_getTypeEncoding 的輸出為:
name = "publicMethodd02:append:";
type = "v28@0:8@16i24";
其中type解釋為:
- v 代表返回值為void
- 28 代表整個(gè)方法參數(shù)占位的總長度
- @0 @代表對象,objc_msgSend 函數(shù)傳入的第1個(gè)參數(shù)(self), 后面的0代表位置0開始
- :8 :代表SEL,objc_msgSend 函數(shù)傳入的第2個(gè)參數(shù)(self),后面的8代表位置8開始
- @16 @代表第1個(gè)參數(shù)的類型為對象類型,后面的16代表位置8開始
- i24 i代表第2個(gè)參數(shù)的類型為int類型,后面的24代表位置24開始
(7)獲取協(xié)議列表
/**
獲取類的協(xié)議列表
@param cls 獲取協(xié)議列表的類
@return 協(xié)議數(shù)組
*/
+ (NSArray *)getProtocolList:(Class)cls {
// 協(xié)議個(gè)數(shù)
unsigned int count;
// 獲取協(xié)議列表
Protocol * __unsafe_unretained *protocolList = class_copyProtocolList(cls, &count);
// 協(xié)議容器
NSMutableArray *protocolArrM = [NSMutableArray arrayWithCapacity:count];
// 遍歷協(xié)議列表
for (NSInteger i = 0; i < count; i++) {
// 獲取協(xié)議名字
const char *protocolName = protocol_getName(protocolList[i]);
// 添加到協(xié)議容器
[protocolArrM addObject:[NSString stringWithUTF8String:protocolName]];
}
free(protocolList);
return protocolArrM.copy;
}
(四)問答
問答1 : 為什么id可以指向任何對象?
- 類是用
objc_class結(jié)構(gòu)體表示的,對象是用objc_object結(jié)構(gòu)體表示的,objc_class繼承自objc_object,而id就是objc_object類型的,
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
問答2 : 分類中是否可以定義屬性?
- 可以定義屬性,但是系統(tǒng)不會(huì)實(shí)現(xiàn)setter和getter
- 定義的屬性無法存值,因?yàn)闆]有成員變量鏈表
struct objc_ivar_list *ivars - 但是,可以使用運(yùn)行時(shí)的關(guān)聯(lián)給分類添加屬性
typedef struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
} category_t;