學習的主要摘自
在oc中,可以: 1.在執行期向類中新增例項變數 2.在執行期可以繼續向類中新增方法 3.在執行期改變與給定的選擇子名稱相對應的方法(方法調配技術)
理解objc_msgSend的作用
在oc中,如果向某物件傳遞資訊,那就會使用動態繫結機制來決定需要呼叫的方法。在底層,所有方法都是普通的C語言函式.然而物件收到訊息之後,究竟該呼叫哪個方法則完全於執行期決定,甚至可以在程式執行時改變,這些特性使得oc成為一門真正的動態語言.
在oc中,給物件傳送訊息的語法是:
id returnValue = [someObject messageName:parameter];
複製程式碼
這裡,someObject叫做“接收者(receiver)”,messageName:叫做"選擇子(selector)",選擇子和引數合起來稱為“訊息”。編譯器看到此訊息後,將其轉換為一條標準的C語言函式呼叫,所呼叫的函式乃是訊息傳遞機制中的核心函式叫做objc_msgSend,它的原型如下:
void objc_msgSend(id self, SEL cmd, ...)
複製程式碼
第一個引數代表接收者,第二個引數代表選擇子,後續引數就是訊息中的那些引數,數量是可變的,所以這個函式就是引數個數可變的函式。編譯器會把剛才那個例子中的訊息轉換為如下函式
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
複製程式碼
objc_msgSend函式會依據接受者與選擇子的型別來呼叫適當的方法.為了完成此操作,該方法需要在接收者所屬的類中搜尋其"方法列表",如果能找到與選擇子相符的方法,就跳至其實現程式碼.若是找不到,就沿著繼承體系繼續向上查詢.如果找到了就執行,如果最終還是找不到,就執行訊息轉發操作.
快速執行路徑:按照上面說的想呼叫一個方法似乎需要很多步驟,所幸objc_msgSend會將匹配的結果快取在"快速對映表"裡面,每個類都有這樣一塊快取,若是稍後還向該類傳送與選擇子相同的訊息,那麼執行起來就很快了.
理解這些,就會明白,在傳送訊息時,程式碼究竟是如何執行的,而且也能理解,為何在除錯的時候,棧"回溯"資訊中總是出現objc_msgSend.
理解訊息轉發機制
若想令類能理解某條訊息,我們必須以程式碼實現出對應的方法才行.但是在編譯期向類傳送了其無法解讀的訊息並不會報錯,因為在執行期繼續可以向類中新增方法,所以編譯器在編譯時還不知道類中到底有沒有對應方法的實現.當物件接收到無法解讀的訊息後,就會啟動訊息轉發機制.程式設計師可由此過程告訴物件應該如何處理未知訊息.
開發者在編寫自己的類時,可於轉發過程中設定掛鉤,用以執行預訂的邏輯,而不使應用程式崩潰.
訊息轉發分為兩個階段:
1.徵詢接受者,看它能否動態新增方法,以處理這個未知的選擇子,這個過程叫做動態方法解析(dynamic method resolution).
2.請接受者看看有沒有其他物件能處理這條訊息:
如果有,則執行期系統會把訊息轉給那個物件。
如果沒有,則啟動完整的訊息轉發機制(full forwarding mechanism),執行期系統會把與訊息有關的全部細節都封裝到
NSInvocation物件中,再給接受者最後一次機會,令其設法解決當前還未處理的這條訊息。
類方法+(BOOL)resolveInstanceMethod:(SEL)selector:
檢視這個類是否能新增一個例項方法用以處理此選擇子
例項方法- (id)forwardTargetForSelector:(SEL)selector;
:詢問是否能找到未知訊息的備援接受者,如果能找到備援物件,就將其返回,如果不能,就返回nil。
例項方法- (void)forwardInvocation:(NSInvocation*)invocation:
建立NSInvocation物件,將尚未處理的那條訊息 有關的全部細節都封於其中,在觸發NSInvocation物件時,“訊息派發系統(message-dispatch system)”就會將訊息派給目標物件。
總結: 若物件無法響應某個選擇子,則進入訊息轉發流程. 通過執行期的動態方法解析功能,我們可以在需要用到某個方法時再將其加入類中. 物件可以把其無法解讀的某些選擇子轉交給其他物件來處理. 經過上述兩步之後,如果還是沒辦法處理選擇子,那就啟動完整的訊息轉發機制.
應用-向物件傳送沒有實現的訊息
//呼叫當前類中沒有實現的方法
[self performSelector:@selector(readBook)];
[self performSelector:@selector(writeBook)];
[self performSelector:@selector(findBook)];
}
void readBook(id self,SEL _cmd) {
NSLog(@"now i can readBook");
}
//1.動態方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod: %@",NSStringFromSelector(sel));
if (sel == @selector(readBook)) {
class_addMethod([self class], sel, (IMP)readBook, "V@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//2.備援接受者
-(id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTarget: %@",NSStringFromSelector(aSelector));
CLRead *read = [[CLRead alloc]init];
if ([read respondsToSelector:aSelector]) {
return read;
}
return [super forwardingTargetForSelector:aSelector];
}
//3.完整的訊息轉發
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"method signature for selector: %@",NSStringFromSelector(aSelector));
if (aSelector == @selector(code)) {
return [NSMethodSignature signatureWithObjCTypes:"V@:"];
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation: %@",NSStringFromSelector([anInvocation selector]));
if ([anInvocation selector] == @selector(code)) {
CLRead *read = [[CLRead alloc]init];
[anInvocation invokeWithTarget:read];
}
}
複製程式碼
方法調配技術
在oc中,與給定的選擇子名稱相對應的方法是不是也可以在執行期改變呢?沒錯,若能善用此特性,則可發揮出巨大優勢,因為我們既不需要原始碼,也不需要通過繼承子類來覆寫方法就能改變這個類本身的功能.這樣一來,新功能將在本類的所有例項中生效,而不是僅限於覆寫了相關方法的那些子類例項.此方案經常稱為"方法調配"
類的方法列表會把選擇子的名稱對映到相關的方法實現之上,使得"動態訊息派發系統"能夠據此找到應該呼叫的方法.這些方法均以函式指標的形式來表示,這種指標叫做IMP,其原型如下:
id (*IMP)(id,SEL,...)
複製程式碼
交換方法實現
交換方法實現,可用下列函式:
void method_exchangeImplementations(Method m1,Method m2)
複製程式碼
此函式的兩個參數列示待交換的兩個方法實現,而方法實現則可通過下列函式獲得:
Method class_getInstanceMethod(Class aClass, SEL aSelector)
複製程式碼
此函式根據給定的選擇從類中取出與之相關的方法.
很少有人在除錯程式之外的場合用上述"方法調配技術"來永久改動某個類的功能.不能僅僅因為Objective-c語言裡有這個特性就一定要用它.若是濫用,反而會令程式碼變得不易讀懂且難於維護.
應用-防止陣列越界的crach
//在NSArray中新增分類
+(void)load {
//替換不可變陣列中的方法 []呼叫的方法
Method oldObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));
Method newObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedNewSubscript:));
method_exchangeImplementations(oldObjectAtIndex, newObjectAtIndex);
//替換不可變陣列中的objectAtIndex
Method oldObjectAtIndex1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method newObjectAtIndex1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(newObjectAtIndex:));
method_exchangeImplementations(oldObjectAtIndex1, newObjectAtIndex1);
//替換可變陣列中的方法 []呼叫的方法
Method oldMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndexedSubscript:));
Method newMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(mutableObjectAtIndexedNewSubscript:));
method_exchangeImplementations(oldMutableObjectAtIndex, newMutableObjectAtIndex);
//替換可變陣列中的方法 objectatindex
Method oldMutableObjectAtIndex1 = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:));
Method newMutableObjectAtIndex1 = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(newMutableObjectAtIndex:));
method_exchangeImplementations(oldMutableObjectAtIndex1, newMutableObjectAtIndex1);
}
-(id)newMutableObjectAtIndex:(NSUInteger)index {
if (index > self.count - 1 || !self.count) {
@try {
//因為前面方法交換了,這裡其實呼叫的原生的方法
return [self newMutableObjectAtIndex:index];
} @catch (NSException *exception) {
NSLog(@"可變陣列越界了");
return nil;
} @finally {
}
}else {
return [self newMutableObjectAtIndex:index];
}
}
-(id)mutableObjectAtIndexedNewSubscript:(NSUInteger)index {
if (index > self.count - 1 || !self.count) {
@try {
//因為前面方法交換了,這裡其實呼叫的原生的方法
return [self mutableObjectAtIndexedNewSubscript:index];
} @catch (NSException *exception) {
NSLog(@"可變陣列越界了");
return nil;
} @finally {
}
}else {
return [self mutableObjectAtIndexedNewSubscript:index];
}
}
-(id)newObjectAtIndex:(NSUInteger)index {
if (index > self.count - 1 || !self.count) {
@try {
//因為前面方法交換了,這裡其實呼叫的原生的方法
return [self newObjectAtIndex:index];
} @catch (NSException *exception) {
NSLog(@"不可變陣列越界了");
return nil;
} @finally {
}
}else {
return [self newObjectAtIndex:index];
}
}
-(id)objectAtIndexedNewSubscript:(NSUInteger)index {
if (index > self.count - 1 || !self.count) {
@try {
//因為前面方法交換了,這裡其實呼叫的原生的方法
return [self objectAtIndexedNewSubscript:index];
} @catch (NSException *exception) {
NSLog(@"不可變陣列越界了");
return nil;
} @finally {
}
}else {
return [self objectAtIndexedNewSubscript:index];
}
}
複製程式碼
編譯期
編譯時就是獲取objc檔案.