一.認(rèn)識(shí)一下runtime類
二.The Runtime
- 1.
Objective-C:
是一門簡(jiǎn)單的語言栋艳,95%是C恰聘。只是在語言層面上加了些關(guān)鍵字和語法。真正讓Objective-C如此強(qiáng)大的是它的運(yùn)行時(shí)。它很小但卻很強(qiáng)大晴叨。它的核心是消息分發(fā)凿宾。 - 2.Messages
執(zhí)行一個(gè)方法,有些語言兼蕊,編譯器會(huì)執(zhí)行一些額外的優(yōu)化和錯(cuò)誤檢查初厚,因?yàn)檎{(diào)用關(guān)系很直接也很明顯。但對(duì)于消息分發(fā)來說孙技,就不那么明顯了产禾。在發(fā)消息前不必知道某個(gè)對(duì)象是否能夠處理消息。你把消息發(fā)給它牵啦,它可能會(huì)處理亚情,也可能轉(zhuǎn)給其他的 Object 來處理。一個(gè)消息不必對(duì)應(yīng)一個(gè)方法哈雏,一個(gè)對(duì)象可能實(shí)現(xiàn)一個(gè)方法來處理多條消息楞件。
在Objective-C:
中,消息是通過objc_msgSend()
這個(gè) runtime 方法及相近的方法來實(shí)現(xiàn)的裳瘪。這個(gè)方法需要一個(gè)target土浸,selector,還有一些參數(shù)彭羹。理論上來說黄伊,編譯器只是把消息分發(fā)變成objc_msgSend來執(zhí)行。比如下面這兩行代碼是等價(jià)的:
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);` - 3.Objects, Classes, MetaClasses
大多數(shù)面向?qū)ο蟮恼Z言里有 classes 和 objects 的概念派殷。Objects通過Classes生成还最。但是在Objective-C中,classes本身也是objects(譯者注:這點(diǎn)跟python很像)愈腾,也可以處理消息憋活,這也是為什么會(huì)有類方法和實(shí)例方法。具體來說虱黄,Objective-C 中的 Object 是一個(gè)結(jié)構(gòu)體(struct)悦即,第一個(gè)成員是isa,指向自己的class橱乱。這是在objc/objc.h中定義的辜梳。
typedef struct objc_object { Class isa;} *id;
object的class保存了方法列表,還有指向父類的指針泳叠。但classes也是objects作瞄,也會(huì)有isa變量,那么它又指向哪兒呢危纫?這里就引出了第三個(gè)類型:metaclasses
宗挥。一個(gè)metaclass
被指向class乌庶,class被指向object。它保存了所有實(shí)現(xiàn)的方法列表契耿,以及父類的metaclass
瞒大。 - 4.Methods, Selectors and IMPs*
我們知道了運(yùn)行時(shí)會(huì)發(fā)消息給對(duì)象。我們也知道一個(gè)對(duì)象的class保存了方法列表搪桂。那么這些消息是如何映射到方法的透敌,這些方法又是如何被執(zhí)行的呢?
第一個(gè)問題的答案很簡(jiǎn)單踢械。class的方法列表其實(shí)是一個(gè)字典酗电,key 為 selectors ,IMPs 為value 内列。一個(gè)IMP是指向方法在內(nèi)存中的實(shí)現(xiàn)撵术。很重要的一點(diǎn)是,selector和IMP之間的關(guān)系是在運(yùn)行時(shí)才決定的话瞧,而不是編譯時(shí)荷荤。這樣我們就可以在其中進(jìn)行操作啦。
IMP通常是指向方法的指針移稳,。下面演示了Method和IMP
- (id)doSomethingWithInt:(int)aInt{}
id doSomethingWithInt(id self, SEL _cmd, int aInt){} 第一個(gè)參數(shù)是self会油,類型為id个粱,第二個(gè)參數(shù)是_cmd,類型為SEL翻翩,余下的是方法的參數(shù)都许。這也是self和_cmd被定義的地方
- 5運(yùn)行時(shí)到底能做什么呢?
所有的運(yùn)行時(shí)方法都有特定的前綴嫂冻。下面是一些有意思的方法:
class
class開頭的方法是用來修改和自省classes胶征。方法如class_addIvar, class_addMethod, class_addProperty和class_addProtocol允許重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList能拿到一個(gè)class的所有內(nèi)容桨仿。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty返回單個(gè)內(nèi)容睛低。
也有一些通用的自省方法,如class_conformsToProtocol, class_respondsToSelector, class_getSuperclass服傍。最后钱雷,你可以使用class_createInstance來創(chuàng)建一個(gè)object。
ivar
這些方法能讓你得到名字吹零,內(nèi)存地址和Objective-C type encoding罩抗。
method
這些方法主要用來自省,比如method_getName, method_getImplementation, method_getReturnType等等灿椅。也有一些修改的方法套蒂,包括method_setImplementation和method_exchangeImplementations钞支,這些我們后面會(huì)講到。
objc
一旦拿到了object操刀,你就可以對(duì)它做一些自省和修改烁挟。你可以get/set ivar, 使用object_copy和object_dispose來copy和free object的內(nèi)存。最NB的不僅是拿到一個(gè)class馍刮,而是可以使用object_setClass來改變一個(gè)object的class信夫。待會(huì)就能看到使用場(chǎng)景。
property
屬性保存了很大一部分信息卡啰。除了拿到名字静稻,你還可以使用property_getAttributes來發(fā)現(xiàn)property的更多信息,如返回值匈辱、是否為atomic振湾、getter/setter名字、是否為dynamic亡脸、背后使用的ivar名字押搪、是否為弱引用。
protocol
Protocols有點(diǎn)像classes浅碾,但是精簡(jiǎn)版的大州,運(yùn)行時(shí)的方法是一樣的。你可以獲取method, property, protocol列表, 檢查是否實(shí)現(xiàn)了其他的protocol垂谢。
sel
最后我們有一些方法可以處理 selectors厦画,比如獲取名字,注冊(cè)一個(gè)selector等等
Classes And Selectors From Strings
通過一個(gè)字符串生成Classes和Selectors滥朱。NSClassFromString和NSSelectorFromString
:
Class stringclass = NSClassFromString(@"NSString");
這樣做有以下優(yōu)點(diǎn):1.可以得知是否存在某個(gè)class根暑,NSClassFromString 會(huì)返回nil,2根據(jù)不同的輸入返回不同的class或method進(jìn)行相應(yīng)的操作徙邻,比如你在解析一些數(shù)據(jù)排嫌,每個(gè)數(shù)據(jù)項(xiàng)都有要解析的字符串以及自身的類型(String,Number缰犁,Array)淳地。你可以在一個(gè)方法里搞定這些,也可以使用多個(gè)方法民鼓。
Method Swizzling
我們知道方法由兩個(gè)部分組成薇芝。Selector相當(dāng)于一個(gè)方法的id;IMP是方法的實(shí)現(xiàn)丰嘉。這樣分開的一
個(gè)便利之處是selector和IMP之間的對(duì)應(yīng)關(guān)系可以被改變夯到。比如一個(gè) IMP 可以有多個(gè) selectors 指向它。而 Method Swizzling 可以交換兩個(gè)方法的實(shí)現(xiàn)饮亏∷<郑或許你會(huì)問“什么情況下會(huì)需要這個(gè)呢阅爽?”。我們先來看下Objective-C中荐开,兩種擴(kuò)展class的途徑付翁。首先是 subclassing。你可以重寫某個(gè)方法晃听,調(diào)用父類的實(shí)現(xiàn)百侧,這也意味著你必須使用這個(gè)subclass的實(shí)例,但如果繼承了某個(gè)Cocoa class能扒,而Cocoa又返回了原先的class(比如 NSArray)佣渴。這種情況下,你會(huì)想添加一個(gè)方法到NSArray初斑,也就是使用Category辛润。99%的情況下這是OK的,但如果你重寫了某個(gè)方法见秤,就沒有機(jī)會(huì)再調(diào)用原先的實(shí)現(xiàn)了砂竖。
Method Swizzling 可以搞定這個(gè)問題。你可以重寫某個(gè)方法而不用繼承鹃答,同時(shí)還可以調(diào)用原先的實(shí)現(xiàn)乎澄。通常的做法是在category中添加一個(gè)方法(當(dāng)然也可以是一個(gè)全新的class)〔馑ぃ可以通過method_exchangeImplementations這個(gè)運(yùn)行時(shí)方法來交換實(shí)現(xiàn)三圆。來看一個(gè)demo,這個(gè)demo演示了如何重寫addObject:方法來紀(jì)錄每一個(gè)新添加的對(duì)象避咆。
#import <objc/runtime.h>
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end
@implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addobject = class_getInstanceMethod(self, @selector(addObject:));
Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
}
- (void)logAddObject:(id)aobject {
[self logAddObject:aObject];
NSLog(@"Added object %@ to array %@", aObject, self);
}
@end
我們把方法交換放到了load中,這個(gè)方法只會(huì)被調(diào)用一次修噪,而且是運(yùn)行時(shí)載入查库。如果指向臨時(shí)用一下,可以放到別的地方黄琼。注意到一個(gè)很明顯的遞歸調(diào)用logAddObject:樊销。這也是Method Swizzling容易把我們搞混的地方,因?yàn)槲覀円呀?jīng)交換了方法的實(shí)現(xiàn)脏款,所以其實(shí)調(diào)用的是addObject:
動(dòng)態(tài)繼承围苫、交換
我們可以在運(yùn)行時(shí)創(chuàng)建新的class,這個(gè)特性用得不多撤师,但其實(shí)它還是很強(qiáng)大的剂府。你能通過它創(chuàng)建新的子類,并添加新的方法剃盾。
但這樣的一個(gè)子類有什么用呢腺占?別忘了Objective-C的一個(gè)關(guān)鍵點(diǎn):object內(nèi)部有一個(gè)叫做isa的變量指向它的class淤袜。這個(gè)變量可以被改變,而不需要重新創(chuàng)建衰伯。然后就可以添加新的ivar和方法了铡羡。可以通過以下命令來修改一個(gè)object的class.
object_setClass(myObject, [MySubclass class]);
這可以用在Key Value Observing意鲸。當(dāng)你開始o(jì)bserving an object時(shí)烦周,Cocoa會(huì)創(chuàng)建這個(gè)object的class的subclass,然后將這個(gè)object的isa指向新創(chuàng)建的subclass怎顾。點(diǎn)擊這里查看更詳細(xì)的解釋读慎。
動(dòng)態(tài)方法處理
目前為止,我們討論了方法交換杆勇,以及已有方法的處理贪壳。那么當(dāng)你發(fā)送了一個(gè)object無法處理的消息時(shí)會(huì)發(fā)生什么呢?很明顯蚜退,"it breaks"闰靴。大多數(shù)情況下確實(shí)如此,但Cocoa和runtime也提供了一些應(yīng)對(duì)方法钻注。
首先是動(dòng)態(tài)方法處理蚂且。通常來說,處理一個(gè)方法幅恋,運(yùn)行時(shí)尋找匹配的selector然后執(zhí)行之杏死。有時(shí),你只想在運(yùn)行時(shí)才創(chuàng)建某個(gè)方法捆交,比如有些信息只有在運(yùn)行時(shí)才能得到淑翼。要實(shí)現(xiàn)這個(gè)效果,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:品追。如果確實(shí)增加了一個(gè)方法玄括,記得返回YES。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
if (aSelector ==@selector(myDynamicMethod)) {
class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES;
} return [super resolveInstanceMethod:aSelector];
}
消息轉(zhuǎn)發(fā)
如果 resolve method 返回NO肉瓦,運(yùn)行時(shí)就進(jìn)入下一步驟:消息轉(zhuǎn)發(fā)遭京。有兩種常見用例。1) 將消息轉(zhuǎn)發(fā)到另一個(gè)可以處理該消息的object泞莉。2) 將多個(gè)消息轉(zhuǎn)發(fā)到同一個(gè)方法哪雕。
消息轉(zhuǎn)發(fā)分兩步。首先鲫趁,運(yùn)行時(shí)調(diào)用-forwardingTargetForSelector:斯嚎,如果只是想把消息發(fā)送到另一個(gè)object,那么就使用這個(gè)方法,因?yàn)楦咝⒖浮H绻胍薷南⒘泻穑敲淳鸵褂?forwardInvocation:,運(yùn)行時(shí)將消息打包成NSInvocation苦始,然后返回給你處理寞钥。處理完之后,調(diào)用invokeWithTarget:陌选。
Cocoa有幾處地方用到了消息轉(zhuǎn)發(fā)理郑,主要的兩個(gè)地方是代理(Proxies)和響應(yīng)鏈(Responder Chain)。NSProxy是一個(gè)輕量級(jí)的class咨油,它的作用就是轉(zhuǎn)發(fā)消息到另一個(gè)object您炉。如果想要惰性加載object的某個(gè)屬性會(huì)很有用霉涨。NSUndoManager也有用到农猬,不過是截取消息,之后再執(zhí)行村怪,而不是轉(zhuǎn)發(fā)到其他的地方法瑟。
響應(yīng)鏈?zhǔn)顷P(guān)于Cocoa如何處理與發(fā)送事件與行為到對(duì)應(yīng)的對(duì)象冀膝。比如說,使用Cmd+C執(zhí)行了copy命令霎挟,會(huì)發(fā)送-copy:到響應(yīng)鏈窝剖。首先是First Responder,通常是當(dāng)前的UI酥夭。如果沒有處理該消息赐纱,則轉(zhuǎn)發(fā)到下一個(gè)-nextResponder。這么一直下去直到找到能夠處理該消息的object熬北,或者沒有找到疙描,報(bào)錯(cuò)。
使用Block作為Method IMP
iOS 4.3帶來了很多新的runtime方法讶隐。除了對(duì)properties和protocols的加強(qiáng)淫痰,還帶來一組新的以 imp 開頭的方法。通常一個(gè) IMP 是一個(gè)指向方法實(shí)現(xiàn)的指針整份,頭兩個(gè)參數(shù)為 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 帶來了block籽孙,imp_implementationWithBlock() 能讓我們使用block作為 IMP烈评,下面這個(gè)代碼片段展示了如何使用block來添加新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
可以看到犯建,Objective-C 表面看起來挺簡(jiǎn)單讲冠,但還是很靈活的,可以帶來很多可能性适瓦。動(dòng)態(tài)語言的優(yōu)勢(shì)在于在不擴(kuò)展語言本身的情況下做很多很靈巧的事情竿开。比如Key Value Observing谱仪,提供了優(yōu)雅的API可以與已有的代碼無縫結(jié)合,而不需要新增語言級(jí)別的特性否彩。
三.runtime 優(yōu)秀博客
Objective-C對(duì)象模型及應(yīng)用-
objc/runtime 探索
iOS開發(fā)教程之Objc Runtime筆記
四.初識(shí)runtime
1.objc的Types定義:
包括objc_method結(jié)構(gòu)體方法****Method****;
objc_ivar結(jié)構(gòu)體實(shí)例變量****Ivar****;
objc_category結(jié)構(gòu)體類目****Category ;
類中聲明的objc_property結(jié)構(gòu)體屬性****objc_property_t****;
calass類objc_class結(jié)構(gòu)體;****
/* Types */
#if !OBJC_TYPES_DEFINED //objc_types_defined
(1)/// An opaque type that represents(代表) a method in a class definition(定義). // 代表一個(gè)類定義中的一個(gè)方法
typedef struct objc_method *Method;
-----------------
以前方法的定義;
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
}
SEL selector的簡(jiǎn)寫,俗稱方法選擇器,實(shí)質(zhì)存儲(chǔ)的是方法的名稱
IMP implement的簡(jiǎn)寫,俗稱方法實(shí)現(xiàn),看源碼得知它就是一個(gè)函數(shù)指針
Method 對(duì)上述兩者的一個(gè)包裝結(jié)構(gòu).
例如:
獲取類方法:Method class_getClassMethod(Class cls, SEL name)
(2).Ivar:實(shí)例變量,成員變量;———— --只能獲取自己類的實(shí)例變量,因?yàn)榉庋b性,其他類的實(shí)例變量不能獲取
typedef struct objc_ivar *Ivar;
例:獲取實(shí)例變量name;
用屬性聲明的時(shí)候必須要@synthesize name
才能有實(shí)例變量;
@property(nonatomic,copy)NSString * name; @synthesize name;
或者:
@interface MasterViewController : UITableViewController { NSString *name; }
(3).An opaque type that represents a category. //代表一個(gè)類目
typedef struct objc_category *Category;
(4).代表oc類中聲明的屬性; — 多個(gè)屬性的列表; (包括獲取私有屬性);
typedef struct objc_property *objc_property_t;
(5).objc_class:
類對(duì)象的isa指向元類;類class在runtime中的表示:(在實(shí)際 中用Class 替代struct objc_class)
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //指針疯攒,類對(duì)象的isa指向元類
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;//父類
const char *name OBJC2_UNAVAILABLE;//類名
long version OBJC2_UNAVAILABLE;//類版本號(hào),默認(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;//方法緩存,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象列荔,這時(shí)會(huì)在methodLists中遍歷敬尺,如果cache了,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率贴浙。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//協(xié)議鏈表.
#endif} OBJC2_UNAVAILABLE;/* Use `Class` instead of `struct objc_class *` */#endif
2.定義協(xié)議結(jié)構(gòu)體struct objc_object Protocol;
#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif
**3.定義一個(gè)方法method的結(jié)構(gòu)體objc_method_description:方法名name,方法參數(shù)types;
**
SEL name; /**< The name of the method */方法名
char *types; /**< The types of the method arguments */方法參數(shù)};
**4.定義一個(gè)聲明屬性結(jié)構(gòu)體objc_property_attribute_t: 屬性名name,屬性值value;
**
typedef struct {
const char *name; /**< The name of the attribute */特性名稱
const char *value; /**< The value of the attribute (usually empty) */特性值;
} objc_property_attribute_t;
特性相關(guān)編碼
屬性的特性字符串 以 T@encode(type) 開頭, 以 V實(shí)例變量名稱 結(jié)尾,中間以特性編碼填充,通過property_getAttributes
即可查看
特性編碼
具體含義
R --- readonly
C --- copy
& --- retain
N --- nonatomic
G(name) --- getter=(name)
S(name) --- setter=(name)
D --- @dynamic
W --- weak
P --- 用于垃圾回收機(jī)制
五.認(rèn)識(shí)一下runtime的函數(shù)
1. 對(duì)象拷貝id object_copy(id obj, size_t size)
; 即[obj copy]
-----arc中不能用;*
*2.釋放給定對(duì)象的內(nèi)存object_dispose(id obj)
-釋放對(duì)象,同[obj release]
——arc中不能使用;
*3.獲取對(duì)象obj的類---Class object_getClass(id obj)
; 同oc的[obj class]
;
*4.設(shè)置id對(duì)象obj的類為cos:Class object_setClass(id obj, Class cos)
;
*5.判斷一個(gè)id對(duì)象是否是class類:BOOL object_isClass(id obj)
——如果是類或者元類就返回YES,如果是對(duì)象就返回NO,因?yàn)閷?duì)象不符合objc_class結(jié)構(gòu)體中包含該有的成員變
例如:
ViewController *obj = [ViewController new];
BOOL fi = object_isClass(obj);
NSLog(@"%@",@(fi)); //0,NO,因?yàn)閛bj是對(duì)象,不是類;
6.獲取對(duì)象obj的類名,const char *object_getClassName(id obj)
; obj:是一個(gè)對(duì)象;
例如:
ViewController *obj = [ViewController new];
NSLog(@"%s",object_getClassName(obj));//ViewController
7.不支持arc,不能用object_getIndexedIvars(id obj)
8.獲取一個(gè)對(duì)象的一個(gè)實(shí)例變量的值
id object_getIvar(id obj, Ivar ivar)
//只能獲取自己實(shí)例變量的值;
跟Ivar class_getInstanceVariable(Class cls, const char *name)
結(jié)合使用;
跟oc的:[obj valueForKey:ivar]
,self.語法獲取類似砂吞!
9.void object_setIvar(id obj, Ivar ivar, id value)
設(shè)置一個(gè)對(duì)象(obj)的一個(gè)實(shí)例變量(ivar)的值 Ivar class_getInstanceVariable(Class cls, const char *name)
得到一個(gè)類中成員變量名字XX的成員變量Ivar
Example:
//獲取一個(gè)類中名字為age的實(shí)例變量
Ivar var = class_getInstanceVariable([self class], "age");
//給這個(gè)實(shí)力變量賦值
object_setIvar(self, var, @22);
10.設(shè)置一個(gè)實(shí)例變量的實(shí)例Ivar:Ivar object_setInstanceVariable(id obj, const char *name, void *value)
arc不能使用
11.獲取一個(gè)實(shí)例變量的實(shí)例Ivar:Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)
—arc不能用;
12.根據(jù)class的name獲取Class類Class objc_getClass(const char *name)
等同于oc的:
NSClassFromString()
;返回這個(gè)名字類的對(duì)象的元類; 如果沒有注冊(cè)過,將返回nil;
Example:
Class vc = objc_getClass("ViewController");
Class vc1 = NSClassFromString(@"ViewController");
13.根據(jù)class的name獲取Class元類-Class objc_getMetaClass(const char *name);
什么是元類呢?
Objective-C類是由Class類型來表示的崎溃,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針蜻直。它的定義如下typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(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;
isa:需要注意的是在Objective-C中概而,所有的類自身也是一個(gè)對(duì)象,這個(gè)對(duì)象的Class里面也有一個(gè)isa指針般婆,它指向metaClass(元類)
如下圖:
獲取類定義的方法有三個(gè):objc_lookUpClass, objc_getClass和objc_getRequiredClass
到腥。如果類在運(yùn)行時(shí)未注冊(cè),則objc_lookUpClass會(huì)返回nil
蔚袍,而objc_getClass
會(huì)調(diào)用類處理回調(diào)乡范,并再次確認(rèn)類是否注冊(cè),如果確認(rèn)未注冊(cè)啤咽,再返回nil晋辆。而objc_getRequiredClass函數(shù)的操作與objc_getClass相同
,只不過如果沒有找到類宇整,則會(huì)殺死進(jìn)程瓶佳。objc_getMetaClass
函數(shù):如果指定的類沒有注冊(cè),則該函數(shù)會(huì)調(diào)用類處理回調(diào)鳞青,并再次確認(rèn)類是否注冊(cè)霸饲,如果確認(rèn)未注冊(cè),再返回nil臂拓。不過厚脉,每個(gè)類定義都必須有一個(gè)有效的元類定義,所以這個(gè)函數(shù)總是會(huì)返回一個(gè)元類定義胶惰,不管它是否有效傻工。
14.獲取注冊(cè)類的總數(shù),列表bufferint objc_getClassList(Class *buffer, int bufferCount)
;buffer:緩存class的所有value的數(shù)組; bufferCount:緩存所有的類的個(gè)數(shù)
// 創(chuàng)建并返回一個(gè)指向所有已注冊(cè)類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
Example:利用objc_getClassList輸出項(xiàng)目所有類
int numClasses;
Class *classes = NULL;
classes = NULL;
numClasses = objc_getClassList(NULL, 0);
NSLog(@"Number of classes: %d", numClasses);
if (numClasses > 0 )
{
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
NSLog(@"Class name: %s", class_getName(classes[i]));
}
free(classes);//malloc 分配內(nèi)存 需要free釋放
}
15.獲取類的類名----const char *class_getName(Class cls)
Example:
const char *className = class_getName(self.class);
NSLog(@"%s",className);
//轉(zhuǎn)換為NSString字符串
NSString *strName = [NSString stringWithCString:className encoding:NSUTF8StringEncoding];
16.判斷這個(gè)class類是否是元類BOOL class_isMetaClass(Class cls)
;
獲取這個(gè)class類的父類** Class class_getSuperclass(Class cls)
;
設(shè)置class類cls的父類為新的newSuper類Class class_setSuperclass(Class cls, Class newSuper)
@warning You should not use this function.//你不應(yīng)該使用這個(gè)功能;
獲取這個(gè)類的實(shí)例變量大小size_t class_getInstanceSize(Class cls)
不知道有什么卵用
17.成員變量(ivar)- 獲取類cls中指定name的實(shí)例成員變量 Ivar class_getInstanceVariable(Class cls, const char *name)
— 可以用這個(gè)來獲取api的私有方法;
成員變量(ivars)- 獲取類中指定name的變量Ivar class_getClassVariable(Class cls, const char *name)
Example:
self.name = @"iOS";
Ivar var = class_getInstanceVariable([self class], "name");
id nameVar = object_getIvar(self, var); //同get;
NSLog(@"%@", nameVar);
Ivar var = class_getClassVariable([self class], "name");
object_setIvar(self, var, @"iOS");
id nameVar = object_getIvar(self, var);
NSLog(@"%@", nameVar);
18.獲取類中所有實(shí)例成員變量Ivar Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
這個(gè)比較常用
class_copyPropertyList
返回的僅僅是對(duì)象類的屬性(@property申明的屬性),而class_copyIvarList
返回類的所有屬性和變量(包括在@interface大括號(hào)中聲明的變量)
獲取cls類指定名字的屬性objc_property_t class_getProperty(Class cls, const char *name)
Example:
- (NSMutableArray *)getArrayValue:(NSArray *)array{
NSMutableArray *valueArray = [NSMutableArray array]; //value數(shù)組
for (NSObject *object in array) {
unsigned int numberofIvars = 0;
// 獲取類成員變量列表,numberofIvars 為類成員數(shù)量
Ivar* ivars = class_copyIvarList([object class], &numberofIvars);
NSMutableArray *objectArray = [NSMutableArray array];
for(const Ivar* p = ivars; p< ivars+numberofIvars;p++){
Ivar const ivar = *p ;
//獲取變量名;
NSString* key = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *value = [object valueForKey:key];
[objectArray addObject:value];
}
[valueArray addObject:objectArray];
}
free(ivars);//(需要free釋放 否者內(nèi)存泄露)
return valueArray;
}
//獲取cls類指定名字的屬性
objc_property_t properties = class_getProperty(objc_getClass("Class"), [@"name"UTF8String]);
const char *propertyName = property_getName(properties);
NSString *str = [NSString stringWithUTF8String:propertyName];
NSLog(@"%@",str);
19.獲取實(shí)例方法(Method)— 獲取類中的某個(gè)實(shí)例方法(減號(hào)方法):Method class_getInstanceMethod(Class cls, SEL name)
獲取類中的某個(gè)類方法(加號(hào)方法): Method class_getClassMethod(Class cls, SEL name)
獲取類中的SEL方法的實(shí)現(xiàn):IMP class_getMethodImplementation(Class cls,SEL name)
IMP方法實(shí)現(xiàn)這個(gè)參數(shù)可以被用在方法的實(shí)現(xiàn)替換函數(shù)class_replaceMethod;
添加方法class_addMethod(Class cls, SEL name, IMP imp, const char \\\\*types)
等同oc的方法:- (IMP)methodForSelector:(SEL)aSelector
;
.判斷這個(gè)方法是否添加成功BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
替換cls類中已有方法SEL-name的實(shí)現(xiàn)為imp,如果該方法不存在添加該方法IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
Example:利用runtime懶人實(shí)現(xiàn)iOS 防止按鈕連續(xù)點(diǎn)擊
20.判斷類中是否實(shí)現(xiàn)了某個(gè)方法的 BOOL class_respondsToSelector(Class cls, SEL sel)
與oc中NSObject.h的方法一樣:- (BOOL)respondsToSelector:(SEL)aSelector
或- (BOOL)instancesRespondToSelector:(SEL)aSelector
在使用代理時(shí)經(jīng)常需要用到這個(gè)判斷
21.獲取類的所有方法列表-Method *class_copyMethodList(Class cls, unsigned int *outCount)
——包括獲取到了私有方法;outCount是返回的一個(gè)值,包含返回array列表數(shù)組的長(zhǎng)度個(gè)數(shù),如果outCount是NULL,就不會(huì)返回length;
Example:
例如:獲取UILabel類的所有方法列表
u_int count; Method *methods = class_copyMethodList([UILabel class], &count); for (int i =0; i<count; i++) {
SEL name1 = method_getName(methods[i]);
const char *selName= sel_getName(name1);
NSString *strName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding]; NSLog(@"%@",strName);
這2句等同于NSStringFromSelector(name1);
}
free(methods);
NSLog(@"%u",count);//171個(gè)方法;包括很多私有方法;
22.判斷類是否實(shí)現(xiàn)指定的協(xié)議- BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
與NSObject類中的方法類似:- (BOOL)conformsToProtocol:(Protocol*)aProtocol
不常用吧中捆;
返回類實(shí)現(xiàn)的協(xié)議列表Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
Example:
BOOL protocol = class_conformsToProtocol(self.class, @protocol(XXXDelegate));
//oc中:
BOOL protocol = [self conformsToProtocol:@protocol(XXXDelegate)];
//class_copyProtocolList
unsigned int outCount=0;
Protocol * __unsafe_unretained *protocols = class_copyProtocolList(objc_getClass("Class"), &outCount);
for (int i =0; i<outCount; i++) {
Protocol *myProtocol = protocols[i];
const char *protocolName = protocol_getName(myProtocol);
NSString *str = [NSString stringWithUTF8String:protocolName];
NSLog(@"%@",str);
}
23.給class類添加一個(gè)新的實(shí)例變量,再判斷是否成功
/*cls:動(dòng)態(tài)創(chuàng)建的類; name:實(shí)例變量名; size:實(shí)例變量類型字節(jié)數(shù); alignment:對(duì)齊方法,一般是0; */ BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types)
[注意]只能在動(dòng)態(tài)創(chuàng)建類objc_allocateClassPair之后,注冊(cè)類objc_registerClassPair之前添加實(shí)例變量
給class動(dòng)態(tài)添加一個(gè)屬性:class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
24.動(dòng)態(tài)添加類.
1* 創(chuàng)建一個(gè)新類和元類:* Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
superclass:父類;
name:類名;
extraBytes:變量字節(jié)數(shù),一般是0;
跟objc_registerClassPair是一對(duì),搭配使用;
2 *在應(yīng)用中注冊(cè)由objc_allocateClassPair創(chuàng)建的類; *void objc_registerClassPair(Class cls)
注冊(cè)后,才能使用這個(gè)類;
3 * 銷毀一個(gè)類及其相關(guān)聯(lián)的類 *void objc_disposeClassPair ( Class cls );
//在運(yùn)行中還存在或存在子類實(shí)例鸯匹,就不能夠調(diào)用這個(gè)
Example:
Class Test= objc_allocateClassPair([NSObject class], "Test", 0);
//為類添加變量
class_addIvar(Test, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
//為類添加方法
//IMP 是函數(shù)指針
// typedef id (*IMP)(id, SEL, ...);
IMP i = imp_implementationWithBlock(^(id this,id some){
NSLog(@"%@",some);
return @111;
});
//注冊(cè)方法名為 test: 的方法
SEL s = sel_registerName("test:");
class_addMethod(Test, s, i, "i@:");
//結(jié)束類的定義
objc_registerClassPair(Test);
六 消息轉(zhuǎn)發(fā)
我們程序崩潰的時(shí)候看到這樣的提示:**** unrecognized selector sent to instance 表明你曾向某個(gè)對(duì)象發(fā)送了一條無法解讀的消息。當(dāng)一個(gè)對(duì)象收到無法解讀的消息后會(huì)如何處理泄伪,也就是說對(duì)象無法響應(yīng)選擇子(方法)殴蓬,這時(shí)就要進(jìn)入到消息轉(zhuǎn)發(fā)機(jī)制的流程。
1.查找接收者所屬的類臂容,看其是否能動(dòng)態(tài)添加方法科雳,以處理這個(gè)“未知的方法”。(動(dòng)態(tài)方法解析) +(BOOL) resolveInstanceMethod:(SEL)selector
2 .運(yùn)行期系統(tǒng)把消息轉(zhuǎn)給其他接收者處理脓杉。(備援接收者)
-(id)forwardingTargetForSelector:(SEL)selector
3* .經(jīng)過上述兩步后糟秘,如果還是沒有辦法處理選擇子,就啟動(dòng)完成的消息轉(zhuǎn)發(fā)球散。創(chuàng)建NSInvocation對(duì)象尿赚,把與尚未處理的那條消息有關(guān)的全部細(xì)節(jié)都封于其中。此對(duì)象包含選擇子蕉堰、目標(biāo)target及參數(shù)凌净。在觸發(fā)NSInvocation
對(duì)象時(shí),消息派發(fā)系統(tǒng)會(huì)把消息指派給目標(biāo)對(duì)象屋讶。
-(void)forwardInvocation:(NSInvocation *)invocation
7.Objective-C Reflection(Objective-C 反射機(jī)制)
JSONModel中的實(shí)現(xiàn):
對(duì)象屬性的獲取則主要在最后一個(gè)inspectProperties方法
-(void)__inspectProperties
{
//JMLog(@"Inspect class: %@", [self class]);
NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
//temp variables for the loops
Class class = [self class];
NSScanner* scanner = nil;
NSString* propertyType = nil;
// inspect inherited properties up to the JSONModel class
while (class != [JSONModel class]) {
//JMLog(@"inspecting: %@", NSStringFromClass(class));
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
//loop over the class properties
for (unsigned int i = 0; i < propertyCount; i++) {
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
//get property name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
p.name = [NSString stringWithUTF8String:propertyName];
//JMLog(@"property: %@", p.name);
//get property attributes
const char *attrs = property_getAttributes(property);
NSString* propertyAttributes = [NSString stringWithUTF8String:attrs];
if ([propertyAttributes hasPrefix:@"Tc,"]) {
//mask BOOLs as structs so they can have custom convertors
p.structName = @"BOOL";
}
scanner = [NSScanner scannerWithString: propertyAttributes];
//JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
[scanner scanUpToString:@"T" intoString: nil];
[scanner scanString:@"T" intoString:nil];
···
//finally store the property index in the static property index
objc_setAssociatedObject(self.class,
&kClassPropertiesKey,
[propertyIndex copy],
OBJC_ASSOCIATION_RETAIN // This is atomic
);
在這邊可以看到基本步驟如下
通過調(diào)用自身的class方法獲取當(dāng)前類的元數(shù)據(jù)信息
通過runtime的 class_copyPropertyList 方法取得當(dāng)前類的屬性列表冰寻,以指針數(shù)組的形式返回
遍歷指針數(shù)組,通過property_getName獲取屬性名皿渗,property_getAttributes獲取屬性類型
使用NSScanner來掃描屬性類型字符串斩芭,將類似如下的形式"T@"NSNumber",&,N,V_id",處理成NSNumber乐疆,逐個(gè)屬性循環(huán)處理
將所有處理好的數(shù)據(jù)放入propertyIndex這個(gè)字典中
通過objc_setAssociatedObject將這些數(shù)據(jù)關(guān)聯(lián)到kClassPropertiesKey
使用時(shí)在properties方法中這樣取出屬性數(shù)據(jù):
//returns a list of the model's properties
-(NSArray*)__properties__
{
//fetch the associated object
NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
if (classProperties) return [classProperties allValues];
//if here, the class needs to inspect itself
[self __setup__];
//return the property list
classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
return [classProperties allValues];
}
以上就是JSONModel中使用反射機(jī)制實(shí)現(xiàn)的類屬性獲取過程划乖,相比常見的逐個(gè)取值賦值的方式,這種方法在代碼上的確簡(jiǎn)潔優(yōu)雅了很多