runtime:Objective-C是動(dòng)態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時(shí)做的事放到了運(yùn)行時(shí),這個(gè)運(yùn)行時(shí)系統(tǒng)就是runtime
。
runtime
其實(shí)就是一個(gè)庫叶堆,它基本上是用C和匯編寫的一套API,這個(gè)庫使C語言有了面向?qū)ο蟮哪芰Α?/p>
靜態(tài)語言
:在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)斥杜。
動(dòng)態(tài)語言(OC)
:在運(yùn)行的時(shí)候根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用虱颗。
isa
:OC中,類和類的實(shí)例在本質(zhì)上沒有區(qū)別蔗喂,都是對象忘渔,任何對象都有isa
指針,它指向類或者元類缰儿。
isa
:是一個(gè)Class
類型的指針畦粮。每個(gè)實(shí)例對象有個(gè)isa
的指針,他指向?qū)ο蟮念惞哉螅?code>Class里也有個(gè)isa
的指針宣赔,指向meteClass
(元類)。元類保存了類方法的列表瞪浸。當(dāng)類方法被調(diào)用時(shí)儒将,先會(huì)從本身查找類方法的實(shí)現(xiàn),如果沒有默终,元類會(huì)向他父類查找該方法椅棺。同時(shí)注意的是:元類(meteClass)也是類,它也是對象齐蔽。元類也有isa
指針,它的isa
指針最終指向的是一個(gè)根元類(root meteClass)床估。根元類的isa
指針指向本身含滴,這樣形成了一個(gè)封閉的內(nèi)循環(huán)。
SEL
:SEL(選擇器)
是方法的selector
的指針丐巫。方法的selector
表示運(yùn)行時(shí)方法的名字谈况。OC
在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字递胧、參數(shù)碑韵,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址),這個(gè)標(biāo)識(shí)就是SEL
缎脾。
IMP
:IMP
是一個(gè)函數(shù)指針祝闻,指向方法最終實(shí)現(xiàn)的首地址。SEL
就是為了查找方法的最終實(shí)現(xiàn)IMP
遗菠。
Method
:用于表示類定義中的方法联喘,它是結(jié)構(gòu)體中包含一個(gè)SEL
和IMP
华蜒,相當(dāng)于在SEL
和IMP
之間做了一個(gè)映射。
消息機(jī)制
:任何方法的調(diào)用本質(zhì)就是發(fā)送一個(gè)消息豁遭。編譯器會(huì)將消息表達(dá)式[receiver message]
轉(zhuǎn)化為一個(gè)消息函數(shù)objc_msgSend(receiver,selector)
叭喜。
Runtime的使用
:獲取屬性列表,獲取成員變量列表蓖谢,獲得方法列表捂蕴,獲取協(xié)議列表,方法交換(黑魔法)闪幽,動(dòng)態(tài)的添加方法啥辨,調(diào)用私有方法,為分類添加屬性沟使。
一委可、什么是Runtime運(yùn)行時(shí)
runtime主要做了兩件事情:
1.封裝:
runtime
把對象用C語言的結(jié)構(gòu)體來表示,方法用C語言的函數(shù)來表示腊嗡。這些結(jié)構(gòu)體和函數(shù)被runtime
封裝后着倾,我們就可以在程序運(yùn)行的時(shí)候,對類/對象/方法進(jìn)行操作燕少。
2.尋找方法的最終執(zhí)行:當(dāng)執(zhí)行
[receiver message]
的時(shí)候卡者,相當(dāng)于想receiver
發(fā)送一條message
腰湾。runtime
會(huì)根據(jù)receiver
能否處理這條message
脐湾,從而做出不同的反應(yīng)。
在OC中颤枪,類是用Class來表示的底挫,而Class實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針恒傻。
struct objc_class{
Class isa OBJC_ISA_AVAILABILITY; // isa指針
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息
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;
Cache
用于緩存最近使用的方法建邓。一個(gè)類只有一部分方法是常用的盈厘,每次調(diào)用一個(gè)方法之后,這個(gè)方法就被緩存到cache
中官边,下次調(diào)用時(shí)runtime
會(huì)先在cache
中查找沸手,如果cache
中沒有,才會(huì)去methodList
中查找注簿。有了cache
契吉,經(jīng)常用到的方法的調(diào)用效率就提高了!
你只要記住诡渴,runtime
其實(shí)就是一個(gè)庫捐晶,它是一套API,這個(gè)庫使C語言有了面向?qū)ο蟮哪芰ΑN覀兛梢赃\(yùn)用runtime
這個(gè)庫里面的各種方法租悄,在程序運(yùn)行時(shí)的時(shí)候?qū)︻?實(shí)例對象/變量/屬性/方法等進(jìn)行各種操作谨究。
二、 什么是iso指針
在解釋isa
之前泣棋,你需要知道胶哲,在Objective-C中,所有的類自身也是一個(gè)對象潭辈,我們可以向這個(gè)對象發(fā)送消息(調(diào)用類方法)鸯屿。
先來看一下runtime
中實(shí)例對象的結(jié)構(gòu)體objc_object
。
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
從結(jié)構(gòu)體中可以看到把敢,這個(gè)結(jié)構(gòu)體只包含一個(gè)指向其類的isa指針寄摆。
isa指針的作用:當(dāng)我們向一個(gè)對象發(fā)送消息時(shí),runtime
會(huì)根據(jù)這個(gè)對象的isa
指針找到這個(gè)對象所屬的類修赞,在這個(gè)類的方法列表及父類的方法列表中婶恼,尋找與消息對應(yīng)的selector
指向的方法,找到后就運(yùn)行這個(gè)方法柏副。
要徹底理解isa勾邦,還需要了解一下元類的概念。
(meta-class)元類中存儲(chǔ)著一個(gè)類的所有類方法割择。
向一個(gè)對象發(fā)送消息時(shí)眷篇,
runtime
會(huì)在這個(gè)對象所屬的類的方法列表中查找方法;
向一個(gè)類發(fā)送消息時(shí)荔泳,會(huì)在這個(gè)類的meta-class(元類)
的方法列表中查找蕉饼。
meta-class
是一個(gè)類,也可以向它發(fā)送消息玛歌,那么它的isa又是指向什么呢昧港?為了不讓這個(gè)結(jié)構(gòu)無限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-class
的isa
指向基類(NSObject)
的meta-class
支子,而基類的meta-class
的isa
指針是指向自己(NSObject)
三慨飘、 什么是SEL,IMP译荞,Method
SEL
SEL
又叫選擇器,是方法的selector
的指針休弃。
typedef struct objc_selector *SEL;
方法的selector
用于表示運(yùn)行時(shí)方法的名字吞歼。Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字塔猾、參數(shù)序列篙骡,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址),這個(gè)標(biāo)識(shí)就是SEL
。
兩個(gè)類之間糯俗,無論它們是父子關(guān)系尿褪,還是沒有關(guān)系,只要方法名相同得湘,那么方法的SEL就是一樣的杖玲,每一個(gè)方法都對應(yīng)著一個(gè)SEL,所以在Objective-C同一個(gè)類中淘正,不能存在兩個(gè)同名的方法摆马,即使參數(shù)類型不同也不行。
SEL是一個(gè)指向方法的指針鸿吆,是根據(jù)方法名hash化了的一個(gè)字符串囤采,而對于字符串的比較僅僅需要比較他們的地址就可以了,所以速度上非常優(yōu)秀惩淳,它的存在只是為了加快方法的查詢速度蕉毯。
不同的類可以擁有相同的selector
,不同類的實(shí)例對象執(zhí)行相同的selector時(shí)思犁,會(huì)在格子的方法列表中取根據(jù)selector
尋找對應(yīng)的IMP
代虾。SEL
就是為了查找方法的最終實(shí)現(xiàn)IMP
。
IMP
IMP實(shí)際上是一個(gè)函數(shù)指針抒倚,指向方法實(shí)現(xiàn)的首地址褐着。代表了方法的最終實(shí)現(xiàn)。
id (*IMP)(id,SEL,…)
第一個(gè)參數(shù)是指向self
的指針(如果是實(shí)例方法托呕,則是類實(shí)例的內(nèi)存地址;如果是類方法,則是指向元類的指針),第二個(gè)參數(shù)是方法選擇器(selector)含蓉,省略號是方法的參數(shù)。
每個(gè)方法對應(yīng)唯一的SEL
项郊,通過SEL
快速準(zhǔn)確的獲得對應(yīng)的IMP
馅扣,取得IMP
后,就獲得了執(zhí)行這個(gè)方法代碼了着降。
Method
Method是用于表示類的方法差油。
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}
Method
結(jié)構(gòu)體中包含一個(gè)SEL
和IMP
,實(shí)際上相當(dāng)于在SEL
和IMP
之間作了一個(gè)映射任洞。有了SEL
蓄喇,我們便可以找到對應(yīng)的IMP
,從而調(diào)用方法的實(shí)現(xiàn)代碼交掏。
四妆偏、 什么是消息機(jī)制
當(dāng)執(zhí)行了[receiver message]
的時(shí)候,相當(dāng)于向receiver
發(fā)送一條message
盅弛。runtime
會(huì)根據(jù)receiver
能否處理這條message
钱骂,從而做出不同的反應(yīng)叔锐。
方法(消息機(jī)制)的調(diào)用流程
消息直到運(yùn)行時(shí)才綁定到方法的實(shí)現(xiàn)上。編譯器會(huì)將消息表達(dá)式[receiver message]
轉(zhuǎn)化為一個(gè)消息函數(shù)见秽,即objc_msgSend(receiver,selector)
愉烙。
objc_msgSend(receiver,selector)
// 如果消息中還有其它參數(shù)
objc_msgSend(receiver,selector,arg1,arg2,…)
objc_msgSend
做了如下事情:
1.通過對象的
isa
指針獲取類的結(jié)構(gòu)體。
2.在結(jié)構(gòu)體的方法表里查找方法的selector
解取。
3.如果沒有找到selector
步责,則通過objc_msgSend
結(jié)構(gòu)體中指向父類的指針找到父類,并在父類的方法表里找到方法的selector
肮蛹。
4.依次會(huì)一直找到NSObject
勺择。
5.一旦找到selector
,就會(huì)獲取到方法實(shí)現(xiàn)IMP
伦忠。
6.傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)省核。
7.如果最終沒有定位到selector
,就會(huì)走消息轉(zhuǎn)發(fā)流程昆码。
消息轉(zhuǎn)發(fā)機(jī)制
以[receiver message]
的方式調(diào)用方法气忠,如果receiver
無法響應(yīng)message
,編譯器會(huì)報(bào)錯(cuò)赋咽。但如果是以performSelecror
來調(diào)用旧噪,則需要等到運(yùn)行時(shí)才能確定object
是否能接收message
消息。如果不能脓匿,則程序崩潰淘钟。
當(dāng)我們不能確定一個(gè)對象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:
來判斷一下
if ([self respondsToselector:@selector(doSomething)]) {
[self performSelector:@selector(doSomething)];
}
如果不使用respondsToSelector:
來判斷陪毡,那么這就可以用到“消息轉(zhuǎn)發(fā)”機(jī)制米母。
當(dāng)對象無法接收消息,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制毡琉,通過這一機(jī)制铁瞒,告訴對象如何處理未知的消息。
這樣就就可以采取一些措施桅滋,讓程序執(zhí)行特定的邏輯慧耍,從而避免崩潰。措施分為三個(gè)步驟丐谋。
1.動(dòng)態(tài)方法解析
對象接收到未知的消息時(shí)芍碧,首先會(huì)調(diào)用所屬類的類方法+resolveInstanceMethod:
(實(shí)例方法)或者+resolveClassMethod:
(類方法)。
在這個(gè)方法中号俐,我們有機(jī)會(huì)為該未知消息新增一個(gè)“處理方法”师枣。使用該“處理房啊”的前提是已經(jīng)實(shí)現(xiàn),只需要在運(yùn)行時(shí)通過class_addMethod
函數(shù)萧落,動(dòng)態(tài)的添加到類里面就可以了践美。代碼如下。
void functionForMethod1(id self,SEL _cmd) {
NSLog(@“%@,%p”,self,_cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(self);
if ([selectorString isEqualToString:@“method1”]) {
class_addMethod(self.class,@selector(method1),(IMP)functionForMethod1,”@:”);
}
return [super resolveInstanceMethod:sel];
}
2. 備用接收者
如果在上一步無法處理消息找岖,則runtime
會(huì)繼續(xù)調(diào)下面的方法陨倡。
- (id)forwardingTargetForSelector:(SEL)aSelector
如果這個(gè)方法返回一個(gè)對象,則這個(gè)對象會(huì)作為消息的新接收者许布。注意這個(gè)對象不能是self自身兴革,否則就是出現(xiàn)無限循環(huán)。如果沒有指定對象來處理aSelector
蜜唾,則應(yīng)該return [super forwardingTargetForSelector:aSelector]
杂曲。
但是我們只將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對象上,無法對消息進(jìn)行處理袁余,例如操作消息的參數(shù)和返回值擎勘。
@interface RuntimeTest() {
RuntimeMethod *_TestMethod;
}
@implementation RuntimeTest
- (instancetype)init {
self = [super init];
if (self != nil) {
_TestMethod = [[TestMethod alloc] init];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@“forwardingTargetForSelector”);
NSString *selectorString = NSStringFromSelector(aSelector);
// 將消息轉(zhuǎn)發(fā)給_TestMethod來處理
if ([selectorString isEqualToString:@“method2”]) {
return _TestMethod;
}
return [super forwardingTargetForSelector:aSelector];
}
3.完整消息轉(zhuǎn)發(fā)
如果在上一步還是不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制颖榜。此時(shí)會(huì)調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([RuntimeMethod instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invakeWithTarget:_TestMethod];
}
}
這是最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對象棚饵。創(chuàng)建一個(gè)表示消息的NSInvocation
對象,把與消息的有關(guān)全部細(xì)節(jié)封裝在anInvocation
中掩完,包括selector
噪漾,目標(biāo)(target)和參數(shù)。在forwardInvocation
方法中將消息轉(zhuǎn)發(fā)給其它對象且蓬。
forwardInvocation:方法的實(shí)現(xiàn)有兩個(gè)任務(wù):
1.定位可以響應(yīng)封裝在anInvocation中的消息的對象欣硼。
2.使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象恶阴。anInvocation將會(huì)保留調(diào)用結(jié)果诈胜,runtime會(huì)提取這一結(jié)果并發(fā)送到消息的原始發(fā)送者。
在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能存淫,我們可以對消息的內(nèi)容進(jìn)行修改耘斩。另外,若發(fā)現(xiàn)消息不應(yīng)由本類處理桅咆,則應(yīng)調(diào)用父類的同名方法括授,以便集成體系中的每個(gè)類都有機(jī)會(huì)處理。
另外岩饼,必須重寫下面的方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機(jī)制從這個(gè)方法中獲取消息來創(chuàng)建NSInvocation對象荚虚。完整的示例如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([RuntimeMethod instanceRespondToSelector:aSelector]) {
signature = [RuntimeMethod instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([RuntimeMethod instanceRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_TestMethod];
}
}
NSObject
的forwardInvocation
方法只是調(diào)用了doesNotRecognizeSelector
方法,它不會(huì)轉(zhuǎn)發(fā)任何消息籍茧。如果不在以上所述的三個(gè)步驟中處理未知消息版述,則會(huì)引發(fā)異常。
forwardInvocation
就像一個(gè)未知消息的分發(fā)中心寞冯,將這些未知的消息轉(zhuǎn)發(fā)給其它對象渴析⊥砘铮或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對象,取決于具體的實(shí)現(xiàn)俭茧。
消息的轉(zhuǎn)發(fā)機(jī)制可以用下圖來幫助理解咆疗。
五、 runtime的使用
- 獲取屬性列表
- 獲取成員變量列表
- 獲取方法列表
- 獲取協(xié)議列表
- 方法交換(黑魔法)
- 添加方法
- 調(diào)用私有方法
- 為分類添加屬性
1.獲取屬性列表
運(yùn)用class_copyPropertyList
方法來獲得屬性列表母债,遍歷把屬性加入數(shù)組中午磁,最終返回此數(shù)組。其中[self dictionaryWithProperty:properties[i]]
方法是用來拿到屬性的描述毡们,例如copy
迅皇,readonly
,NSString
等信息衙熔。
/** 屬性列表 */
- (NSArray *)propertiesInfo
{
return [[self class] propertiesInfo];
}
+ (NSArray *)propertiesInfo
{
NSMutableArray *propertieArray = [NSMutableArray array];
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([self class],&propertyCount);
for (int index = 0; index < propertyCount; index++)
{
[propertieArray addObject:({
NSDictionary *dictionary = [self dictionaryWithProperty:properties[index]];
dictionary;
})];
}
free(properties);
return propertieArray;
}
// 格式化之后的屬性列表
+ (NSArray *)propertiesWithCodeFormat
{
NSMutableArray *array = [NSMutableArray array];
NSArray *properties = [[self class] propertiesInfo];
for (NSDictionary *item in properties)
{
NSMutableString *format = ({
NSMutableString *formatString = [NSMutableString stringWithFormat:@“property ”];
// attribute
NSArray *attribute = [item objectForKey:@“attribute”];
attribute = [attribute sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
if (attribute && attribute.count > 0)
{
NSString *attributeStr = [NSString stringWithFormat:@“(%@)”,[attribute componentsJoinedByString:@“, ”]];
[formatString appendString:attributeStr];
}
// type
NSString *type = [item objectForKey:@“type”];
if (type)
{
[formatString appendString:@“ ”];
[formatString appednString:type];
}
// name
NSString *name = [item objectForKey:@“name”];
if (name)
{
[formatString appendString:@“ ”];
[formatString appendString:name];
[formatString appendString:@“;”];
}
formatString;
});
[array addObject:format];
}
return array;
}
+ (NSDictionary *)dictionaryWithProperty:(objc_property_t)property
{
NSMutableDictionary *result = [NSMutableDictionary dictionary];
// name
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[result setObject:propertyName forKey:@“name”];
// attribute
NSMutableDictionary *attributeDictionary = ({
[NSMutableDictionary dictionary];
unsigned int attributeCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property,&attributeCount);
for (int index = 0; index < attributeCount; index++)
{
NSString *name = [NSString stringWithCString:attrs[index].name encoding:NSUTF8StringEncoding];
NSString *value = [NSString stringWithCString:attrs[index].value encoding:NSUTF8StringEncoding];
}
free(attrs);
dictionary;
});
NSMutableArray *attributeArray = [NSMutableArray array];
// R
if ([attributeDictionary objectForKey:@“R”])
{
[attributeArray addObject:@“readonly”];
}
// C
if ([attributeDictionary objectForKey:@“C”])
{
[attributeArray addObject:@“copy”];
}
// &
if ([attributeDictionary objectForKey:@“&”])
{
[attributeArray addObject:@“strong”];
}
// N
if ([attributeDictionary objectForKey:@“N”])
{
[attributeArray addObject:@“nonatomic”];
} else
{
[attributeArray addObject:@“atomic”];
}
// G<name>
if ([attributeDictionary objectForKey:@“G”])
{
[attributeArray addObject:[NSString stringWithFormat:@“getter=%@”,[attributeDictionary objectForKey:@“G”]]];
}
// S<name>
if ([attributeDictionary objectForKey:@“S”])
{
[attributeArray addObject:[NSString stringWithFormat:@“setter=%@”,[attributeDictionary objectForKey:@“G”]]];
}
// D
if ([attributeDictionary objectForKey:@“D”])
{
[result setObject:[NSNumber numberWithBool:YES] forKey:@“isDynamic”];
} else
{
[result setObject:[NSNumber numberWithBool:NO] forKey:@“isDynamic”];
}
// W
if ([attributeDictionary objectForKey:@“W”])
{
[attributeArray addObject:@“weak”];
}
// P
if ([attributeArray objectForKey:@“P”])
{
}
// T
if ([attributeDictionary objectForKey:@“T”])
{
NSDictionary *typeDic = @{
@“c”:@“char”,
@“i”:@“int”,
@“s”:@“short”,
@“l(fā)”:@“l(fā)ong”,
@“q”:@“l(fā)ong long”,
@“C”:@“unsigned char”,
@“I”:@“unsigned int”,
@“S”:@“unsigned short”,
@“L”:@“unsigned long”,
@“Q”:@“unsigned long long”,
@“f”:@“float”,
@“d”:@“double”,
@“B”:@“BOOL”,
@“v”:@“void”,
@“*”:@“char *”,
@“@”:@“id”,
@“#”:@“Class”,
@“:”:@“SEL”,
};
// TODO:An array
NSString *key = [attributeDictionary objectForKey:@“T”];
id type_str = [typeDic objectForKey:key];
if (type_str == nil)
{
if ([[key substringToIndex:1] isEqualToString:@“@”] && [key rangeOfString:@“?”].location == NSNotFound)
{
type_str = [[key substringWithRange:NSMakeRange(2,key.length - 3)] stringByAppendingString:@“*”] ;
}
else if ([[key substringToIndex:1] isEqualToString:@“^”])
{
id str = [typeDic objectForKey:[key substringFromIndex:1]];
if (str)
{
type_str = [NSString stringWithFormat:@“%@ *”,str];
}
}
else
{
type_str = @“unknow”;
}
}
[result setObject:type_str forKey:@“type”];
}
[result setObject:attributeArray forKey:@“attribute”];
return result;
}
2.獲取成員變量列表
運(yùn)用class_copyIvarList
方法來獲得變量列表登颓,通過遍歷把變量加入到數(shù)組中,最終返回此數(shù)組青责。其中[[self class] decodeType:ivar_getTypeEncoding(ivars[i])]
方法是用來拿到變量的類型挺据,例如char
,int
,unsigned long
等信息。
/** 成員變量列表 */
- (NSArray *)ivarInfo
{
return [[self class] invarInfo];
}
+ (NSArray *)ivarInfo
{
unsigned int outCount;
Ivar *ivars = class_copyIvarList([self class],&outCount);
for (int index = 0; index < outCount; index++)
{
NSString *type = [[self class] decodeType:ivar_getTypeEncoding(ivars[index])];
NSString *name = [NSString stringWithCString:ivar_getName(ivars[index]) encoding:NSUTF8StringEncoding];
NSString *ivarDescription = [NSString stringWithFormat:@“%@ %@”,type,name];
[result addObject:ivarDescription];
}
free(ivars);
return result.count ? [result copy] : nil;
}
+ (NSString *)decodeType:(const char *)cString
{
if (!strcmp(cString,@encode(char)))
{
return @“char”;
}
if (!strcmp(cString,@encode(int)))
{
return @“int”;
}
if (!strcmp(cString,@encode(short)))
{
return @“short”;
}
if (!strcmp(cString,@encode(long)))
{
return @“l(fā)ong”;
}
if (!strcmp(cString,@encode(long long)))
{
return @“l(fā)ong long”;
}
if (!strcmp(cString,@encode(unsigned char)))
{
return @“unsigned char”;
}
if (!strcmp(cString,@encode(unsigned int)))
{
return @“unsigned int”;
}
if (!strcmp(cString,@encode(unsigned short)))
{
return @“unsigned short”;
}
if (!strcmp(cString,@encode(unsigned long)))
{
return @“unsigned long”;
}
if (!strcmp(cString,@encode(unsigned long long)))
{
return @“unsigned long long”;
}
if (!strcmp(cString,@encode(float)))
{
return @“float”;
}
if (!strcmp(cString,@encode(double)))
{
return @“double”;
}
if (!strcmp(cString,@encode(bool)))
{
return @“bool”;
}
if (!strcmp(cString,@encode(_Bool)))
{
return @“_Bool”;
}
if (!strcmp(cString,@encode(void)))
{
return @“void”;
}
if (!strcmp(cString,@encode(char *)))
{
return @“char *”;
}
if (!strcmp(cString,@encode(id)))
{
return @“id”;
}
if (!strcmp(cString,@encode(Class)))
{
return @“class”;
}
if (!strcmp(cString,@encode(SEL)))
{
return @“SEL”;
}
if (!strcmp(cString,@encode(BOOL)))
{
return @“BOOL”;
}
NSString *result = [NSString stringWithCString:cString encoding:NSUTF8StringEncoding];
if ([[result substringToIndex:1] isEqualToString@“@”] && [result rangeOfString:@“?”].location == NSNotFound)
{
result = [[result substringWithRange:NSMakeRange(2,result.length - 3)] stringByAppendingString:@“*”];
}
else
{
if ([[result substringToIndex:1] isEqualToString:@“^”])
{
result = [NSString stringWithFormat:@“%@ *”,[NSString decodeType:[result substringFromIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]];
}
}
retuen result;
}
3.獲取方法列表
通過runtime
的class_copyMethodList
方法來獲取方法列表脖隶,通過遍歷把方法加入到數(shù)組中扁耐,最終返回此數(shù)組。
// 對象方法列表
- (NSArray *)instanceMethodList
{
u_int count;
NSMutableArray *methodList = [NSMutableArray array];
Method *methods = class_copyMethodList([self class],&count);
for (int index = 0; index < count; index++)
{
SEL name = method_getName(methods[index]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
[methodList addObject:strName];
}
free(methods);
return methods;
}
+ (NSArray *)instanceMethodList
{
u_int count;
NSMutableArray *methodList = [NSMutableArray array];
Method *methods = class_copyMethodList([self class],&count);
for (int index = 0; index < count; index++)
{
SEL name = method_getName(methods[index]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
}
free(methods);
return methodList;
}
// 獲取類方法列表
+ (NSArray *)classMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(object_getClass(self),&count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int index = 0; index < count; index++)
{
Method method = methodList[index];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
4.獲取協(xié)議列表
運(yùn)用class_copyProtocolList
方法來獲得協(xié)議列表产阱。
//協(xié)議列表
- (NSDictionary *)protocolList
{
retuen [[self class] protocolList];
}
+ (NSDictionary *)protocolList
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
unsigned int count;
Protocol * __unsigned_unretained *protocols = class_copyProtocolList([self class],&count);
for (int index = 0; index < count; index ++)
{
Protocol *protocol = protocols[index];
NSString *protocolName = [NSString stringWithCString:protocol_getName(protocol)];
NSMutableArray *superProtocolArray = ({
NSMutableArray *array = [NSMutableArray array];
unsigned int superProtocolCount;
Protocol * __unsafe_unretained *superProtocols = protocol_copyProtocolList(protocol,&superProtocolCount);
for (int ii = 0; ii < superProtocolCount; ii++)
{
Protocol *superProtocol = superProtocols[ii];
NSString *superProtocolName = [NSString stringWithCString:protocol_getName(superProtocol) encoding:NSUTF8StringEncoding];
[array addObject:superProtocolName];
}
free(superProtocols);
array;
});
[dictionary setObject:superProtocolArray forKey:protocolName];
}
free(protocols);
return dictionary;
}
5.方法交換(黑魔法)
runtime
的重頭戲婉称,被稱作黑魔法的方法交換Swizzling
。交換方法是在method_exchangeImplementations
里發(fā)生的构蹬。
/** 交換實(shí)例方法 */
+ (void)SwizzlingInstanceMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Class class = [self class];
SEL originalSelector = oldMethod;
SEL swizzledSelector = newMethod;
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);
}
}
/** 交換類方法 */
+ (void)SwizzlingClassMethodWithOldMethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Class class = object_getClass((id)self);
SEL originalSelector = oldMethod;
SEL swizzledSelector = newMethod;
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);
}
}
使用Swizzling
的過程中要注意兩個(gè)問題:
Swizzling要在+load方法中執(zhí)行
運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法王暗,
+load
與+initialize
。
+load
會(huì)在main
函數(shù)之前調(diào)用庄敛,并且一定會(huì)調(diào)用俗壹。
+initialize
是在第一次調(diào)用類方法或?qū)嵗椒ㄖ氨徽{(diào)用,有可能一直不被調(diào)用一般使用
Swizzling
是為了影響全局藻烤,所以為了方法交換一定成功绷雏,Swizzling
要放在+load
中執(zhí)行。
新建工程創(chuàng)建類怖亭,A類和B類的
load
順序是無序的 但是運(yùn)行過一次后load
順序就固定了
假如A類先load
在A類的load
方法中創(chuàng)建B類的實(shí)例 會(huì)使B類先調(diào)用initialize
方法 后調(diào)用load
方法
正常情況下涎显,會(huì)先調(diào)用load
方法,后調(diào)用initialize
方法
Swizzling要在dispatch_once中執(zhí)行
Swizzling
是為了影響全局兴猩,所以只讓它執(zhí)行一次就可以了期吓,所以要放在dispatch_once
中。
6.添加方法
/** 添加方法 */
+ (void)addMethodWithSEL:(SEL)methodSEL methodIMP:(SEL)methodIMP
{
Method method = class_getInstanceMethod(self, methodIMP);
IMP getMethodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
class_addMethod(self, methodSEL, getMethodIMP, types);
}
添加方法的運(yùn)用
接收到未知的消息時(shí)倾芝,首先會(huì)調(diào)用所屬類的類方法
+resolveInstanceMethod:(實(shí)例方法)
或+resolveClassMethod:(類方法)
讨勤。
第一種情況是箭跳,根據(jù)已知的方法名動(dòng)態(tài)的添加一個(gè)方法。
第二種情況是悬襟,直接添加一個(gè)方法衅码。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 1.如果方法名是addMethod,就添加一個(gè)MethodOne方法來執(zhí)行
if (sel == NSSelectorfromString(@“addMethod”))
{
class_addMethod(self, sel, (IMP)MethodOne, “v@:”);
return YES;
}
// 2.如果找不到方法脊岳,就添加一個(gè)addMethod來執(zhí)行
[self addMethodWithSEL:sel methodIMP:@selector(addMethod)];
return YES;
}
7.調(diào)用私有方法
由于消息機(jī)制,runtime
可以通過objc_msgSend
來幫我們調(diào)用一些私有方法
TestRuntime *test = [[TestRuntime alloc] init];
objc_msgSend(test, @selector(privateMethod));
使用objc_msgSend
需要注意兩個(gè)問題:
1.需要導(dǎo)入頭文件
#import <objc/message.h>
2.在Build Settings
里設(shè)置Enable Strict Checking of objc_msgSend Calls
的值為NO
8.為分類添加屬性
在分類中屬性不會(huì)自動(dòng)生成實(shí)例變量和存取方法垛玻,但是可以運(yùn)用runtime
的關(guān)聯(lián)對象(Associated Object)
來解決這個(gè)問題割捅。
@interface UIImage (swizzlingImage)
// 用關(guān)聯(lián)對象為分類添加屬性
@property (nonatomic, copy) NSString *categoryProperty;
@end
@implementation UIImage (swizzlingImage)
// getter
- (NSString *)categoryProperty
{
return objc_getAssociatedObject(self, _cmd);
}
// setter
- (void)setCategoryProperty:(NSString *)categoryProperty
{
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
使用objc_getAssociatedObject
和objc_setAssociatedObject
來做到存取方法,使用關(guān)聯(lián)對象模擬實(shí)例變量帚桩。下面是兩個(gè)方法的原型:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
方法中的@selector(categoryProperty)
就是參數(shù)key
亿驾,使用@selector(categoryProperty)
作為key
傳入,可以確保key
的唯一性账嚎。
OBJC_ASSOCIATION_COPY_NONATOMIC
是屬性修飾符
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
{
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_PETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};