Runtime
1.對(duì)象和類(lèi)
Objective-C類(lèi)是由Class
類(lèi)型來(lái)表示的菌赖,它實(shí)際上是一個(gè)指向objc_class
結(jié)構(gòu)體的指針鹰霍。它的定義如下:
typedef struct objc_class *Class;
在objc/runtime.h中挖炬,struct objc_class 的定義如下:
struct objc_class {
Class isa;//元類(lèi)
#if !__OBJC2__
Class super_class//父類(lèi) OBJC2_UNAVAILABLE;
long version//版本信息 OBJC2_UNAVAILABLE;
long info//類(lèi)信息 OBJC2_UNAVAILABLE;
long instance_size//實(shí)例變量大小 OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars//成員變量鏈表 OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists//辦法 OBJC2_UNAVAILABLE;
struct objc_cache *cache//辦法緩存 OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols//協(xié)議 OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
雖然這個(gè)結(jié)構(gòu)體已經(jīng)過(guò)時(shí)了,但是還是有參考的意義怀樟,比起最新的版本更好理解绽慈。
由于 objc_class 也有 isa 指針,所以類(lèi)本身也是一個(gè)對(duì)象媒吗。
下面就根據(jù)runtime提供的一些辦法仑氛,使用簡(jiǎn)單的例子來(lái)探窺其究竟:
//首先創(chuàng)建一個(gè)測(cè)試類(lèi)
#import <Foundation/Foundation.h>
@interface MyRuntimeTestClass : NSObject
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
#import "MyRuntimeTestClass.h"
@interface MyRuntimeTestClass ()
{
NSInteger _instance1;
NSString *_instance2;
}
@property (nonatomic, assign) NSInteger *integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation MyRuntimeTestClass
+(void)classMethod1
{
}
-(void)method1
{
NSLog(@"call the method1");
}
-(void)method2
{
NSLog(@"call the method2");
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
//測(cè)試類(lèi)和對(duì)象
-(void)testMyRuntimeTestClass
{
MyRuntimeTestClass *obj = [[MyRuntimeTestClass alloc]init];
self.myRuntimeTestClass = obj;
unsigned int outCount = 0;
Class cls = obj.class;
NSLog(@"class name :%s",class_getName(cls));
NSLog(@"==========================================================");
NSLog(@"super class name :%s",class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
Class metaClass = objc_getMetaClass(class_getName(cls));
NSLog(@"%s metaClass name:%s",class_getName(cls),class_getName(metaClass));
NSLog(@"==========================================================");
NSLog(@"%s is %@ a metaClass",class_getName(cls),class_isMetaClass(cls) ? @"" : @"not");
NSLog(@"==========================================================");
NSLog(@"class size:%zu",class_getInstanceSize(cls));
NSLog(@"==========================================================");
// 成員變量
Ivar *ivars = class_copyIvarList(cls, &outCount);//獲取整個(gè)成員變量列表
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name:%s at index:%d",ivar_getName(ivar),i);
}
free(ivars);
Ivar array = class_getInstanceVariable(cls, "_array"); //獲取類(lèi)中指定名稱(chēng)實(shí)例成員變量的信息
if (array != NULL) {
NSLog(@"instance variable's %s",ivar_getName(array));
}
NSLog(@"==========================================================");
// 屬性操作
objc_property_t *properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog(@"property's name: %s",property_getName(property));
}
free(properties);
objc_property_t string = class_getProperty(cls, "_string");
if (string != NULL) {
NSLog(@"property %s",property_getName(string));
}
NSLog(@"==========================================================");
// 方法操作
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSLog(@"method's signature:%s",sel_getName(method_getName(method)));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
NSLog(@"instand method %s",sel_getName(method_getName(method1)));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
if (classMethod != NULL) {
NSLog(@"class method %s",sel_getName(method_getName(method1)));
}
IMP imp = class_getMethodImplementation(cls, @selector(method1));
IMP imp2 = method_getImplementation(class_getInstanceMethod(cls, @selector(method1)));
imp();
imp2();
NSLog(@"==========================================================");
/**
* 1.成員變量和屬性: (1) 成員變量?jī)?nèi)部使用,屬性外部使用闸英,屬性是為了讓類(lèi)外能夠訪(fǎng)問(wèn)到成員變量锯岖,即是屬性是外部訪(fǎng)問(wèn)成員變量的接口。
(2)類(lèi)的變量包含成員變量和屬性甫何,成員變量就好似一個(gè)人的自己固有的屬性(如大腦出吹,眼睛等等),屬性是一個(gè)人的外部特征(如他的名字辙喂,職業(yè)等等)捶牢。
(3)只要@property 聲明了成員變量,SDK自動(dòng)生成成員變量巍耗,不要需要手動(dòng)對(duì)應(yīng),是專(zhuān)用于從類(lèi)外部對(duì)其進(jìn)行調(diào)用或賦值的.
(4)成員變量命名方式:_變量名
2.@property:自動(dòng)創(chuàng)建setter and getter
3.類(lèi)對(duì)象和類(lèi)的實(shí)例(即對(duì)象):
*/
}
根據(jù)上面的一些例子秋麸,我們來(lái)看看元類(lèi):
/**
* 測(cè)試NSObject
*/
-(void)testNSObject
{
NSObject *obj = [[NSObject alloc]init];
Class cls = obj.class;//typedef struct objc_class *Class;
NSLog(@"NSObject name is %s",class_getName(cls));
NSLog(@"NSObject super class name is %s",class_getName(class_getSuperclass(cls)));// object super isa
NSLog(@"NSObject metaClass name is %s",class_getName(objc_getMetaClass(class_getName(cls))));// object isa
}
2.方法和消息
selector:
常說(shuō)的方法或是選擇子,其實(shí)質(zhì)就是函數(shù)的指針炬太,根據(jù) selector 可以找到對(duì)應(yīng)的辦法灸蟆。Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字娄琉、參數(shù)序列次乓,生成一個(gè)唯一的整型標(biāo)識(shí)(
Int
類(lèi)型的地址),這個(gè)標(biāo)識(shí)就是SEL
孽水。
IMP
IMP
實(shí)際上是一個(gè)函數(shù)指針票腰,指向方法實(shí)現(xiàn)的首地址,是一個(gè)函數(shù)實(shí)現(xiàn)的入口女气。
辦法的調(diào)用:
Objective-C 語(yǔ)言中杏慰,消息直到運(yùn)行時(shí)才被綁定起來(lái)。編譯器會(huì)將消息表達(dá)式
[receiver message]
轉(zhuǎn)化為一個(gè)消息函數(shù)的調(diào)用,即objc_msgSend
這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情:
- 首先它找到
selector
對(duì)應(yīng)的方法實(shí)現(xiàn)缘滥。因?yàn)橥粋€(gè)方法可能在不同的類(lèi)中有不同的實(shí)現(xiàn)轰胁,所以我們需要依賴(lài)于接收者的類(lèi)來(lái)找到的確切的實(shí)現(xiàn)。- 它調(diào)用方法實(shí)現(xiàn)朝扼,并將接收者對(duì)象及方法的所有參數(shù)傳給它赃阀。
- 最后,它將實(shí)現(xiàn)返回的值作為它自己的返回值擎颖。
獲取辦法的地址
該方式就類(lèi)似 C 語(yǔ)言的函數(shù)調(diào)用榛斯,直接找到函數(shù)的地址。一般情況下不會(huì)使用該方式搂捧,只有在比較頻繁調(diào)用某個(gè)函數(shù)的時(shí)候才會(huì)使用該方式驮俗。即使 OC 中使用了辦法緩存機(jī)制,如果一個(gè)辦法頻繁調(diào)用允跑,根據(jù)緩存列表王凑,快速找到相應(yīng)的辦法和辦法實(shí)現(xiàn),也是有一定的時(shí)間的聋丝。
/**
* 直接獲取辦法的地址:有cocoa框架提供索烹,并非 Objective-C 語(yǔ)言的特性 (一個(gè)辦法比較頻繁調(diào)用才會(huì)使用該辦法,相當(dāng)于直接調(diào)用函數(shù))
*/
-(void)testGetMethodAdress
{
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[MyRuntimeTestClass methodForSelector:@selector(method1)];
for (int i = 0 ; i < 1000 ; i++)
setter(self, @selector(method1), YES);
}
消息轉(zhuǎn)發(fā)
當(dāng)對(duì)象接收到無(wú)法解讀的消息后弱睦,就會(huì)啟動(dòng)”消息轉(zhuǎn)發(fā)“术荤。消息轉(zhuǎn)發(fā)分三個(gè)階段:動(dòng)態(tài)解析,備用接收者每篷,完整的消息轉(zhuǎn)發(fā)。
動(dòng)態(tài)解析:
該方式是當(dāng)對(duì)象接收到無(wú)法解讀的消息后端圈,會(huì)調(diào)用 +(BOOL)resolveInstanceMethod:(SEL)sel焦读,
+(BOOL)resolveClassMethod:(SEL)sel
這兩個(gè)辦法,這時(shí)候我們可以在該辦法處理對(duì)象無(wú)法解讀的消息舱权。
/**
* 動(dòng)態(tài)解析:當(dāng)對(duì)象接收到無(wú)法識(shí)別的消息矗晃,首先會(huì)調(diào)用所屬類(lèi)的類(lèi)方法+resolveInstanceMethod:(實(shí)例方法)或者+resolveClassMethod:(類(lèi)方法)
*/
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"testMySendMsgOfDymanicMehod"]) {
class_addMethod(self.class, @selector(testMySendMsgOfDymanicMehod), (IMP)functionForTestMySendMsgOfDymanicMehod, "@:");
}
return [super resolveInstanceMethod:sel];;
}
+(BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
void functionForTestMySendMsgOfDymanicMehod(id self, SEL _cmd)
{
NSLog(@"the IMP of functionForTestMySendMsgOfDymanicMehod");
}
備用接收者:
當(dāng)?shù)谝徊降膭?dòng)態(tài)解析也無(wú)法解讀未知的消息的時(shí)候,就會(huì)轉(zhuǎn)向備用接收者:-(id)forwardingTargetForSelector:(SEL)aSelector
其實(shí)質(zhì)就是尋找有沒(méi)有其他對(duì)象能解讀該對(duì)象宴倍,即是所謂的備用接收者张症。
/**
* 備用接收者
*/
-(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector");
NSLog(@"can not performSelector called: %@",NSStringFromSelector(aSelector));
NSString *selectorString = NSStringFromSelector(aSelector);
if ([selectorString isEqualToString:@"method1"]) {
return self.myRuntimeTestClass;
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息轉(zhuǎn)發(fā):
當(dāng)前面兩種方式都不能解讀未知的消息,那就啟用最后一中方式鸵贬,即是將尚未處理的那條消息的有關(guān)的全部細(xì)節(jié)都封裝在 NSInvocation 中俗他,此對(duì)象包含選擇子,目標(biāo)以及參數(shù)阔逼。在觸發(fā) NSInvocation 對(duì)象時(shí)兆衅,“消息派發(fā)系統(tǒng)”將親自出馬,把消息指派給目標(biāo)對(duì)象。
/**
* 由于完整的消息轉(zhuǎn)發(fā)需要將尚未處理的消息有關(guān)的全部細(xì)節(jié)封裝在NSInvocation中羡亩,故應(yīng)重寫(xiě)該辦法摩疑。
*/
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector:");
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([MyRuntimeTestClass instancesRespondToSelector:aSelector]) {
signature = [MyRuntimeTestClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
/**
* 完整的消息轉(zhuǎn)發(fā)
*/
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"forwardInvocation");
if ([MyRuntimeTestClass resolveClassMethod:anInvocation.selector]) {
anInvocation.target = self.myRuntimeTestClass;
}
}
3.辦法交換
有時(shí)候我們需要替換系統(tǒng)的辦法時(shí),比如我想在每次調(diào)用
-(void)viewWillAppear:(BOOL)animated
都打印一段話(huà)畏铆。這樣每個(gè) VC 都碼上打印語(yǔ)句雷袋,未免效率有些不高,這時(shí)候我們就可以使用辦法交換辞居,將我們自己的辦法和系統(tǒng)的辦法交換楷怒。
/**
* 第一次調(diào)用類(lèi)的類(lèi)方法或?qū)嵗椒ㄖ氨徽{(diào)用
*/
+(void)initialize
{
NSLog(@"initialize");
}
/**
* 在類(lèi)初始加載時(shí)調(diào)用
*/
+(void)load
{
NSLog(@"load");
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzlingSelector = @selector(YYviewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
if (didAddMethod) {
//實(shí)現(xiàn)兩個(gè)辦法的實(shí)現(xiàn)部分交換
class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
});
/*
1.Selector(typedef struct objc_selector *SEL):用于在運(yùn)行時(shí)中表示一個(gè)方法的名稱(chēng)。一個(gè)方法選擇器是一個(gè)C字符串速侈,它是在Objective-C運(yùn)行時(shí)被注冊(cè)的率寡。選擇器由編譯器生成,并且在類(lèi)被加載時(shí)由運(yùn)行時(shí)自動(dòng)做映射操作倚搬。
2.Method(typedef struct objc_method *Method):在類(lèi)定義中表示方法的類(lèi)型
3. Implementation(typedef id (*IMP)(id, SEL, ...)):這是一個(gè)指針類(lèi)型冶共,指向方法實(shí)現(xiàn)函數(shù)的開(kāi)始位置。這個(gè)函數(shù)使用為當(dāng)前CPU架構(gòu)實(shí)現(xiàn)的標(biāo)準(zhǔn)C調(diào)用規(guī)范每界。每一個(gè)參數(shù)是指向?qū)ο笞陨淼闹羔?self)捅僵,第二個(gè)參數(shù)是方法選擇器。然后是方法的實(shí)際參數(shù)眨层。
*/
}
#pragma mark -method swizzling
-(void)YYviewWillAppear:(BOOL)animated
{
[self YYviewWillAppear:animated];
NSLog(@"method had changed!");
}
注:辦法的交換永遠(yuǎn)都應(yīng)該在 load 辦法中實(shí)現(xiàn)庙楚,因?yàn)?load 辦法在類(lèi)初始化的時(shí)候就會(huì)調(diào)用,而 initialize 要在類(lèi)被發(fā)送消息的時(shí)候才會(huì)調(diào)用趴樱。