簡(jiǎn)介
objective-c(簡(jiǎn)寫(xiě)objc)屬于動(dòng)態(tài)語(yǔ)言,不像C語(yǔ)言一樣靜態(tài)編譯期,就確定了調(diào)用方法的指針,而objc所謂的方法調(diào)用只是消息的發(fā)送,如下:
[recevier message];
//轉(zhuǎn)換為
objc_msgSend(recevier, selector);
//如果存在參數(shù)如下:
objc_msgSend(recevier, selector, arg1, arg2, ...);
因此具體的調(diào)用的函數(shù)指針是在運(yùn)行期確定的,并且在此期間還可以動(dòng)態(tài)修改最終調(diào)用的函數(shù)指針位置,如isa-swizzling及method-swizzling技術(shù),或者若未存在此方法,可以動(dòng)態(tài)的添加方法;
另外,objc源碼是開(kāi)源的,且?guī)缀跞渴褂?code>C語(yǔ)言實(shí)現(xiàn)(有些使用了匯編實(shí)現(xiàn)),可以從蘋(píng)果開(kāi)源官方網(wǎng)站獲取此代碼。
與runtime交互
objc 從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互,分別是通過(guò) Objective-C 源代碼,通過(guò) Foundation 框架的NSObject類定義的方法,通過(guò)對(duì) runtime 函數(shù)的直接調(diào)用。
Objective-C源代碼
大部分都是編寫(xiě)objc代碼,其他都交由runtime系統(tǒng)來(lái)后臺(tái)執(zhí)行具體的操作,如[recevier message]轉(zhuǎn)為調(diào)用objc_msgSend方法來(lái)執(zhí)行;
NSObject類
Cocoa中大多數(shù)類都繼承自NSObject類(NSProxy類是個(gè)例外,它是一個(gè)抽象超類,來(lái)充當(dāng)其他對(duì)象或尚不存在的對(duì)象的替代者),自然也繼承了其方法,如description方法可重載實(shí)現(xiàn)定義類描述;還提供了一些運(yùn)行時(shí)獲取類信息并檢查一些特性:如class獲取類對(duì)象; isMemberOfClass檢查類對(duì)象是否在給定類的實(shí)例,isKindOfClass檢查類實(shí)例是否為給定類或者類的繼承類實(shí)例;respondsToSelector:檢查對(duì)象能否響應(yīng)指定的消息;conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;methodForSelector:則返回指定方法實(shí)現(xiàn)的地址。
Runtime函數(shù)
runtime系統(tǒng)是一個(gè)由一系列數(shù)據(jù)結(jié)構(gòu)和函數(shù)組成,具有公共接口的動(dòng)態(tài)共享庫(kù)。其構(gòu)成了NSObject類的基礎(chǔ),大部分還是使用更上層的接口編程,一般會(huì)用在hook接口或者與其他語(yǔ)言橋接等場(chǎng)景;
消息
objc方法調(diào)用是以消息發(fā)送的形式傳遞的并獲取到相應(yīng)的函數(shù)指針,從而實(shí)現(xiàn)函數(shù)的直接調(diào)用,具體使用的objc方法為:
void objc_msgSend(receiver, selector, arg1, ....);
其中發(fā)送調(diào)用時(shí)隱含了receiver=self, selector=_cmd,這兩個(gè)參數(shù)是由編譯器編譯時(shí)自動(dòng)添加上的;
而方法中的
super關(guān)鍵詞接收到消息時(shí),編譯器會(huì)創(chuàng)建一個(gè)objc_super的結(jié)構(gòu)體,如下:struct objc_super { id receiver; Class class; };這個(gè)結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定超類的定義。但
receiver仍然是self本身,這點(diǎn)需要注意,因?yàn)楫?dāng)我們想通過(guò)[super class]獲取超類時(shí),編譯器只是將指向self的id指針和class的SEL傳遞給了objc_msgSendSuper函數(shù),因?yàn)橹挥性?code>NSObject類才能找到class方法,然后class方法調(diào)用object_getClass(),接著調(diào)用objc_msgSend(objc_super->receiver, @selector(class)),傳入的第一個(gè)參數(shù)是指向self的id指針,與調(diào)用[self class]相同,所以我們得到的永遠(yuǎn)都是self的類型。劃重點(diǎn)以上表明:
在同一對(duì)象中調(diào)用
[self class]和[super class]返回的都是self的Class類isa指針;@implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
方法的調(diào)用流程

檢測(cè)這個(gè)
selector是不是要忽略的。比如 Mac OS X 開(kāi)發(fā),有了垃圾回收就不理會(huì)retain,release這些函數(shù)了。檢測(cè)這個(gè) target 是不是
nil對(duì)象。ObjC 的特性是允許對(duì)一個(gè)nil對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash,因?yàn)闀?huì)被忽略掉。如果上面兩個(gè)都過(guò)了,那就通過(guò)獲取
self實(shí)例對(duì)象的isa獲取類結(jié)構(gòu)體(包含父類super_class、cache緩存及方法列表);優(yōu)先從
cache緩存中查找,若查找到就跳轉(zhuǎn)到對(duì)應(yīng)的IMP函數(shù)指針去執(zhí)行;若
cache緩存中未找到,就去methodLists方法列表中查找;如果方法列表中未找到,就去
super_class父類結(jié)構(gòu)體中查找,一直找到NSObject類為止;如果還找不到就要開(kāi)始進(jìn)入消息轉(zhuǎn)發(fā)流程了,后面會(huì)提到。
獲取方法地址
直接獲取到方法地址(IMP函數(shù)指針)可有效避免runtime消息發(fā)送流程,對(duì)于大量同函數(shù)調(diào)用的情況,可提升調(diào)用效率,但不太常見(jiàn);
NSObject提供了通過(guò)id及selector來(lái)獲取對(duì)應(yīng)IMP函數(shù)指針(包括實(shí)例對(duì)象或者類對(duì)象的函數(shù)指針)的方法:
- (IMP)methodForSelector:(SEL)aSelector;
該方法不屬于objc的本身的特性,而為cocoa runtime的特性,查看源碼實(shí)際為NSObject的類方法,通過(guò)class_getMethodImplementation方法獲取,源碼如下:
+ (IMP)instanceMethodForSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return class_getMethodImplementation(self, sel);
}
具體的IMP函數(shù)指針調(diào)用,需要轉(zhuǎn)換為具體的函數(shù)指針類型,包括返回值類型,參數(shù)列表及其類型,如下:
IMP imp = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
//無(wú)返回值
void (*setter)(id, SEL, BOOL) = (void *)imp;
//存在返回值,則指定返回值類型即可
id (*setter)(id, SEL, BOOL) = (void *)imp;
//or
int (*setter)(id, SEL, BOOL) = (void *)imp;
消息轉(zhuǎn)發(fā)
對(duì)于[objc message]消息發(fā)送調(diào)用形式,若找不到該方法,在編譯期就會(huì)報(bào)錯(cuò);若是通過(guò)[objc performSelector:@selectro(message)]形式,需要在運(yùn)行期才能確定是否能響應(yīng)該消息,若無(wú)法響應(yīng)就會(huì)報(bào)找不到該實(shí)例方法,進(jìn)而引發(fā)崩潰;
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[KVCTest test1]: unrecognized selector sent to instance 0x600000c4e8e0'
*** First throw call stack:
xxxx
因此通常會(huì)通過(guò)respondsToSelector:方法來(lái)判定是否能響應(yīng)此消息,才觸發(fā)消息的發(fā)送;
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
消息轉(zhuǎn)發(fā)步驟如下:
- 動(dòng)態(tài)方法解析
- 備用接收者
-
完整轉(zhuǎn)發(fā)
image.png
動(dòng)態(tài)方法解析
對(duì)象在緩存及方法列表中未找到相應(yīng)的方法后,runtime首先會(huì)調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel(實(shí)例對(duì)象)或者+ (BOOL)resolveClassMethod:(SEL)sel(類對(duì)象),若在此方法通過(guò)class_addMethod方法添加方法并且返回YES(或者在父類往上繼承體系添加也可),則會(huì)調(diào)用添加的方法,具體的方法使用如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
其中class_addMethod涉及到類型編碼(Type Encodings),其指定的是imp(id, SEL, ...)方法返回值+輸入?yún)?shù)組合類型,為字符數(shù)組(每個(gè)對(duì)應(yīng)一個(gè)字符);types變量第一個(gè)輸入?yún)?shù)一定是id對(duì)象類型,則類型為@;第二個(gè)參數(shù)為SEL方法選擇器類型,則為:;對(duì)于其他類型,具體見(jiàn)官網(wǎng),不過(guò)runtime也提供了相應(yīng)的獲取類型編碼的方法:
const char * method_getTypeEncoding(Method m);
使用如下:
@property(nonatomic, copy) NSString *propertyName;
@property(nonatomic, copy, class) NSString *className;
@dynamic propertyName;
@dynamic className;
+ (NSString *)name {
return @"class test";
}
- (NSString *)description {
return @"test";
}
//動(dòng)態(tài)方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(propertyName)) {
// const char *typeCode = method_getTypeEncoding(class_getInstanceMethod([self class], @selector(description)));
const char *typeCode = "@@:";
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(description)), typeCode);
return YES;
} else if (sel == @selector(test1)) {
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(test)), "i@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(className)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(name)), "@@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
@dynamic關(guān)鍵字在類的實(shí)現(xiàn)文件中修飾一個(gè)屬性,表明系統(tǒng)不用自動(dòng)生成setter/getter方法,由自己去動(dòng)態(tài)實(shí)現(xiàn);
注意:[self class] object_getClass(self) object_getClass([self class])的區(qū)別,先上源碼:
+ (id)self {
return (id)self;
}
- (id)self {
return self;
}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
若self為實(shí)例對(duì)象時(shí),[self class]返回的類對(duì)象,等價(jià)于object_getClass(self),object_getClass([self class])返回的是元類;
若self為類對(duì)象時(shí),[self class]返回的是self自身即類對(duì)象,且object_getClass(self)與object_getClass([self class])等價(jià),且都返回的是元類對(duì)象,不同于類對(duì)象;
動(dòng)態(tài)方法解析,一般用于@dynamic屬性,且需要指定已經(jīng)實(shí)現(xiàn)了的處理方法,如上description及name;
備用接收者
如果動(dòng)態(tài)方法無(wú)法解析,runtime會(huì)繼續(xù)調(diào)用如下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
方法解析:
如果對(duì)象實(shí)現(xiàn)(或者繼承)此方法,并且返回非
nil,則消息會(huì)轉(zhuǎn)發(fā)至新的接收者;返回的對(duì)象不能為self,會(huì)導(dǎo)致無(wú)限循環(huán);如果在非根類對(duì)象實(shí)現(xiàn)此方法且未返回任何內(nèi)容(即未指定新的接收者),應(yīng)該調(diào)用
[super forwardingTargetForSelector]將其轉(zhuǎn)發(fā)給父類,以此往上繼承調(diào)用;該方法無(wú)法對(duì)消息進(jìn)行處理,如操作消息的參數(shù)和返回值;
該方法適合將消息轉(zhuǎn)發(fā)給能處理該消息的接收者,如實(shí)現(xiàn)多繼承;
使用如下:
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(xxx)) {
return NSClassFromString(@"Class name");
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息轉(zhuǎn)發(fā)
當(dāng)動(dòng)態(tài)方法不能解析且未轉(zhuǎn)發(fā)給其他接收者時(shí),runtime就啟動(dòng)完整消息轉(zhuǎn)發(fā)機(jī)制:通過(guò)調(diào)用如下方法來(lái)轉(zhuǎn)發(fā)給其他接收者,并且可以修改該消息;
- (void)forwardInvocation:(NSInvocation *)invocation;
方法解析如下:
NSObject對(duì)象實(shí)現(xiàn)了該方法,但只是調(diào)用doesNotRecognizeSelector:方法,若繼承對(duì)象未實(shí)現(xiàn)該方法(前提是未動(dòng)態(tài)解析方法或轉(zhuǎn)發(fā)備用接收者),就會(huì)拋出異常;該消息的唯一參數(shù)為
NSInvocation類型的對(duì)象,該對(duì)象封裝了原始的消息及消息參數(shù);該參數(shù)來(lái)源于methodSignatureForSelector:方法返回的方法簽名,該方法必須被重寫(xiě),否則會(huì)拋出異常;
forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象?;蛘咚部梢韵笠粋€(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡(jiǎn)單的”吃掉“某些消息,因此沒(méi)有響應(yīng)也沒(méi)有錯(cuò)誤。forwardInvocation:方法也可以對(duì)不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力。
使用如下:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
轉(zhuǎn)發(fā)與多繼承

objective-c只支持單繼承,但是通過(guò)消息轉(zhuǎn)發(fā)機(jī)制可以實(shí)現(xiàn)“多繼承”的效果;如上圖所示,Warrior和Diplomat沒(méi)有繼承關(guān)系,但是Warrior將negotiate消息轉(zhuǎn)發(fā)給了Diplomat后,就好似Diplomat是Warrior的超類一樣;
注意:
forwardInvocation:方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用。 所以,如果我們希望一個(gè)對(duì)象將negotiate消息轉(zhuǎn)發(fā)給其它對(duì)象,則這個(gè)對(duì)象不能有negotiate方法。否則,forwardInvocation:將不可能會(huì)被調(diào)用。
轉(zhuǎn)發(fā)與繼承
不過(guò)消息轉(zhuǎn)發(fā)雖然類似于繼承,但NSObject的一些方法還是能區(qū)分兩者。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來(lái)像是繼承,則可以重寫(xiě)這些方法,如以下代碼所示:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector])
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
方法調(diào)配 Method Swizzling
Method Swizzling方法調(diào)配技術(shù)是蘋(píng)果的“黑魔法”,可以不用繼承或者重寫(xiě)方法,就可以修改類的方法實(shí)現(xiàn);
常用的方法如下:
Method class_getInstanceMethod(Class cls, SEL name);
void method_exchangeImplementations(Method m1, Method m2);
具體使用如下:
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
針對(duì)使用的答疑如下:
-
為啥使用
load方法+load方法是類初始加載時(shí)(應(yīng)用啟動(dòng)就會(huì)加載)調(diào)用且只調(diào)用一次,load方法按照父類到子類,類自身到Category的順序被調(diào)用,且若都實(shí)現(xiàn)了該方法,都會(huì)被調(diào)用;不同于+initialize方法,該方法是在第一次調(diào)用類方法或者實(shí)例方法是調(diào)用,有可能被調(diào)用多次(若子類未實(shí)現(xiàn)此方法或者子類調(diào)用[super initialize]都會(huì)導(dǎo)致被調(diào)用多次);注意:在方法中不能調(diào)用
[super load],因此+load方法在父類、子類是被分別調(diào)用的,且存在順序;如果調(diào)用,若父類已經(jīng)交換IMP,導(dǎo)致又被交換回來(lái),進(jìn)而失效; -
是否需要
dispatch_once+load方法系統(tǒng)會(huì)自動(dòng)調(diào)用且只調(diào)用一次,但不保證被手動(dòng)調(diào)用,為防止被多次調(diào)用并發(fā)問(wèn)題,建議添加dispatch_once來(lái)保證唯一性; -
為啥調(diào)用
class_addMethod,直接調(diào)用method_exchangeImplementations交換方法不就行了class_getInstanceMethod返回的可能是父類的實(shí)現(xiàn),即子類未實(shí)現(xiàn)被交換的方法,導(dǎo)致父類的實(shí)現(xiàn)指向了子類交換的方法,進(jìn)而導(dǎo)致父類調(diào)用被交換的方法(交換后實(shí)際調(diào)用的是子類交換的方法)引發(fā)崩潰;對(duì)于該方法調(diào)用不同場(chǎng)景的影響,可見(jiàn)Runtime Method Swizzling 實(shí)戰(zhàn)
-
xxx_viewWillAppear方法中調(diào)用[self xxx_viewWillAppear:animated]是否會(huì)造成死循環(huán)不會(huì),
[self xxx_viewWillAppear:animated]實(shí)際調(diào)用的被交換的方法viewWillAppear:;
對(duì)于不同類方法交換的場(chǎng)景,可見(jiàn)Method Swizzling的各種姿勢(shì);
Method Swizzling方法調(diào)配使用的場(chǎng)景如下:
實(shí)現(xiàn)AOP面向切面編程;
實(shí)現(xiàn)埋點(diǎn)統(tǒng)計(jì),如用戶行為統(tǒng)計(jì)等,見(jiàn)iOS動(dòng)態(tài)性(二)可復(fù)用而且高度解耦的用戶統(tǒng)計(jì)埋點(diǎn)實(shí)現(xiàn)
實(shí)現(xiàn)異常保護(hù),如避免NSArray數(shù)組越界;
AOP 面向切面編程
AOP(Aspect Oriented Program)面向切面編程,如下是百度百科及維基百科的解釋:
面向切面的程序設(shè)計(jì)(Aspect-oriented programming,AOP,又譯作面向方面的程序設(shè)計(jì)、剖面導(dǎo)向程序設(shè)計(jì))是計(jì)算機(jī)科學(xué)中的一種程序設(shè)計(jì)思想,旨在將橫切關(guān)注點(diǎn)與業(yè)務(wù)主體進(jìn)行進(jìn)一步分離,以提高程序代碼的模塊化程度。通過(guò)在現(xiàn)有代碼基礎(chǔ)上增加額外的通知(Advice)機(jī)制,能夠?qū)Ρ宦暶鳛椤?strong>切點(diǎn)(Pointcut)”的代碼塊進(jìn)行統(tǒng)一管理與裝飾,如“對(duì)所有方法名以‘set*’開(kāi)頭的方法添加后臺(tái)日志”。該思想使得開(kāi)發(fā)人員能夠?qū)⑴c代碼核心業(yè)務(wù)邏輯關(guān)系不那么密切的功能(如日志功能)添加至程序中,同時(shí)又不降低業(yè)務(wù)代碼的可讀性。面向切面的程序設(shè)計(jì)思想也是面向切面軟件開(kāi)發(fā)的基礎(chǔ)。
百度百科:
在軟件業(yè),AOP為Aspect Oriented Programming的縮寫(xiě),意為:面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期間動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開(kāi)發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
主要目的:將日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來(lái),通過(guò)對(duì)這些行為的分離,我們希望可以將它們獨(dú)立到非指導(dǎo)業(yè)務(wù)邏輯的方法中,進(jìn)而改變這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼。
理解:
橫切關(guān)注點(diǎn)不同于主業(yè)務(wù)邏輯代碼,應(yīng)該是主業(yè)務(wù)邏輯代碼中的非業(yè)務(wù)邏輯代碼通用部分,如主業(yè)務(wù)邏輯中添加行為統(tǒng)計(jì),“行為統(tǒng)計(jì)”就是橫切關(guān)注點(diǎn),將其抽離出來(lái);主業(yè)務(wù)邏輯(可能是多個(gè)業(yè)務(wù)模塊)需要橫切關(guān)注點(diǎn)時(shí)可統(tǒng)一插入到主業(yè)務(wù)邏輯中,而不影響主業(yè)務(wù)邏輯。
典型案例就是日志記錄,因?yàn)槿罩竟δ芡鶛M跨系統(tǒng)中的每個(gè)業(yè)務(wù)模塊,即“橫切”所有有日志需求的類及方法體。
針對(duì)ios的面向切面編程就是Method Swizzling黑魔法,可以在不改變?cè)写a(或者函數(shù))邏輯上,添加非業(yè)務(wù)邏輯,如添加行為日志記錄及上報(bào);
在 Objective-C 的實(shí)現(xiàn)結(jié)構(gòu)中 Runtime 的動(dòng)態(tài)派發(fā)機(jī)制保證了這么語(yǔ)言的靈活性,而在運(yùn)行時(shí),動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是AOP(面向切面編程)。
Method Swizzling如上所述,不做闡述,不過(guò)有人通過(guò)該技術(shù)實(shí)現(xiàn)了優(yōu)秀的AOP庫(kù),如Aspects;
Aspects
Aspects 就是一個(gè)不錯(cuò)的 AOP 庫(kù),封裝了 Runtime , Method Swizzling 這些黑色技巧,只提供兩個(gè)簡(jiǎn)單的API:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
使用舉例如下:
- viewWillAppear`中添加日志
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
-
調(diào)試查看點(diǎn)擊狀態(tài)
[_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) { NSLog(@"%@: %@", aspectInfo.instance, aspectInfo.arguments); } error:NULL]; -
為系統(tǒng)類添加處理程序,如
UIVieweController,官方demo:@implementation UIViewController (DismissActionHook) // Will add a dismiss action once the controller gets dismissed. - (void)pspdf_addWillDismissAction:(void (^)(void))action { PSPDFAssert(action != NULL); [self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) { if ([aspectInfo.instance isBeingDismissed]) { action(); } } error:NULL]; } @end
isa swizzling
isa swizzling是KVO(鍵值觀察)機(jī)制的實(shí)現(xiàn)技術(shù),其通過(guò)修改object對(duì)象的isa指針指向生成的中間代理類NSKVONotifying_xxx(官方的類名稱),NSKVONotifying_xxx的super_class指針指向原有的觀察類對(duì)象object class;

NSKVONotifying_xxx生成的中間類,重寫(xiě)被觀察的對(duì)象四個(gè)方法:class,setter,dealloc,_isKVOA;
重寫(xiě)setter
重寫(xiě)class方法目的是讓被觀察者對(duì)象調(diào)用[object class]時(shí)返回的原有的類實(shí)例;
官方文檔上對(duì)于KVO的實(shí)現(xiàn)的最后,給出了需要我們注意的一點(diǎn)是,永遠(yuǎn)不要用用isa來(lái)判斷一個(gè)類的繼承關(guān)系,而是應(yīng)該用class方法來(lái)判斷類的實(shí)例。
重寫(xiě)setter
重寫(xiě)setter方法目的是能監(jiān)聽(tīng)到被觀察者調(diào)用屬性設(shè)置方法,如setXxxx(Xxxx為屬性名稱)或者調(diào)用setValude:forKey:時(shí),能添加通知消息方法:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
在didChangValueForKey:中調(diào)用觀察者必須重寫(xiě)的方法:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
因此,若- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;方法生效,即觀察者能通過(guò)上述方法受到屬性被改變的通知,則需要滿足如下:
-
自動(dòng)通知,
NSObject實(shí)現(xiàn)了自動(dòng)通知的方法;- 存在
setter訪問(wèn)器方法,并且通過(guò)setter方法或者self.xxx間接調(diào)用setter方法,則中間類會(huì)添加will/didChangeValueForKey:觸發(fā)事件通知; - 不存在
setter訪問(wèn)器方法,需要通過(guò)setValude:forKey:方法來(lái)修改屬性,中間類會(huì)添加will/didChangeValueForKey:觸發(fā)事件通知; - 對(duì)于集合類,如NSMutalArray,需要通過(guò)
mutableArrayValueForKey來(lái)獲取中間代理類,觸發(fā)通知,否則直接通過(guò)addObject:無(wú)法收到通知; - 對(duì)于存在依賴關(guān)系的屬性,具體可查看官方文檔;
- 存在
手動(dòng)通知:手動(dòng)通知提供了更自由的方式去決定什么時(shí)間,什么方式去通知觀察者。這可以幫助你最少限度觸發(fā)不必要的通知,或者一組改變值發(fā)出一個(gè)通知,想要使用手動(dòng)通知必須實(shí)現(xiàn)
automaticallyNotifies-ObserversForKey:方法;并且手動(dòng)調(diào)用will/didChangeValueForKey:來(lái)觸發(fā)通知;
重寫(xiě)dealloc
用來(lái)銷毀新生成的NSKVONotifying_類;
重寫(xiě)_isKVOA方法
這個(gè)私有方法估計(jì)可能是用來(lái)標(biāo)示該類是一個(gè) KVO 機(jī)制聲稱的類。
YYModel
具體的實(shí)現(xiàn)機(jī)制可參考YYModel對(duì)于setter封裝:
/// 獲取監(jiān)控的屬性
objc_property_t getKVOProperty(Class cls, NSString *keyPath) {
if (!keyPath || !cls) {
return NULL;
}
objc_property_t res = NULL;
unsigned int count = 0;
const char *property_name = keyPath.UTF8String;
objc_property_t *properties = class_copyPropertyList(cls, &count);
for (unsigned int idx = 0; idx < count; idx++) {
objc_property_t property = properties[idx];
if (strcmp(property_name, property_getName(property)) == 0) {
res = property;
break;
}
}
free(properties);
return res;
}
/// 檢測(cè)屬性是否存在setter方法
BOOL ifPropertyHasSetter(objc_property_t property) {
BOOL res = NO;
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int idx = 0; idx < attrCount; idx++) {
if (attrs[idx].name[0] == 'S') {
res = YES;
}
}
free(attrs);
return res;
}
/// 獲取屬性的數(shù)據(jù)類型
YYEncodingType getPropertyType(objc_property_t) {
unsigned int attrCount;
YYEncodingType type = YYEncodingTypeUnknown;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int idx = 0; idx < attrCount; idx++) {
if (attrs[idx].name[0] == 'T') {
type = YYEncodingGetType(attrs[idx].value);
}
}
free(attrs);
return type;
}
/// 根據(jù)setter名稱獲取屬性名
NSString *getPropertyNameFromSelector(SEL selector) {
NSString *selName = [NSStringFromSelector(selector) substringFromIndex: 3];
NSString *firstAlpha = [[selName substringToIndex: 1] lowercaseString];
return [selName stringByReplacingCharactersInRange: NSMakeRange(0, 1) withString: firstAlpha];
}
/// 根據(jù)屬性名獲取setter名稱
SEL getSetterFromKeyPath(NSString *keyPath) {
NSString *firstAlpha = [[keyPath substringToIndex: 1] uppercaseString];
NSString *selName = [NSString stringWithFormat: @"set%@", [keyPath stringByReplacingCharactersInRange: NSMakeRange(0, 1) withString: firstAlpha]];
return NSSelectorFromString(selName);
}
/// 設(shè)置bool屬性的kvo setter
static void setBoolVal(id self, SEL _cmd, BOOL val) {
NSString *name = getPropertyNameFromSelector(_cmd);
void (*objc_msgSendKVO)(void *, SEL, NSString *) = (void *)objc_msgSend;
void (*objc_msgSendSuperKVO)(void *, SEL, BOOL) = (void *)objc_msgSendSuper;
objc_msgSendKVO(self, @selector(willChangeValueForKey:), val);
objc_msgSendSuperKVO(self, _cmd, val);
objc_msgSendKVO(self, @selector(didChangeValueForKey:), val);
}
/// KVO實(shí)現(xiàn)
static void addObserver(id observedObj, id observer, NSString *keyPath) {
objc_property_t observedProperty = getKVOProperty([observedObj class], keyPath);
if (!ifPropertyHasSetter(observedProperty)) {
return;
}
NSString *kvoClassName = [@"SLObserved_" stringByAppendString: NSStringFromClass([observedObj class])];
Class kvoClass = NSClassFromString(kvoClassName);
if (!kvoClass)) {
kvoClass = objc_allocateClassPair([observedObj class], kvoClassName.UTF8String, NULL);
Class(^classBlock)(id) = ^Class(id self) {
return class_getSuperclass([self class]);
};
class_addMethod(kvoClass, @selector(class), imp_implementationWithBlock(classBlock), method_getTypeEncoding(class_getMethodImplementation([observedObj class], @selector(class))));
objc_registerClassPair(kvoClass);
}
YYEncodingType type = getPropertyType(observedProperty);
SEL setter = getSetterFromKeyPath(observedProperty);
switch (type) {
case YYEncodingTypeBool: {
class_addMethod(kvoClass, setter, (IMP)setBoolVal, method_getTypeEncoding(class_getMethodImplementation([observedObj class], setter)));
} break;
......
}
}
其中實(shí)現(xiàn)點(diǎn)包含了TypeCode鍵值編碼,runtime中創(chuàng)建類、消息發(fā)送、獲取屬性列表、添加類對(duì)象方法等知識(shí)點(diǎn);
YYModel 源碼
神經(jīng)病院 Objective-C Runtime 出院第三天——如何正確使用 Runtime
Key-Value Observing Programming Guide
概念及數(shù)據(jù)結(jié)構(gòu)
id
/// A pointer to an instance of a class.
typedef struct objc_object *id;
//objc-private.h
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
... 此處省略其他方法聲明
}
//objc.h
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
指向類實(shí)例的指針;
objc_object結(jié)構(gòu)體包含一個(gè)isa指針,類型為isa_t聯(lián)合體。根據(jù)isa就可以順藤摸瓜找到對(duì)象所屬的類。isa這里還涉及到 tagged pointer 等概念。因?yàn)?isa_t使用union實(shí)現(xiàn),所以可能表示多種形態(tài),既可以當(dāng)成是指針,也可以存儲(chǔ)標(biāo)志位。有關(guān)isa_t聯(lián)合體的更多內(nèi)容可以查看 Objective-C 引用計(jì)數(shù)原理。PS:
isa指針不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來(lái)確定類型,而是應(yīng)該用class方法來(lái)確定實(shí)例對(duì)象的類。因?yàn)镵VO的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的isa指針指向一個(gè)中間類而不是真實(shí)的類,這是一種叫做 isa-swizzling 的技術(shù),詳見(jiàn)官方文檔
SEL
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
稱為方法選擇器,是一個(gè)方法selector的指針,用于類查找方法列表中的對(duì)應(yīng)的IMP;
在objc源碼中未找到objc_selector結(jié)構(gòu)體的定義,objc在編譯時(shí)會(huì)根據(jù)方法的名字及參數(shù)列表,生成一個(gè)整型標(biāo)識(shí),這個(gè)標(biāo)識(shí)就是SEL;本質(zhì)上SEL是一個(gè)指向方法的指針(準(zhǔn)確的說(shuō),只是一個(gè)根據(jù)方法名hash化了的KEY值,能唯一代表一個(gè)方法,不同類查找的方法列表不同,因此不會(huì)導(dǎo)致類方法指向同一個(gè)IMP),為了加快方法的查詢速度;
不同類中相同方法名及參數(shù)名的SEL相同,即使參數(shù)的類型不同,因此在一個(gè)類中方法名及參數(shù)名相同但參數(shù)類型不同的方法編譯錯(cuò)誤,objc使用了大量帶參數(shù)類型的方法名稱,導(dǎo)致objc方法名都很長(zhǎng);
工程中的所有的
SEL組成一個(gè)Set集合,Set的特點(diǎn)就是唯一,因此SEL是唯一的。因此,如果我們想到這個(gè)方法集合中查找某個(gè)方法時(shí),只需要去找到這個(gè)方法對(duì)應(yīng)的SEL就行了,SEL實(shí)際上就是根據(jù)方法名hash化了的一個(gè)字符串,而對(duì)于字符串的比較僅僅需要比較他們的地址就可以了,可以說(shuō)速度上無(wú)語(yǔ)倫比??!但是,有一個(gè)問(wèn)題,就是數(shù)量增多會(huì)增大hash沖突而導(dǎo)致的性能下降(或是沒(méi)有沖突,因?yàn)橐部赡苡玫氖?code>perfect hash)。但是不管使用什么樣的方法加速,如果能夠?qū)⒖偭繙p少(多個(gè)方法可能對(duì)應(yīng)同一個(gè)SEL),那將是最犀利的方法。那么,我們就不難理解,為什么SEL僅僅是函數(shù)名了。
可在運(yùn)行時(shí)添加新的selector,也可以通過(guò)如下三種方法獲取方法的SEL:
-
sel_registerName函數(shù) - Objective-C編譯器提供的
@selector() -
NSSelectorFromString()方法
IMP
IMP在objc.h中的定義是:
typedef void (*IMP)(void /* id, SEL, ... */ );
它就是一個(gè)函數(shù)指針,這是由編譯器生成的。第一個(gè)參數(shù)為self的指針(如果是實(shí)例方法,則是類實(shí)例的內(nèi)存地址;如果是類方法,則指向元類的指針);第二個(gè)SEL為方法選擇器;接下來(lái)就是參數(shù)列表;
Class
Class 其實(shí)是一個(gè)指向 objc_class 結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
//objc/objc-private.h定義
struct objc_class : objc_object {
// Class ISA; //add, 指向類對(duì)象的指針
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
... 省略其他方法
}
// objc/runtime.h定義
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //注意此處,類自身存在指向類對(duì)象的指針,稱為元類
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
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; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
objc_class繼承自objc_object,因此類本身也是類對(duì)象,為了處理類本身及類對(duì)象的關(guān)系,runtime創(chuàng)建了元類(Meta-Class),類對(duì)象所屬的類型叫做元類,用來(lái)標(biāo)識(shí)類對(duì)象具備的元數(shù)據(jù)。類方法(可以理解為類對(duì)象的實(shí)例方法,區(qū)別于類本身的實(shí)例方法)就定義在類對(duì)象中,每個(gè)類僅有一個(gè)類對(duì)象,每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類,所有的元類最終指向根元類(即為NSObject),最終根元類的isa指針指向自己,見(jiàn)下圖,如[NSObject alloc]消息發(fā)送是,會(huì)在類對(duì)象中查詢能夠響應(yīng)消息的方法。
同時(shí),可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協(xié)議;


圖中類實(shí)例的
isa指向類本身,類本身的isa指向元類,元類的isa指向根元類Root class,根元類的isa指向自身;其他的super_class指向父類的類本身及元類,最終父類指向nil;
實(shí)例對(duì)象
typedef struct objc_object *id;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
實(shí)例對(duì)象是我們對(duì)類對(duì)象alloc或者new操作時(shí)所創(chuàng)建的,在這個(gè)過(guò)程中會(huì)拷貝實(shí)例所屬類的成員變量并初始化,其中isa指針也會(huì)被初始化,讓對(duì)象可以訪問(wèn)類及類的繼承體系,但并不拷貝類定義的方法。調(diào)用實(shí)例方法時(shí),系統(tǒng)會(huì)根據(jù)實(shí)例的isa指針去類的方法列表及父類的方法列表中尋找與消息對(duì)應(yīng)的selector指向的方法。
實(shí)例對(duì)象即id類型,其是一個(gè)objc_object結(jié)構(gòu)類型的指針。該類型的對(duì)象可以轉(zhuǎn)換為任何一種對(duì)象,類似于C語(yǔ)言中void *指針類型的作用;
runtime - iOS類對(duì)象、實(shí)例對(duì)象、元類對(duì)象
類對(duì)象
類對(duì)象即類本身,即實(shí)例對(duì)象的isa指向的地址。類對(duì)象存儲(chǔ)著類的成員變量、緩存及實(shí)例方法列表,但不存儲(chǔ)類方法;
元類對(duì)象
元類對(duì)象即類對(duì)象的isa指向的地址,存儲(chǔ)著類方法,其isa指向根元類(Metal-Class,NSObject),根元類的isa指向自身;
cache
接收者收到消息,優(yōu)先去cache緩存中查找,如果沒(méi)有就通過(guò)isa指針去類本身的實(shí)例方法列表中methodLists查找,提升查找速度;
該字段指向struct objc_cache的結(jié)構(gòu)體,具體如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method _Nullable buckets[1] OBJC2_UNAVAILABLE;
};
-
mask:一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過(guò)程中,Objective-C runtime使用這個(gè)字段來(lái)確定開(kāi)始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))。這可以作為一個(gè)簡(jiǎn)單的hash散列算法。
-
occupied:一個(gè)整數(shù),指定實(shí)際占用的緩存bucket的總數(shù)。buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過(guò)mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒(méi)有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)。
Method
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
對(duì)于struct objc_method結(jié)構(gòu)體描述如下:
- 方法名類型為
SEL,前面提到過(guò)相同名字的方法即使在不同類中定義,它們的方法選擇器也相同; - 方法類型
types是個(gè)char指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型; -
imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)指針;
相關(guān)的runtime調(diào)動(dòng)方法如下:
// 調(diào)用指定方法的實(shí)現(xiàn)
id method_invoke ( id receiver, Method m, ... );
// 調(diào)用返回一個(gè)數(shù)據(jù)結(jié)構(gòu)的方法的實(shí)現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名
SEL method_getName ( Method m );
// 返回方法的實(shí)現(xiàn)
IMP method_getImplementation ( Method m );
// 獲取描述方法參數(shù)和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );
// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );
// 獲取方法的指定位置參數(shù)的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通過(guò)引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數(shù)的個(gè)數(shù)
unsigned int method_getNumberOfArguments ( Method m );
// 通過(guò)引用返回方法指定位置參數(shù)的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
// 設(shè)置方法的實(shí)現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
Ivar
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
Ivar是類中實(shí)例變量的類型,可以根據(jù)實(shí)例來(lái)查找類中的名字,也稱“反射”;
成員變量的方法調(diào)用如下:
// 獲取成員變量名
const char * ivar_getName ( Ivar v );
// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );
// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
ivar_getOffset函數(shù),對(duì)于類型id或其它對(duì)象類型的實(shí)例變量,可以調(diào)用object_getIvar和object_setIvar`來(lái)直接訪問(wèn)成員變量,而不使用偏移量;
類成員變量支持權(quán)限控制:
- @protected是受保護(hù)的,只能在本類及其子類中訪問(wèn),在{}聲明的變量默認(rèn)是@protect;
- @private是私有的,只能在本類訪問(wèn);
- @public公開(kāi)的,可以被在任何地方訪問(wèn);
objc_property_t**
objc_property_t是表示Objective-C聲明的屬性的類型,其實(shí)際是指向objc_property結(jié)構(gòu)體的指針,其定義如下:
typedef struct objc_property *objc_property_t;
屬性相關(guān)的方法如下:
// 獲取屬性名
const char * property_getName ( objc_property_t property );
// 獲取屬性特性描述字符串
const char * property_getAttributes ( objc_property_t property );
// 獲取屬性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
// 獲取屬性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );
-
property_copyAttributeValue函數(shù),返回的char *在使用完后需要調(diào)用free()釋放。 -
property_copyAttributeList函數(shù),返回值在使用完后需要調(diào)用free()釋放。
objc_property_attribute_t**
objc_property_attribute_t定義了屬性的特性(attribute),它是一個(gè)結(jié)構(gòu)體,定義如下:
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
protocol協(xié)議
struct objc_protocol_list {
struct objc_protocol_list * _Nullable next;
long count;
__unsafe_unretained Protocol * _Nullable list[1];
};
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;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
//省略一些封裝的便捷 get 方法
....
};
@interface Protocol : NSObject
@end
Protocol就是繼承自NSObject的對(duì)象,其id結(jié)構(gòu)體類型為struct protocol_t;
runtime提供了一系列關(guān)于協(xié)議的方法,如下:
// 返回指定的協(xié)議
Protocol * objc_getProtocol ( const char *name );
// 獲取運(yùn)行時(shí)所知道的所有協(xié)議的數(shù)組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 創(chuàng)建新的協(xié)議實(shí)例
Protocol * objc_allocateProtocol ( const char *name );
// 在運(yùn)行時(shí)中注冊(cè)新創(chuàng)建的協(xié)議
void objc_registerProtocol ( Protocol *proto );
// 為協(xié)議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一個(gè)已注冊(cè)的協(xié)議到協(xié)議中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 為協(xié)議添加屬性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 返回協(xié)議名
const char * protocol_getName ( Protocol *p );
// 測(cè)試兩個(gè)協(xié)議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 獲取協(xié)議中指定條件的方法的方法描述數(shù)組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 獲取協(xié)議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 獲取協(xié)議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協(xié)議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 獲取協(xié)議采用的協(xié)議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看協(xié)議是否采用了另一個(gè)協(xié)議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
-
objc_getProtocol函數(shù),需要注意的是如果僅僅是聲明了一個(gè)協(xié)議,而未在任何類中實(shí)現(xiàn)這個(gè)協(xié)議,則該函數(shù)返回的是nil。 -
objc_copyProtocolList函數(shù),獲取到的數(shù)組需要使用free來(lái)釋放 -
objc_allocateProtocol函數(shù),如果同名的協(xié)議已經(jīng)存在,則返回nil -
objc_registerProtocol函數(shù),創(chuàng)建一個(gè)新的協(xié)議后,必須調(diào)用該函數(shù)以在運(yùn)行時(shí)中注冊(cè)新的協(xié)議。協(xié)議注冊(cè)后便可以使用,但不能再做修改,即注冊(cè)完后不能再向協(xié)議添加方法或協(xié)議
需要強(qiáng)調(diào)的是,協(xié)議一旦注冊(cè)后就不可再修改,即無(wú)法再通過(guò)調(diào)用protocol_addMethodDescription、 protocol_addProtocol和protocol_addProperty往協(xié)議中添加方法等。
category類別
/// An opaque type that represents a category.
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
}
這個(gè)結(jié)構(gòu)體主要包含了分類定義的實(shí)例方法與類方法,其中instance_methods列表是objc_class中方法列表的一個(gè)子集,而class_methods列表是元類方法列表的一個(gè)子集。
Runtime并沒(méi)有在<runtime.h>頭文件中提供針對(duì)分類的操作函數(shù)。因?yàn)檫@些分類中的信息都包含在objc_class中,我們可以通過(guò)針對(duì)objc_class的操作函數(shù)來(lái)獲取分類的信息;
可以通過(guò)類別增加類的方法,但不能通過(guò)類別增加實(shí)例變量,不過(guò)objc提供了解決方案--關(guān)聯(lián)對(duì)象(Associated Object);
關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象類似字典,通過(guò)key關(guān)鍵詞類設(shè)定類中的成員變量并關(guān)聯(lián)相關(guān)的對(duì)象,并通過(guò)key來(lái)獲取關(guān)聯(lián)的對(duì)象,不過(guò)需要手動(dòng)指定內(nèi)存策略,來(lái)告知runtime如何管理關(guān)聯(lián)的對(duì)象,具體的內(nèi)存策略如下:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
具體的<runtime.h>定義的方法如下:
//若value為nil,則移除指定的存在的關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
//移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id _Nonnull object);
typedef void (*objc_hook_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
void objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue,
objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue)
關(guān)聯(lián)對(duì)象使用起來(lái)并不復(fù)雜,它讓我們可以動(dòng)態(tài)地增強(qiáng)類現(xiàn)有的功能,如關(guān)聯(lián)UIAlertView點(diǎn)擊按鈕關(guān)聯(lián)響應(yīng)的block,在按鈕點(diǎn)擊代理實(shí)現(xiàn)中直接獲取關(guān)聯(lián)的按鈕block執(zhí)行;
參考資料
Objective-C Runtime Programming Guide
Objective-C Runtime 運(yùn)行時(shí)之一:類與對(duì)象
Objective-C Runtime 運(yùn)行時(shí)之二:成員變量與屬性
Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息
Objective-C Runtime 運(yùn)行時(shí)之四:Method Swizzling
Objective-C Runtime 運(yùn)行時(shí)之五:協(xié)議與分類
《Effective Objective-C 2.0 編寫(xiě)高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》
