簡(jiǎn)介
objective-c(簡(jiǎn)寫objc)
屬于動(dòng)態(tài)語言,不像C
語言一樣靜態(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
源碼是開源的溉瓶,且?guī)缀跞渴褂?code>C語言實(shí)現(xiàn)(有些使用了匯編實(shí)現(xiàn)),可以從蘋果開源官方網(wǎng)站獲取此代碼。
與runtime交互
objc
從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互堰酿,分別是通過 Objective-C 源代碼疾宏,通過 Foundation 框架的NSObject
類定義的方法,通過對(duì) runtime 函數(shù)的直接調(diào)用触创。
Objective-C源代碼
大部分都是編寫objc
代碼坎藐,其他都交由runtime
系統(tǒng)來后臺(tái)執(zhí)行具體的操作,如[recevier message]
轉(zhuǎn)為調(diào)用objc_msgSend
方法來執(zhí)行哼绑;
NSObject類
Cocoa
中大多數(shù)類都繼承自NSObject
類(NSProxy
類是個(gè)例外岩馍,它是一個(gè)抽象超類,來充當(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)共享庫趣避。其構(gòu)成了NSObject
類的基礎(chǔ),大部分還是使用更上層的接口編程新翎,一般會(huì)用在hook
接口或者與其他語言橋接等場(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)我們想通過[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 開發(fā),有了垃圾回收就不理會(huì)retain
,release
這些函數(shù)了旷太。檢測(cè)這個(gè) target 是不是
nil
對(duì)象展懈。ObjC 的特性是允許對(duì)一個(gè)nil
對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash,因?yàn)闀?huì)被忽略掉供璧。如果上面兩個(gè)都過了标沪,那就通過獲取
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
類為止;如果還找不到就要開始進(jìn)入消息轉(zhuǎn)發(fā)流程了军浆,后面會(huì)提到棕洋。
獲取方法地址
直接獲取到方法地址(IMP
函數(shù)指針)可有效避免runtime
消息發(fā)送流程,對(duì)于大量同函數(shù)調(diào)用的情況乒融,可提升調(diào)用效率掰盘,但不太常見;
NSObject
提供了通過id
及selector
來獲取對(duì)應(yīng)IMP
函數(shù)指針(包括實(shí)例對(duì)象或者類對(duì)象的函數(shù)指針)的方法:
- (IMP)methodForSelector:(SEL)aSelector;
該方法不屬于objc
的本身的特性赞季,而為cocoa runtime
的特性愧捕,查看源碼實(shí)際為NSObject
的類方法,通過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:)];
//無返回值
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ò);若是通過[objc performSelector:@selectro(message)]
形式廉涕,需要在運(yùn)行期才能確定是否能響應(yīng)該消息命迈,若無法響應(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ì)通過respondsToSelector:
方法來判定是否能響應(yīng)此消息壶愤,才觸發(fā)消息的發(fā)送淑倾;
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
消息轉(zhuǎn)發(fā)步驟如下:
- 動(dòng)態(tài)方法解析
- 備用接收者
-
完整轉(zhuǎn)發(fā)
動(dòng)態(tài)方法解析
對(duì)象在緩存及方法列表中未找到相應(yīng)的方法后,runtime
首先會(huì)調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel
(實(shí)例對(duì)象)或者+ (BOOL)resolveClassMethod:(SEL)sel
(類對(duì)象)征椒,若在此方法通過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ì)于其他類型覆获,具體見官網(wǎng),不過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)方法無法解析,runtime
會(huì)繼續(xù)調(diào)用如下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
方法解析:
如果對(duì)象實(shí)現(xiàn)(或者繼承)此方法柑晒,并且返回非
nil
欧瘪,則消息會(huì)轉(zhuǎn)發(fā)至新的接收者;返回的對(duì)象不能為self
匙赞,會(huì)導(dǎo)致無限循環(huán)佛掖;如果在非根類對(duì)象實(shí)現(xiàn)此方法且未返回任何內(nèi)容(即未指定新的接收者)妖碉,應(yīng)該調(diào)用
[super forwardingTargetForSelector]
將其轉(zhuǎn)發(fā)給父類,以此往上繼承調(diào)用芥被;該方法無法對(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ī)制:通過調(diào)用如下方法來轉(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ù)來源于methodSignatureForSelector:
方法返回的方法簽名,該方法必須被重寫专肪,否則會(huì)拋出異常;
forwardInvocation:
方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心堪侯,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象嚎尤。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象伍宦。它可以將一個(gè)消息翻譯成另外一個(gè)消息芽死,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(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
只支持單繼承揖曾,但是通過消息轉(zhuǎn)發(fā)機(jī)制可以實(shí)現(xiàn)“多繼承”的效果;如上圖所示亥啦,Warrior
和Diplomat
沒有繼承關(guān)系炭剪,但是Warrior
將negotiate
消息轉(zhuǎn)發(fā)給了Diplomat
后,就好似Diplomat
是Warrior
的超類一樣翔脱;
注意:
forwardInvocation:
方法只有在消息接收對(duì)象中無法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用奴拦。 所以,如果我們希望一個(gè)對(duì)象將negotiate
消息轉(zhuǎn)發(fā)給其它對(duì)象届吁,則這個(gè)對(duì)象不能有negotiate
方法错妖。否則绿鸣,forwardInvocation:
將不可能會(huì)被調(diào)用。
轉(zhuǎn)發(fā)與繼承
不過消息轉(zhuǎn)發(fā)雖然類似于繼承暂氯,但NSObject的一些方法還是能區(qū)分兩者潮模。如respondsToSelector:
和isKindOfClass:
只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈株旷。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來像是繼承再登,則可以重寫這些方法,如以下代碼所示:
- (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ù)是蘋果的“黑魔法”晾剖,可以不用繼承或者重寫方法锉矢,就可以修改類的方法實(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)致又被交換回來渺杉,進(jìn)而失效; -
是否需要
dispatch_once
+load
方法系統(tǒng)會(huì)自動(dòng)調(diào)用且只調(diào)用一次挪钓,但不保證被手動(dòng)調(diào)用是越,為防止被多次調(diào)用并發(fā)問題,建議添加dispatch_once
來保證唯一性碌上; -
為啥調(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)景的影響吗蚌,可見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)景,可見Method Swizzling的各種姿勢(shì)蚯妇;
Method Swizzling
方法調(diào)配使用的場(chǎng)景如下:
實(shí)現(xiàn)AOP面向切面編程敷燎;
實(shí)現(xiàn)埋點(diǎn)統(tǒng)計(jì)暂筝,如用戶行為統(tǒng)計(jì)等,見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)一步分離它褪,以提高程序代碼的模塊化程度。通過在現(xiàn)有代碼基礎(chǔ)上增加額外的通知(Advice)機(jī)制翘悉,能夠?qū)Ρ宦暶鳛椤?strong>切點(diǎn)(Pointcut)”的代碼塊進(jìn)行統(tǒng)一管理與裝飾茫打,如“對(duì)所有方法名以‘set*’開頭的方法添加后臺(tái)日志”。該思想使得開發(fā)人員能夠?qū)⑴c代碼核心業(yè)務(wù)邏輯關(guān)系不那么密切的功能(如日志功能)添加至程序中妖混,同時(shí)又不降低業(yè)務(wù)代碼的可讀性老赤。面向切面的程序設(shè)計(jì)思想也是面向切面軟件開發(fā)的基礎(chǔ)。
百度百科:
在軟件業(yè)制市,AOP為Aspect Oriented Programming的縮寫抬旺,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期間動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)祥楣。AOP是OOP的延續(xù)开财,是軟件開發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容荣堰,是函數(shù)式編程的一種衍生范型床未。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離竭翠,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低振坚,提高程序的可重用性,同時(shí)提高了開發(fā)的效率斋扰。
主要目的:將日志記錄渡八,性能統(tǒng)計(jì),安全控制传货,事務(wù)處理屎鳍,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對(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),將其抽離出來忧饭;主業(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ùn)行時(shí),動(dòng)態(tài)地將代碼切入到類的指定方法赖瞒、指定位置上的編程思想就是AOP(面向切面編程)女揭。
Method Swizzling
如上所述,不做闡述栏饮,不過有人通過該技術(shù)實(shí)現(xiàn)了優(yōu)秀的AOP
庫吧兔,如Aspects
哥捕;
Aspects
Aspects 就是一個(gè)不錯(cuò)的 AOP 庫瓢姻,封裝了 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ù),其通過修改object
對(duì)象的isa
指針指向生成的中間代理類NSKVONotifying_xxx(官方的類名稱)
罐监,NSKVONotifying_xxx
的super_class
指針指向原有的觀察類對(duì)象object class
吴藻;
NSKVONotifying_xxx
生成的中間類,重寫被觀察的對(duì)象四個(gè)方法:class
弓柱,setter
沟堡,dealloc
,_isKVOA
;
重寫setter
重寫class
方法目的是讓被觀察者對(duì)象調(diào)用[object class]
時(shí)返回的原有的類實(shí)例矢空;
官方文檔上對(duì)于KVO的實(shí)現(xiàn)的最后航罗,給出了需要我們注意的一點(diǎn)是,永遠(yuǎn)不要用用isa來判斷一個(gè)類的繼承關(guān)系屁药,而是應(yīng)該用class方法來判斷類的實(shí)例粥血。
重寫setter
重寫setter
方法目的是能監(jiān)聽到被觀察者調(diào)用屬性設(shè)置方法,如setXxxx(Xxxx為屬性名稱)
或者調(diào)用setValude:forKey:
時(shí),能添加通知消息方法:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
在didChangValueForKey:
中調(diào)用觀察者必須重寫的方法:
- (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;
方法生效绢彤,即觀察者能通過上述方法受到屬性被改變的通知,則需要滿足如下:
-
自動(dòng)通知蜓耻,
NSObject
實(shí)現(xiàn)了自動(dòng)通知的方法茫舶;- 存在
setter
訪問器方法,并且通過setter
方法或者self.xxx
間接調(diào)用setter
方法刹淌,則中間類會(huì)添加will/didChangeValueForKey:
觸發(fā)事件通知饶氏; - 不存在
setter
訪問器方法,需要通過setValude:forKey:
方法來修改屬性有勾,中間類會(huì)添加will/didChangeValueForKey:
觸發(fā)事件通知疹启; - 對(duì)于集合類,如NSMutalArray蔼卡,需要通過
mutableArrayValueForKey
來獲取中間代理類喊崖,觸發(fā)通知,否則直接通過addObject:
無法收到通知雇逞; - 對(duì)于存在依賴關(guān)系的屬性荤懂,具體可查看官方文檔;
- 存在
手動(dòng)通知:手動(dòng)通知提供了更自由的方式去決定什么時(shí)間塘砸,什么方式去通知觀察者节仿。這可以幫助你最少限度觸發(fā)不必要的通知,或者一組改變值發(fā)出一個(gè)通知掉蔬,想要使用手動(dòng)通知必須實(shí)現(xiàn)
automaticallyNotifies-ObserversForKey:
方法廊宪;并且手動(dòng)調(diào)用will/didChangeValueForKey:
來觸發(fā)通知;
重寫dealloc
用來銷毀新生成的NSKVONotifying_
類女轿;
重寫_isKVOA方法
這個(gè)私有方法估計(jì)可能是用來標(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ì)象所屬的類,不能依靠它來確定類型链方,而是應(yīng)該用class
方法來確定實(shí)例對(duì)象的類持痰。因?yàn)镵VO的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的isa
指針指向一個(gè)中間類而不是真實(shí)的類,這是一種叫做 isa-swizzling 的技術(shù)祟蚀,詳見官方文檔
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)確的說,只是一個(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ì)于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度上無語倫比R刚聪铺!但是,有一個(gè)問題萄窜,就是數(shù)量增多會(huì)增大hash沖突而導(dǎo)致的性能下降(或是沒有沖突铃剔,因?yàn)橐部赡苡玫氖?code>perfect hash)撒桨。但是不管使用什么樣的方法加速,如果能夠?qū)⒖偭繙p少(多個(gè)方法可能對(duì)應(yīng)同一個(gè)SEL
)键兜,那將是最犀利的方法凤类。那么,我們就不難理解普气,為什么SEL
僅僅是函數(shù)名了谜疤。
可在運(yùn)行時(shí)添加新的selector
,也可以通過如下三種方法獲取方法的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
為方法選擇器于未;接下來就是參數(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ì)象所屬的類型叫做元類蚯瞧,用來標(biāo)識(shí)類對(duì)象具備的元數(shù)據(jù)。類方法(可以理解為類對(duì)象的實(shí)例方法品擎,區(qū)別于類本身的實(shí)例方法)就定義在類對(duì)象中埋合,每個(gè)類僅有一個(gè)類對(duì)象,每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類萄传,所有的元類最終指向根元類(即為NSObject
)甚颂,最終根元類的isa指針指向自己,見下圖秀菱,如[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è)過程中會(huì)拷貝實(shí)例所屬類的成員變量并初始化,其中isa指針也會(huì)被初始化叉谜,讓對(duì)象可以訪問類及類的繼承體系旗吁,但并不拷貝類定義的方法。調(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語言中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
緩存中查找鳞尔,如果沒有就通過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ù)霞扬。在方法查找過程中糕韧,Objective-C runtime使用這個(gè)字段來確定開始線性查找數(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ù)組可能包含不超過mask+1
個(gè)元素杖小。需要注意的是,指針可能是NULL愚墓,表示這個(gè)緩存bucket
沒有被占用予权,另外被占用的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
扫腺,前面提到過相同名字的方法即使在不同類中定義,它們的方法選擇器也相同村象; - 方法類型
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 );
// 通過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數(shù)的個(gè)數(shù)
unsigned int method_getNumberOfArguments ( Method m );
// 通過引用返回方法指定位置參數(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í)例來查找類中的名字籍救,也稱“反射”习绢;
成員變量的方法調(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`來直接訪問成員變量闪萄,而不使用偏移量;
類成員變量支持權(quán)限控制:
- @protected是受保護(hù)的奇颠,只能在本類及其子類中訪問败去,在{}聲明的變量默認(rèn)是@protect;
- @private是私有的烈拒,只能在本類訪問圆裕;
- @public公開的,可以被在任何地方訪問荆几;
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來釋放 -
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è)后就不可再修改卵史,即無法再通過調(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
并沒有在<runtime.h>
頭文件中提供針對(duì)分類的操作函數(shù)忧设。因?yàn)檫@些分類中的信息都包含在objc_class
中,我們可以通過針對(duì)objc_class
的操作函數(shù)來獲取分類的信息颠通;
可以通過類別增加類的方法址晕,但不能通過類別增加實(shí)例變量,不過objc提供了解決方案--關(guān)聯(lián)對(duì)象(Associated Object)顿锰;
關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象
類似字典谨垃,通過key
關(guān)鍵詞類設(shè)定類中的成員變量并關(guān)聯(lián)相關(guān)的對(duì)象,并通過key
來獲取關(guān)聯(lián)的對(duì)象硼控,不過需要手動(dòng)指定內(nè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ì)象使用起來并不復(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 編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》