iOS runtime執行時的用法(一)
<span style="color:#333333">RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。</span>
編譯時: 即編譯器對語言的編譯階段,編譯時只是對語言進行最基本的檢查報錯,包括詞法分析、語法分析等等,將程式程式碼翻譯成計算機能夠識別的語言(例如彙編等),編譯通過並不意味著程式就可以成功執行。
執行時: 即程式通過了編譯這一關之後編譯好的程式碼被裝載到記憶體中跑起來的階段,這個時候會具體對型別進行檢查,而不僅僅是對程式碼的簡單掃描分析,此時若出錯程式會崩潰。
可以說編譯時是一個靜態的階段,型別錯誤很明顯可以直接檢查出來,可讀性也好;而執行時則是動態的階段,開始具體與執行環境結合起來。
OC語言的動態性主要體現在三個方面:動態型別(Dynamic typing)、動態繫結(Dynamic binding)和動態載入(Dynamic loading)。
動態型別指的是物件指標型別的動態性,具體是指使用id任意型別將物件的型別確定推遲到執行時,由賦給它的物件型別決定物件指標的型別。另外型別確定推遲到執行時之後,可以通過NSObject的isKindOfClass方法動態判斷物件最後的型別(動態型別識別)。也就是說id修飾的物件為動態型別物件,其他在編譯器指明型別的為靜態型別物件,通常如果不需要涉及到多型的話還是要儘量使用靜態型別(原因上面已經說到:錯誤可以在編譯器提前查出,可讀性好)。(編譯的時候不會檢查物件的型別,執行的時候才會檢查物件的型別,)
動態繫結指的是方法確定的動態性,具體指的是利用OC的訊息傳遞機制將要執行的方法的確定推遲到執行時,可以動態新增方法。也就是說,一個OC物件是否呼叫某個方法不是由編譯器決定的,而是由執行時決定的;另外關於動態繫結的關鍵一點是基於訊息傳遞機制的訊息轉發機制,主要處理應對一些接受者無法處理的訊息,此時有機會將訊息轉發給其他接收者處理,具體見下面介紹。(編譯的時候不會決定執行那個方法,執行的時候給物件傳送訊息,決定執行那個方法)
動態繫結是基於動態型別的,在執行時物件的型別確定後,那麼物件的屬性和方法也就確定了(包括類中原來的屬性和方法以及執行時動態新加入的屬性和方法),這也就是所謂的動態綁定了。動態繫結的核心就該是在執行時動態的為類新增屬性和方法,以及方法的最後處理或轉發,主要用到C語言語法,因為涉及到執行時,因此要引入執行時標頭檔案:#include <objc/runtime.h>
訊息傳遞機制:
在OC中方法的呼叫實際不是物件呼叫其方法,而是要理解成物件接受訊息,方法的呼叫實際就是告訴物件要幹什麼,給物件(的指標)傳送一個訊息。
對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式。
對於OC的函式,屬於動態呼叫過程,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行的時候才會根據函式的名稱找到對應的函式來呼叫。
事實證明:
·在編譯階段,OC可以呼叫任何函式,即使這個函式並未實現,只要宣告過就不會報錯。
·在編譯階段,C語言呼叫未實現的函式就會報錯。
///OC方法呼叫的本質,就是給物件傳送訊息。
在 Objective-C 中,執行 [object foo] 語句並不會立即執行 foo 這個方法的程式碼。它是在執行時給 object 傳送一條叫 foo 的訊息。這個訊息,也許會由 object 來處理,也許會被轉發給另一個物件,或者不予理睬假裝沒收到這個訊息。多條不同的訊息也可以對應同一個方法實現。這些都是在程式執行的時候決定的。
編譯時你寫的 Objective-C 函式呼叫的語法都會被翻譯成一個 C 的函式呼叫 - objc_msgSend()。比如,下面兩行程式碼就是等價的:
[p eat]; // 本質:給物件傳送訊息
objc_msgSend(p, @selector(eat));
// 本質:給物件傳送訊息
objc_msgSend(p, @selector(eat));
訊息傳遞過程:
- 首先,找到 object 的 class;
- 通過 class 找到 foo 對應的方法實現;
- 如果 class 中沒到 foo,繼續往它的 superclass 中找,一直找到NSObjct類為止;
- 一旦找到 foo 這個函式,就去執行它的實現
- 如果沒找到就進入執行時的動態方法解析,可以用執行時新增方法;
訊息直到執行時才會與方法實現進行繫結。
這裡要清楚一點,objc_msgSend
方法看清來好像返回了資料,其實objc_msgSend
從不返回資料,而是你的方法在執行時實現被呼叫後才會返回資料。
- 首先檢測這個
selector
是不是要忽略。比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函式。 - 檢測這個
selector
的 target 是不是nil
,Objc 允許我們對一個 nil 物件執行任何方法不會 Crash,因為執行時會被忽略掉。 - 如果上面兩步都通過了,那麼就開始查詢這個類的實現
IMP
,先從 cache 裡查詢,如果找到了就執行對應的函式去執行相應的程式碼。 - 如果 cache 找不到就找類的方法列表中是否有對應的方法。
- 如果類的方法列表中找不到就到父類的方法列表中查詢,一直找到 NSObject 類為止。
- 如果還找不到,就要開始進入動態方法解析了。
在訊息的傳遞中,編譯器會根據情況在 objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
, objc_msgSendSuper_stret
這四個方法中選擇一個呼叫。如果訊息是傳遞給父類,那麼會呼叫名字帶有 Super 的函式,如果訊息返回值是資料結構而不是簡單值時,會呼叫名字帶有 stret 的函式。
動態方法解析會在訊息轉發機制侵入前執行,動態方法解析器將會首先給予提供該方法選擇器對應的
IMP
的機會。如果你想讓該方法選擇器被傳送到轉發機制,就讓resolveInstanceMethod:
方法返回NO
。
訊息轉發
重定向
訊息轉發機制執行前,Runtime 系統允許我們替換訊息的接收者為其他物件。通過 - (id)forwardingTargetForSelector:(SEL)aSelector
方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
如果此方法返回 nil
或者 self
,則會計入訊息轉發機制(forwardInvocation:
),否則將向返回的物件重新發送訊息。
轉發
當動態方法解析不做處理返回 NO
時,則會觸發訊息轉發機制。這時 forwardInvocation:
方法會被執行,我們可以重寫這個方法來自定義我們的轉發邏輯:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
唯一引數是個 NSInvocation
型別的物件,該物件封裝了原始的訊息和訊息的引數。我們可以實現 forwardInvocation:
方法來對不能處理的訊息做一些處理。也可以將訊息轉發給其他物件處理,而不丟擲錯誤。
注意:引數
anInvocation 是從哪來的?
在forwardInvocation:
訊息傳送前,Runtime 系統會向物件傳送methodSignatureForSelector:
訊息,並取到返回的方法簽名用於生成 NSInvocation 物件。所以重寫forwardInvocation:
的同時也要重寫methodSignatureForSelector:
方法,否則會拋異常。
當一個物件由於沒有相應的方法實現而無法相應某訊息時,執行時系統將通過 forwardInvocation:
訊息通知該物件。每個物件都繼承了 forwardInvocation:
方法。但是, NSObject
中的方法實現只是簡單的呼叫了 doesNotRecognizeSelector:
。通過實現自己的 forwardInvocation:
方法,我們可以將訊息轉發給其他物件。
forwardInvocation:
方法就是一個不能識別訊息的分發中心,將這些不能識別的訊息轉發給不同的接收物件,或者轉發給同一個物件,再或者將訊息翻譯成另外的訊息,亦或者簡單的“吃掉”某些訊息,因此沒有響應也不會報錯。這一切都取決於方法的具體實現。
注意:
forwardInvocation:
方法只有在訊息接收物件中無法正常響應訊息時才會被呼叫。所以,如果我們嚮往一個物件將一個訊息轉發給其他物件時,要確保這個物件不能有該訊息的所對應的方法。否則,forwardInvocation:
將不可能被呼叫。
==========================
使用訊息機制前提,必須匯入#import <objc/message.h>
訊息機制簡單使用
Person *p = [[Person alloc] init]; // 呼叫物件方法 [p eat]; // 本質:給物件傳送訊息 objc_msgSend(p, @selector(eat));
// 呼叫類方法的方式:兩種
// 第一種通過類名呼叫
[Person eat]; // 第二種通過類物件呼叫
[[Person class] eat];
// 用類名呼叫類方法,底層會自動把類名轉換成類物件呼叫
// 本質:讓類物件傳送訊息
objc_msgSend([Person class], @selector(eat));
// 呼叫類方法的方式:兩種
// 第一種通過類名呼叫
[Person eat]; // 第二種通過類物件呼叫
[[Person class] eat];
// 用類名呼叫類方法,底層會自動把類名轉換成類物件呼叫
// 本質:讓類物件傳送訊息
objc_msgSend([Person class], @selector(eat));
執行時的應用:
1.將某些OC程式碼轉為執行時程式碼,探究底層,比如block的實現原理(上邊已講到);
2.攔截系統自帶的方法呼叫(Swizzle 黑魔法),比如攔截imageNamed:、viewDidLoad、alloc;
3.實現分類也可以增加屬性;
4.實現NSCoding的自動歸檔和自動解檔;
5.實現字典和模型的自動轉換。
=================動態新增成員變數======================================
/*
1. 分類不可以擴充成員變數
2. 分類可以擴充屬性,但是屬性不可以儲存值.因為分類中沒有成員變數.我們需要重寫setter和getter
3. 分類可以擴充方法
*/
//設定值
- (void)setLastUrl:(NSString *)lastUrl
{
/*
1. 需要關聯的物件 self
2. 需要關聯的物件的屬性的鍵值(key)
3. 需要關聯的物件的屬性lasturl
4. 需要關聯的物件的屬性的修飾符(策略)
*/
objc_setAssociatedObject(self,"key", lastUrl,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//獲取值
- (NSString *)lastUrl
{
/*
1. 需要關聯的物件 self
2. 需要關聯的物件的屬性的鍵值(key)
*/
returnobjc_getAssociatedObject(self,"key");
}
==========================
runtime 實現方法交換:
#import <UIKit/UIKit.h>
@interface UIImage (XL)
+ (instancetype)imageWithName:(NSString *)name;
@end
///////////////
#import "UIImage+XL.h"
#import <objc/runtime.h>
@implementation UIImage (XL)
+ (void)load
{
Method m1 =class_getClassMethod([UIImageclass],@selector(imageWithName:));
Method m2 =class_getClassMethod([UIImageclass],@selector(imageNamed:));;
method_exchangeImplementations(m1, m2);
}
+ (instancetype)imageWithName:(NSString *)name
{
UIImage *image = [UIImage imageWithName:imageName];
if (image ==nil) {
image = [UIImageimageWithName:name];
}
return image;
}
@end
===================================
#import <Foundation/Foundation.h>
@interface NSMutableArray (XL)
@end
///////////
#import "NSMutableArray+XL.h"
#import <objc/runtime.h>
@implementation NSMutableArray (XL)
+ (void)load
{
Method m1 =class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(cz_addObjct:));
Method m2 =class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(addObject:));;
method_exchangeImplementations(m1, m2);
}
- (void)cz_addObjct:(id)objc
{
if (objc ==nil) {
[selfcz_addObjct:@"you are sb! is nil "];
return;
}
[selfcz_addObjct:objc];
}
@end
===================================
交換原理:當一個類載入到記憶體中時,需要給每個類方法或物件方法生成方法對映表,需要呼叫方法的時候根據方法編號(SEL型別的方法選擇器)到方法對映表中去尋查詢,然後找到對應的函式實現;
·交換之前:
·交換之後:
==========動態的新增方法========
一個c語言函式方法都有兩個隱式引數:
void dynamicMethodIMP(id self, SEL _cmd)
{
NSLog(@"//動態新增的方法在這裡實現");
}
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
如果一個類方法非常多,載入類到記憶體的時候也比較耗費資源,需要給每個方法生成對映表,可以使用動態給某個類,新增方法解決;
在OC中,所有的控制元件(textFiled或者button等控制元件), 陣列, 資料等都是以懶載入的形式載入的.真正使用的時候才會載入,或者新增方法.動態新增的方法的作用就是去處理未實現的例項方法或者是類方法.它的呼叫時刻: 只要我們呼叫了一個不存在的方法時,它就會動態新增方法.
動態新增方法之前我們還需要判斷當前的方法有沒有實現,如果沒有實現才需要動態新增方法.
作為一種動態程式語言,Objective-C 擁有一個執行時系統來支援動態建立類,新增方法、進行訊息傳遞和轉發。
Objective-C 執行時會呼叫 +resolveInstanceMethod: 或者 +resolveClassMethod:,讓你有機會提供一個函式實現。如果你添加了函式並返回 YES, 那執行時系統就會重新啟動一次訊息傳送的過程。
如果 resolveInstanceMethod: 方法返回 NO,執行時就會進行下一步:訊息轉發(Message Forwarding)。
動態去判斷下eat方法有沒有實現,如果沒有實現,動態新增.
// 作用:處理未實現的物件方法
// 呼叫時刻:只要呼叫了一個不存在的物件方法就會呼叫
// sel:就是未實現方法編號
// 判斷物件方法有沒有實現
+(BOOL)resolveInstanceMethod:(SEL)sel
// 判斷類方法有沒有實現
+ (BOOL)resolveClassMethod:(SEL)sel
Class cls
cls 引數表示需要新增新方法的類。
1 |
|
name 引數表示 selector 的方法名稱,可以根據喜好自己進行命名。
1 |
|
imp 即 implementation ,表示由編譯器生成的、指向實現方法的指標。也就是說,這個指標指向的方法就是我們要新增的方法。
1 |
|
最後一個引數 *types 表示我們要新增的方法的返回值和引數。
/ 引數解釋:
// Class;給哪個類新增方法
// SEL:要新增的方法
// IMP:方法實現,函式名(實現卻是在另外一個方法,和新增的方法不一樣)
// types:函式的型別,(返回值+引數型別) v:void @:物件->self :表示SEL->_cmd
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
-(void)resolveThisMethodDynamically;//動態新增的是這個方法,但是實現卻是在另外一個方法中
@end
==================
在.m中
- (void)viewDidLoad {
[superviewDidLoad];
[selfresolveThisMethodDynamically];//呼叫的是沒有實現的方法,是動態新增的
}
//動態新增的方法在這裡實現
void dynamicMethodIMP(idself, SEL_cmd)
{
NSLog(@"//動態新增的方法在這裡實現");
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL ==@selector(resolveThisMethodDynamically))
{
class_addMethod([selfclass], aSEL, (IMP)dynamicMethodIMP, "[email protected]:");
returnYES;
}
return [superresolveInstanceMethod:aSEL];
}
========================
runtime的方法:
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(klass,
&propertyCount);//獲取所有的屬性物件
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);//獲取屬性名字
const char * attributes = property_getAttributes(property);//獲取屬性型別
}
objc_property_t *properties = class_copyPropertyList(klass,
&propertyCount);//獲取所有的屬性物件
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);//獲取屬性名字
const char * attributes = property_getAttributes(property);//獲取屬性型別
}
- 獲得某個類的所有成員變數(outCount 會返回成員變數的總數)
引數:
1、哪個類
2、放一個接收值的地址,用來存放屬性的個數
3、返回值:存放所有獲取到的屬性,通過下面兩個方法可以調出名字和型別Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
- 獲得成員變數的名字(帶下劃線 _name)
const char *ivar_getName(Ivar v)
- 獲得成員變數的型別
const char *ivar_getTypeEndcoding(Ivar v)
型別編碼
為了幫助執行時系統,編譯器將每個方法中的返回和引數型別進行編碼,並將該字串與該方法選擇器關聯。在其他情況下,編碼體系也是很有用的,所以編碼體系是帶有@encode()編譯指令的工公共的可用的。當給一個指定型別,@encode()返回指定的型別的字串編碼。這個型別可以是任何型別,可以是基本型別,如int型指標,可以是一個標記結構或聯合,或類名,可以被C語言的sizeof()運算子作為引數使用。
下面的表格列出了編碼型別。注意當對一個物件歸檔或者分發時,他們中的許多程式碼與你使用的程式碼重疊。然而,這些列表中的編碼在你歸檔的時候不能使用他們,你可能想要在歸檔使用那些不是@encode()生成的程式碼。
編碼型別
相關推薦
iOS runtime執行時的用法(一)
<span style="color:#333333">RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。</span> 編譯時: 即編譯器對語言的編譯階段,編譯時只是對語言進行最基本的檢查
iOS Runtime 執行時之三:訊息處理機制
前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別
iOS runtime執行時特性解決陣列越界問題。
@interface NSObject (Exchange) -(void)swizzleMethod:(SEL)OldSel withNewMethod:(SEL)NewSel; @end#import "NSObject+Exchange.h" @implem
Runtime執行時用法1------獲取類屬性列表
提起Runtime執行時很多初學者會望而卻步, 但是仔細想想, 我們要讀懂別人框架的實現原理, 這些東西還是需要去學習了, 正所謂, 怕什麼什麼就是你的缺點, 面對我們知識層面的不足, 我們一定要勇
iOS中runtime(執行時)的簡單介紹與應用
最近學習到runtime這一塊知識了所以就總結一下,以備以後要用。但是由於runtime這一塊的知識比較多所以今天在這裡只是做一個簡單的總結。 一、什麼是Runtime? Objective-C runtime是一個實現Objective-C語言的
iOS開發之關於Runtime執行時:類與物件
Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這種動態語言的優勢在於:我們寫程式碼時更具靈活性,如我們可以把訊息轉發給我們想要的物件,或者隨意交換一個方法的實現等。 這種特性意味著Objective-C不僅需要一個編譯器,還需要一個執行時系統
iOS學習筆記56(Runtime)-Objective-C Runtime 執行時之三:方法與訊息
前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL
runtime 執行時機制
runtime是一套比較底層的純C語言API, 屬於1個C語言庫, 包含了很多底層的C語言API。 在我們平時編寫的OC程式碼中, 程式執行過程時, 其實最終都是轉成了runtime的C語言程式碼, runtime算是OC的幕後工作者 比如說,下面一個建立物件的方法中, 舉例: OC : [[MJP
iOS-APP-執行時防Crash工具XXShield練就
原文地址 前言 正在執行的 APP 突然 Crash,是一件令人不爽的事,會流失使用者,影響公司發展,所以 APP 執行時擁有防 Crash 功能能有效降低 Crash 率,提升 APP 穩定性。但是有時候 APP Crash 是應有的表現,我們不讓 APPCrash 可能會導致別的邏輯錯誤
Java中Runtime執行時環境機制總結
最近由於在編碼中需要在java程式碼中執行linux命令,使用到了Runtime類的一些方法,也出現幾個小bug,所以趁這個機會對Runtime也就是執行時環境這個類進行總結。 Runtime.getRuntime()能得到一個Runtime物件例項,也就是當前執行時環境例
Objective-C Runtime 執行時之五:協議與分類
Objective-C中的分類允許我們通過給一個類新增方法來擴充它(但是通過category不能新增新的例項變數),並且我們不需要訪問類中的程式碼就可以做到。 Objective-C中的協議是普遍存在的介面定義方式,即在一個類中通過@protocol定義介面,在另外
Objective-C Runtime 執行時之六:拾遺
前面幾篇基本介紹了runtime中的大部分功能,包括對類與物件、成員變數與屬性、方法與訊息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。 本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏
js執行時休眠一段時間
自定義一個函式sleep function sleep(numberMillis) { var now = new Date(); var enterTime= now.getTime() + numberMillis; &nbs
Objective-C Runtime 執行時之一:類與物件
Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這種動態語言的優勢在於:我們寫程式碼時更具靈活性,如我們可以把訊息轉發給我們想要的物件,或者隨意交換一個方法的實現等。 這種特性意味著Objective-C不僅需要一
Objective-C Runtime 執行時之二:成員變數與屬性
在前面一篇文章中,我們介紹了Runtime中與類和物件相關的內容,從這章開始,我們將討論類實現細節相關的內容,主要包括類中成員變數,屬性,方法,協議與分類的實現。 本章的主要內容將聚集在Runtime對成員變數與屬性的處理。在討論之前,我們先介紹一個重要的概念:型別
Objective-C Runtime 執行時之三:方法與訊息
前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL
Objective-C Runtime 執行時之四:Method Swizzling
理解Method Swizzling是學習runtime機制的一個很好的機會。在此不多做整理,僅翻譯由Mattt Thompson發表於nshipster的Method Swizzling一文。 Method Swizzling是改變一個selector的實際實現的
詳解Runtime執行時機制
簡介 Runtime 又叫執行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 程式碼,底層都是基於它來實現的。比如: [receiver message]; // 底層執行時會被編譯器轉化為: objc_msgSend(receive
Runtime執行時-應用篇
在上篇文章iOS執行時Runtime基礎後,本篇將會總結Rutime的具體應用例項,結合其動態特性,Runtime在開發中的應用大致分為以下幾個方面: 一、動態方法交換:Method Swizzling 實現動態方法交換(Method Swizz
[ObjectC]Runtime執行時之三:方法與訊息
這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。 基礎資料型別 SEL SEL又叫選擇器,是表示一個方法的selector的指標,其定義如下:typedef struct objc_selector *SEL;o