用Objective-C等面向?qū)ο笳Z言編程時惹盼,“對象”就是“基本構(gòu)建單元”碍舍,開發(fā)者可以通過對象來存儲并傳遞數(shù)據(jù)埂奈。在對象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程叫做“消息傳遞”若贮。當程序運行起來以后戳吝,為其提供相關(guān)支持的代碼叫做“Objective-C運行期系統(tǒng)”浩销,它提供了一些事得對象之間能夠傳遞消息的重要函數(shù),并且包括創(chuàng)建類實例所用的全部邏輯骨坑。
1.概括來說OC對象消息傳遞中具有下列關(guān)鍵元素
消息:向?qū)ο?類發(fā)送的名稱(選擇器)和一系列參數(shù)
方法:OC中的類或?qū)嵗椒ê成ぃ渎暶髦泻忻Q柬采、輸入?yún)?shù)、返回值和方法簽名(即輸入?yún)?shù)和返回值得數(shù)據(jù)類型)且警;
方法綁定:接收想指定接收器發(fā)送的消息并尋找和執(zhí)行適當方法的處理過程粉捻。OC運行時系統(tǒng)在調(diào)用方法時,會以動態(tài)綁定方式處理消息斑芜。
1.消息由接收者肩刃,選擇子及參數(shù)構(gòu)成。給某對象“發(fā)送消息”(invoke a method)也就相當于在該對象上調(diào)用方法杏头。
//1.self是接收者 2.messageName:是選擇子 3.選擇子和參數(shù)合起來稱為“消息”
id returnValue = [self messageName:@"messageValue"];
2.發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)”來處理盈包,該系統(tǒng)會查出對應(yīng)的方法,并執(zhí)行其代碼醇王。
編譯器看到消息后呢燥,會將上面轉(zhuǎn)換為一條標準的C語言函數(shù)調(diào)用。
第一個參數(shù)是接收者 第二個參數(shù)是選擇子 后面的參數(shù)就是消息中的參數(shù)
void objc_msgSend(id self,SEL cmd,....);
id objc_returnValue = objc_msgSend(self,@selector(messageName:),@"messageValue");
執(zhí)行過程 objc_msgSend 會根據(jù)接收者和選擇子的類型來調(diào)用適當?shù)姆椒?br>
* 1.在接收者所在的類中搜其“方法列表”寓娩,找到相符的方法叛氨,就跳至其實現(xiàn)代碼。
* 2.找不到就沿著繼承體系繼續(xù)往上找棘伴,等找到合適的方法后再跳轉(zhuǎn)寞埠;
* 3.如果最終還是找不到相符的,就執(zhí)行“消息轉(zhuǎn)發(fā)”操作
2.消息轉(zhuǎn)發(fā)機制
若想令類能理解某條消息焊夸,我們必須以程序代碼實現(xiàn)出對應(yīng)的方法才行仁连。但是,在編譯期向類發(fā)送了無法解讀的消息不會報錯阱穗,因為運行時系統(tǒng)可以繼續(xù)向類中添加方法饭冬,所以編譯器編譯時無法確定類中到底會不會有某個方法實現(xiàn)。當對象接受到無法解讀的消息后揪阶,就會啟動“消息轉(zhuǎn)發(fā)”機制伍伤。
上圖是消息轉(zhuǎn)發(fā)過程:
1.對象在收到無法解讀的消息后,首先調(diào)用其所屬類的類方法遣钳。sel是無法響應(yīng)的選擇子扰魂。其返回值表示這個類是否能新增一個實例方法用以處理此選擇子。
//無法響應(yīng)的實例方法
+(BOOL)resolveInstanceMethod:(SEL)sel;
//無法響應(yīng)的類方法
+(BOOL)resolveClassMethod:(SEL)sel;
id absoluteValue(id self,SEL _cmd, id value){
NSInteger intVal = [value integerValue];
if (intVal < 0) {
return [NSNumber numberWithInteger:(intVal * -1)];
}
return value;
}
///動態(tài)添加實例方法方法
+(BOOL) resolveInstanceMethod:(SEL)sel{
NSString *method = NSStringFromSelector(sel);
if ([method hasPrefix:@"absoluteValue"]) {
class_addMethod([self class], sel, (IMP)absoluteValue, "@@:@");
NSLog(@"動態(tài)添加方法 方法名:%@",method);
return YES;
}
return NO;
}
2.后備接收者
當前接受方法對象還有第二次機會處理未知的選擇子蕴茴,在這一步運行期系統(tǒng)會問該對象:能不能把這條消息轉(zhuǎn)給其他接收者處理劝评。
//快速轉(zhuǎn)發(fā)
//將該方法轉(zhuǎn)給其他對象,從而實現(xiàn)快速轉(zhuǎn)發(fā)
-(id)forwardingTargetForSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [LMMessageForwarding new];
}
return nil;
}
3.啟動完整的消息轉(zhuǎn)發(fā)
//該方法能使對象能夠使用消息的全部內(nèi)容(目標倦淀,方法名蒋畜,參數(shù))
-(void)forwardInvocation:(NSInvocation *)anInvocation;
還有一個很重要的問題,我們必須重寫以下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象撞叽。因此我們必須重寫這個方法姻成,為給定的selector提供一個合適的方法簽名插龄。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([LMClass instancesRespondToSelector:aSelector]) {
signature = [LMClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([LMClass instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_class];
}
}
注意:forwardInvocation:
必須要經(jīng)過 methodSignatureForSelector:
方法來獲得一個NSInvocation
,開銷比較大科展。蘋果在 forwardingTargetForSelector 的discussion中也說這個方法是一個相對開銷多的多的方法
接受者在每一步中均有機會處理消息均牢。步驟越往后,處理消息的代價越大才睹。
2.運行時動態(tài)概念
運行時系統(tǒng)通過動態(tài)類型功能徘跪,可以在運行程序時決定對象的類型,因而可以使運行時因素能夠在程序中指定使用哪種對象琅攘。
//當使用靜態(tài)方式設(shè)置變量的類型時垮庐,變量的類型就由它的聲明決定。
LMClass *class
//動態(tài)聲明坞琴,該變量的類型是在運行時確定的
id class
動態(tài)綁定是指在運行程序時(而不是在編譯時)將消息與方法對應(yīng)起來的處理過程
--
OC對象收到消息后哨查,究竟會調(diào)用何種方法需要在運行期才能解析出來,所以與給定的選擇子名稱相對應(yīng)的方法可以在運行期改變剧辐,也可以在運行期添加新方法解恰。
動態(tài)創(chuàng)建方法
class_addMethod
//使用運行時系統(tǒng)創(chuàng)建類
//1.以動態(tài)方式穿件一個類
Class dynaClass = objc_allocateClassPair([NSObject class], "LMDynaClass", 0);
//2.以動態(tài)方式添加一個方法,使用已有方法(description)獲取特征
Method description = class_getInstanceMethod([NSObject class], @selector(description));
const char *types = method_getTypeEncoding(description);
//class_addMethod(那個類添加新方法, SEL, 函數(shù)指針, 類型編碼) 動態(tài)在類同添加方法
class_addMethod(dynaClass, @selector(getLMString), (IMP)getLMString, types);
///注冊這個動態(tài)類
objc_registerClassPair(dynaClass);
動態(tài)跟換方法
method_exchangeImplementations
Method originalMethod = class_getInstanceMethod([NSStringclass], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
//調(diào)用method_exchangeImplementations 交換
method_exchangeImplementations(originalMethod, swappedMethod);
//
NSString *string = @"This is The String";
//小寫變大寫方法
NSString *lowercaseString = [string lowercaseString];
//大寫方法變小寫
NSString *uppercaseString = [string uppercaseString];
NSLog(@"low= %@\n upp = %@",lowercaseString,uppercaseString);
Method lowString = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method lm_lowString = class_getInstanceMethod([NSString class], @selector(lm_lowercaseString));
method_exchangeImplementations(lowString, lm_lowString);
NSString *lowStr = [string lowercaseString];
NSLog(@"lowStr - >%@",lowStr)
-(NSString *)lm_lowercaseString{
//看上去想死循環(huán) ,其實這方法是準備和loweercaseString互換浙于,運行時會調(diào)換
//lm_lowercaseString 實際調(diào)用了 lowercaseString方法
NSString *lowercase = [self lm_lowercaseString];
NSLog(@"%@ => %@",self, lowercase);
return lowercase;
}
3.運行時系統(tǒng)的結(jié)構(gòu)
OC的運行時系統(tǒng)由兩個主要部分構(gòu)成:編譯器和運行系統(tǒng)庫。
編譯器的作用是接收輸入的源代碼挟纱,生成使用了運行時系統(tǒng)庫的代碼羞酗,從而得到合法的、可執(zhí)行的OC程序紊服。
OC語言中的面向?qū)ο笤睾蛣討B(tài)特性都是通過運行時系統(tǒng)實現(xiàn)的檀轨。概括來說,運行時系統(tǒng)由下列部分組成:
類元素(接口欺嗤、實現(xiàn)代碼参萄、協(xié)議、分類煎饼、方法讹挎、屬性、實例變量)
類實例(對象)
對象消息傳遞(包括動態(tài)類型和動態(tài)綁定)
動態(tài)方法決議
動態(tài)加載
對象內(nèi)省
3.1測試類創(chuàng)建實例顯示其數(shù)據(jù)
關(guān)于實例與類的指針解釋可以看回這一章節(jié):對Objectiv-C的一些指針的理解
LMTestClass *tc1 = [[LMTestClass alloc] init];
tc1->myInt = 0xa5a5a5a5;
LMTestClass *tc2 = [[LMTestClass alloc] init];
tc2->myInt = 0xc3c3c3c3;
long tc1Size = class_getInstanceSize([LMTestClass class]);
NSData *obj1Data = [NSData dataWithBytes:(__bridge const void *)(tc1) length:tc1Size];
NSData *obj2Data = [NSData dataWithBytes:(__bridge const void*)(tc2) length:tc1Size];
NSLog(@"LMTestClass object tc1 contains %@", obj1Data);
NSLog(@"LMTestClass object tc2 contains %@", obj2Data);
NSLog(@“LMTestClass Memory address = %p",[LMTestClass class]);
輸出結(jié)果
LMTestClass object tc1 contains <3874e000 01000000 a5a5a5a5 00000000> LMTestClass object tc2 contains <3874e000 01000000 c3c3c3c3 00000000 LMTestClass Memory address = 0x100e07438
從輸出結(jié)果可以看到3874e000 01000000
是isa指針吆玖,指向類筒溃。 a5a5a5a5 00000000
是實例變量的值
0x100e07438
和 3874e000 01000000
地址是一樣,在mac pro中使用翻轉(zhuǎn)的字節(jié)順序存儲數(shù)據(jù)
從結(jié)果可以看出沾乘,實例對象中存在一個isa指針指向類怜奖,和存儲含有實例變量的長度可變數(shù)據(jù)指針。
3.2.實現(xiàn)運行時系統(tǒng)的對象消息傳遞
運行時系統(tǒng)庫數(shù)據(jù)類型分為下列幾類:
類定義數(shù)據(jù)結(jié)構(gòu)(類翅阵、方法歪玲、實例變量迁央、分類、IMP滥崩、和SEL等)
實例數(shù)據(jù)類型(id岖圈、objc_object和objc_super)
值(BOOL)
對象的類定義(objc_getClass)
類的父類(class_getSuperclass)
對象的元類定義(objc_getMetaClass)
類的名稱(class_getName)
類的版本信息(class_getVersion)
以字節(jié)為單位的類尺寸(class_getInstanceSize)
類的實例變量列表(class_copyIvarList)
類的方法列表(class_copyMethodList)
類的協(xié)議列表(class_copyProtocoList)
類的屬性列表(class_copyProperyList)
當程序向?qū)ο蟀l(fā)送消息時,運行時系統(tǒng)會通過自定義代碼中的類方法緩存和虛函數(shù)表查找類的實例方法夭委。為了找到相應(yīng)的方法幅狮,運行時系統(tǒng)會搜索整個類層次結(jié)構(gòu),找到該方法后株灸,它就會執(zhí)行該方法的實現(xiàn)代碼崇摄。
元類是一種特殊的類對象,運行時系統(tǒng)使用其中含有的信息能夠找到并調(diào)用類方法慌烧。每個類都擁有獨一無二的元類逐抑。
對象的isa指針指向描述該對象的類,因此可使用該變量訪問這個對象的實例方法屹蚊、實例變量等厕氨。
上碼
//獲取元類數(shù)據(jù)
id metaClass = objc_getMetaClass("LMTestClass");
long mclzSize = class_getInstanceSize([metaClass class]);
NSData *mclzData = [NSData dataWithBytes:(__bridge const void * _Nullable)(metaClass) length:mclzSize];
NSLog(@"LMTestClass metaClass address:%@",mclzData);
long objSize = class_getInstanceSize([NSObject class]);
NSData *objData = [NSData dataWithBytes:(__bridge const void * _Nullable)([NSObject class]) length:objSize];
NSLog(@"NSObject address : %@", objData);
id objMetaClass = objc_getMetaClass("NSObject");
long objMetaSize = class_getInstanceSize([objMetaClass class]);
NSData *objMetaData = [NSData dataWithBytes:(__bridge const void * _Nullable)(objMetaClass) length:objMetaSize];
NSLog(@"NSObject MetaClass : %@",objMetaData);
LMTestClass元類帶有一個指向父類元類的指針088eb808 01000000
//LMTestClass metaClass LMTestClass metaClass address:<088eb808 01000000 088eb808 01000000 e05d1100 80610000 07000000 01000000 00af0700 00610000>
//--NSObject 地址 NSObject address : <088eb808 01000000>
因為NSObject是根類,所以它的元類isa指針指向自己
//NSObject 元類 NSObject MetaClass : <088eb808 01000000 588eb808 01000000 e005d0ed c77f0000 0f000000 02000000 00b00700 80600000>