概述
iOS運行時(RunTime)系統(tǒng)以前一直聽說,也自己看到各種大牛的技術(shù)博客學(xué)習(xí)過,自己也曾經(jīng)敲過相關(guān)的代碼,但是發(fā)現(xiàn)因為自己沒有好好記錄,因為學(xué)習(xí)不是一次性就可以完成的,導(dǎo)致效率并不高,不能很快的從上次學(xué)習(xí)的位置繼續(xù)。我個人不擅長理論知識論述,關(guān)于運行時系統(tǒng)的理論相信網(wǎng)上已經(jīng)有很多大牛已經(jīng)詳細介紹過了,我自己更多的是總結(jié)學(xué)習(xí)。所以,自己建立了個可以長久積累知識的工程,用來記錄知識點,并且以代碼為導(dǎo)向,通過用運行時的方法實現(xiàn)一些功能,達到驗證理論知識和學(xué)習(xí)的目的,以后還是會增加新的代碼的,希望和大家一起分享。因為工程內(nèi)容主要是對理論知識的驗證和運用,所以沒有華麗的界面,所以的內(nèi)容都是在控制臺輸出的。相信自己的注釋在邏輯方法足夠詳細,也許對理論知識注釋有所欠缺,但是相信每個人都能夠根據(jù)上下文猜懂
鏈接:https://github.com/xiaobai1993/RunTimeTest
下面的類是BOOK類,這個類是自己主要驗證運行時的一些方法所用的測試類,比如下面代碼注釋可以看到的,獲取,修改類的屬性,方法個數(shù),參數(shù)和返回值等,并且通過這個類只聲明了一些方法,并沒有實現(xiàn),通過運行時動態(tài)的轉(zhuǎn)發(fā)機制處理錯誤,在不同的階段分別解決崩潰問題。
@interface BOOK : NSObject
{
NSString * ivarMember;//成員變量
}
@property (nonatomic,strong) NSString * name;
@property (nonatomic,assign) CGFloat price;
@property (nonatomic,strong) NSArray * authors;
@property (nonatomic,assign) int pubishYear;
//---本類定義并且已經(jīng)實現(xiàn)了的方法
-(void)printMessage;
//------運行時方法
-(NSArray*)allPropertyNames;//獲取所有的屬性變量
-(NSArray*)allPropertyAttr;//獲取所有的屬性變量的具體信息
-(NSArray*)allIvarNames;//所有的成員變量
-(void)allMethods;
//--測試運行時調(diào)用類中存在并實現(xiàn)的方法,這三個方法可以不再這里聲明。運行時也能找到,并調(diào)用.
//第一層次
-(void)executeMethodByRunTime_00;
-(void)executeMethodByRunTime_01:(NSString *)param;
-(NSString *)executeMethodByRunTime_02:(NSString *)param;
//第二層次,調(diào)用沒有實現(xiàn)的方法,利用運行時解決崩潰問題。
//這個房沒有實現(xiàn),只有聲明,包含了兩個參數(shù)和返回值,正常在外面調(diào)用時崩潰的,現(xiàn)在嘗試用運行時采用多種不同的方式處理崩潰。并執(zhí)行某些方法
// resolveInstanceMethod 階段解決未定義的方法
-(NSString *)onlyDeclareMethod:(NSString *)first andWithSec:(NSString*)second;
//測試在 - (id)forwardingTargetForSelector:(SEL)aSelector由其他類處理響應(yīng)這個方法
-(NSString *)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString*)second;
//測試在
-(NSString *)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString*)second;
下面是具體的實現(xiàn),實驗的驗證和原理已經(jīng)在過程中注釋,相信可以自己再過較長的時間也可以記得。代碼的實現(xiàn)過程中遇到問題主要是查閱蘋果文檔,因為發(fā)現(xiàn)這部分的英文文檔貌似沒有自己想像的晦澀難懂
#import "BOOK.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "Pen.h"
#define MAXLength 100
@interface BOOK ()
{
@private
//私有的實例變量
NSString * privateIvar;
}
//私有屬性
@property (nonatomic,strong) NSString * privateProperty;
@end
@implementation BOOK
//類方法,本類定義的方法
//-------------------------------------------------------
-(void)printMessage
{
NSLog(@"私有成員變量 privateIvar = %@",privateIvar);
}
//--------------------------------------------------------
//----屬性和方法獲取相關(guān)的內(nèi)容 讀和改
//獲取所有的屬性變量
/**
property_getName獲取屬性名字
*/
-(NSArray*) allPropertyNames{
NSMutableArray * pNames = [[NSMutableArray alloc]init];
unsigned int count ;
objc_property_t * propertys=class_copyPropertyList([self class], &count);
for (int i = 0; i<count; i++) {
const char * pty = property_getName(propertys[i]);
[pNames addObject:[NSString stringWithUTF8String:pty]];
}
free(propertys);//必須釋放
return [pNames copy];
}
-(NSArray*)allIvarNames //這里讀取的值包括了property聲明的屬性,這些屬性以_開頭,所以這里可以更改所有的變量的值
{
NSMutableArray * ivarNames = [[NSMutableArray alloc]init];
unsigned int count ;
Ivar * iVars=class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
const char * var = ivar_getName(iVars[i]);
//選擇性賦值.
if (strcmp(var , "privateIvar")==0) {
object_setIvar(self, iVars[i], @"runTime");//賦值
object_getIvar(self, iVars[i]);//這里是讀取值
}
[ivarNames addObject:[NSString stringWithUTF8String:var]];
}
free(iVars);//必須釋放
return [ivarNames copy];
}
//獲取所有的屬性變量的具體信息
/**:
>property_getAttributes 方法是關(guān)鍵
"T@\"NSString\",&,N,V_name",
"Td,N,V_price",
"T@\"NSArray\",&,N,V_authors",
"Ti,N,V_pubishYear"
You can use the property_getAttributes function to discover the name, the @encode type string of a property, and other attributes of the property.
The string starts with a T followed by the @encode type and a comma, and finishes with a V followed by the name of the backing instance variable. Between these, the attributes are specified by the following descriptors, separated by commas:
--------
T表示屬性字符串開始,V表示結(jié)束,T后面是數(shù)據(jù)類型d表示的是double,逗號后面表示的是屬性變量的類型,N表示nonatomic
其他類型 Objective-C Runtime Programming Guide --> Type Encodings 和 Property Type String
*/
-(NSArray*)allPropertyAttr{
NSMutableArray * pNamesAttr = [[NSMutableArray alloc]init];
unsigned int count ;
objc_property_t * propertys=class_copyPropertyList([self class], &count);
for (int i = 0; i<count; i++) {
const char * pty = property_getAttributes(propertys[i]);
[pNamesAttr addObject:[NSString stringWithUTF8String:pty]];
}
free(propertys);//必須釋放
return [pNamesAttr copy];
}
//獲取所有的方法
/**
*
Method:http://blog.csdn.net/woaifen3344/article/details/50505808
SEL:
Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
You can add new selectors at runtime and retrieve existing selectors using the function sel_registerName.
When using selectors, you must use the value returned from sel_registerName or the Objective-C compiler directive @selector(). You cannot simply cast a C string to SEL.
*/
-(void)allMethods
{
//MAXLenth 其實沒有必要為100,只是系統(tǒng)給的方法必須要傳遞一個長度,自己只能根據(jù)返回值估計一個。防止復(fù)制的長度不夠,實際上10也許就夠了
unsigned int methodCount;
//Method方法默認的參數(shù)有兩個的
#pragma mark -- OC里面的方法在運行時都是轉(zhuǎn)換為了objc_send函數(shù),這個方法第一個參數(shù)是調(diào)用者,第二個是方法名。默認的兩個參數(shù)就是調(diào)用者和方法名
Method *methods = class_copyMethodList([self class], &methodCount);
for (int i=0; i<methodCount; i++) {
SEL methodSel = method_getName(methods[i]);//先轉(zhuǎn)換為SEL類型,消息選擇器
NSLog(@"方法的名字 = %s",sel_getName(methodSel));
unsigned argumentCnt = method_getNumberOfArguments(methods[i]);
NSLog(@"方法的參數(shù)個數(shù) number = %d ",argumentCnt);
for (int j = 0 ; j< argumentCnt;j++) {
char argumentsType[MAXLength];
method_getArgumentType(methods[j], j, argumentsType, MAXLength);
NSLog(@"第%d個參數(shù)是%s",j,argumentsType);
}
char returnType[MAXLength];
method_getReturnType(methods[i], returnType, MAXLength);
NSLog(@"方法的返回類型%s",returnType);
//方法的實現(xiàn)
/**
A pointer to the start of a method implementation.
Declaration
id (*IMP)(id, SEL, ...)
指向方法的函數(shù)指針
*/
//#pragma waring --- IMP暫時不會用,后面的class_addMethod方法用到了動態(tài)添加方法
IMP imp = method_getImplementation(methods[i]);
}
free(methods);
}
//--------------------------------------------------SET和GET方法
//獲取get方法
-(SEL)createGetMethodWithName:(NSString *)pName
{
return NSSelectorFromString(pName);
}
//這是根據(jù)字符串拼接生成的set方法
- (SEL)propertySetterWithKey:(NSString *)key {
NSString *propertySetter = key.capitalizedString;
propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
//生成setter方法
SEL setter = NSSelectorFromString(propertySetter);//set方法
if ([self respondsToSelector:setter]) {
return setter;
}
return nil;
}
//--- 通過運行時執(zhí)行方法
//第一層次
-(void)executeMethodByRunTime_00
{
//通過objc_msgSend方法
NSLog(@"這個方法用來通過運行時驗證一個類已經(jīng)存在的一個方法,在main調(diào)用");
//OC的方法調(diào)用的時候會在運行時系統(tǒng)轉(zhuǎn)換為特定的格式。
}
-(void)executeMethodByRunTime_01:(NSString *)param
{
//通過objc_msgSend方法
NSLog(@"這個方法用來通過運行時驗證一個帶有參數(shù):%@",param);
//OC的方法調(diào)用的時候會在運行時系統(tǒng)轉(zhuǎn)換為特定的格式。
}
//帶有方法和返回值
-(NSString *)executeMethodByRunTime_02:(NSString *)param
{
//通過objc_msgSend方法
NSLog(@"這個方法用來通過運行時驗證一個帶有參數(shù)和返回值");
//OC的方法調(diào)用的時候會在運行時系統(tǒng)轉(zhuǎn)換為特定的格式。
return [NSString stringWithFormat:@"%@runtime",param];
}
// 第二層次,通過解決不存在的方法調(diào)用導(dǎo)致的崩潰,探索方法調(diào)用流程,這里僅說實例方法,類方法應(yīng)該一樣。會一次調(diào)用下面的方法
// 1. 只聲明未定義 先執(zhí)行這里如果這里執(zhí)行了也算成功
/**
This method is called before the Objective-C forwarding mechanism is invoked. If respondsToSelector: or instancesRespondToSelector: is invoked, the dynamic method resolver is given the opportunity to provide an IMP for the given selector first.
這個方法會在OC的轉(zhuǎn)發(fā)機制前被調(diào)用,respondsToSelector,instancesRespondToSelector方法被調(diào)用了,這個方法會首先給一個機會提供一個方法的實現(xiàn)
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// you can use resolveInstanceMethod: to dynamically add it to a class as a method (called resolveThisMethodDynamically) like this:
//根據(jù)蘋果的文檔,自己解決在,函數(shù)
if (sel == @selector(onlyDeclareMethod:andWithSec:))//可以在為沒有實現(xiàn)的方法添加動態(tài)添加一個方法
{
//最后一個參數(shù)是函數(shù)的參數(shù)和返回值類型,可以根據(jù)文檔查閱具體的,從左到右依次是函數(shù)返回值和各個參數(shù)
//第一個@表示返回值是對象(NSString*)是對象的一種,第二個@表示的是第一個參數(shù)id類型,":"表示參數(shù)SEL類型,后面兩個表示
//first和second
class_addMethod([self class], sel, (IMP)implementOnlyDeclare, "@@:@@");
return YES;
}
return [super resolveInstanceMethod:sel];
//最后一位是函數(shù)的編碼格式,需要查看手冊
//class_addMethod(self, sel, (IMP)implementMethod, "v");
}
//--resolveInstanceMethod階段
//--- 自己動態(tài)為沒有實現(xiàn)的方法添加一個方法,class_addMethod方法添加的方法至少包含兩個參數(shù),id,_cmd,自己猜測是因為這里添加的是方法是
//OC的方法,OC的方法在運行時中的C語言方法執(zhí)行時用的objc_msgSend函數(shù)調(diào)用格式有關(guān)
NSString *implementOnlyDeclare(id class ,SEL sel,NSString * first,NSString * second)
{
return [NSString stringWithFormat:@"在resolveInstanceMethod中通過addMethod方法實現(xiàn)方法:%@+%@",first,second];
}
// 2.resolveInstanceMethod:(SEL)sel 未找到正確的方法會執(zhí)行,會走到這個方法
//用于指定哪個對象響應(yīng)這個selector。不能指定為self。若返回nil,表示沒有響應(yīng)者,則會進入第三步。若返回某個對象,則會調(diào)用該對象的方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(onlyDeclareMethod2:andWithSec:)) {
return [[Pen alloc]init];//由Pen類代替自己去處理
}
return nil;
}
//3.forwardingTargetForSelector:(SEL)aSelector 處理不了 到這里
-(NSString *)implementOnlyDeclare3:(NSString *) first second:(NSString *)second
{
return [NSString stringWithFormat:@"在methodSignatureForSelector中實現(xiàn)方法:%@+%@",first,second];
}
//3.在forwardingTargetForSelector方法返回nil的情況下,會轉(zhuǎn)發(fā)到這里來。通過這里我們可以做進一步處理,這里要返回一個方法的簽名,
//如果簽名方法根據(jù)方法確實能夠得到方法簽名,也就是方法存在,在所選擇的類存在,就會進入4,如果方法簽名無效就會進入
//doesNotRecognizeSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector==@selector(onlyDeclareMethod3:andWithSec:)) {
//這里不一定要return 本類的,其他類的方法一樣可以。
#pragma mark 返回這里就崩潰了,目測是循環(huán)調(diào)用本方法導(dǎo)致的
//return [self methodSignatureForSelector:@selector(implementOnlyDeclare3:second:)];
#pragma mark 根據(jù)類型返回一個簽名沒有錯誤,會進入 - (void)forwardInvocation:(NSInvocation *)anInvocation 處理
//return [NSMethodSignature signatureWithObjCTypes:"@@:@@"]; //這里返回的是函數(shù)類型和原來函數(shù)一致的
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//也可以返回和原來函數(shù)不一致的類型
}
return [super methodSignatureForSelector:aSelector];
}
//4.在第三步返回的方法簽名有效的情況下,就會進入這里
- (void)forwardInvocation:(NSInvocation *)anInvocation //NSInvocation用來包裝方法調(diào)用的對象,見下面的案例。
{
//在methodSignatureForSelector 中 return [NSMethodSignature signatureWithObjCTypes:"@@:@@"];上面返回的類型要和下面的
//一致,下面的三種任意一種操作都是可以的
/*1. 這樣寫可以,表示執(zhí)行本類的其他方法
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(implementOnlyDeclare3:second:)]; //在這里實現(xiàn)。
[anInvocation invoke];
*/
//2.這里也可以轉(zhuǎn)發(fā)給Pen類去實現(xiàn)調(diào)用的方法
/*Pen * pen = [[Pen alloc]init];
[anInvocation setTarget:pen];
[anInvocation invoke];
*/
///3.在methodSignatureForSelector 中 return [NSMethodSignature signatureWithObjCTypes:"v@:"];上面返回的類型要和下面的
//一致
[anInvocation setTarget:[[Pen alloc]init]];
[anInvocation setSelector:@selector(executeDefalut)];
[anInvocation invoke];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"在這里系統(tǒng)表示不識別這個方法,%@",NSStringFromSelector(aSelector));
}
/**
* 這個例子實現(xiàn)了系統(tǒng)performSelector withObject的方法的擴展,也展現(xiàn)了NSInvocation的使用
*
* @param selector 消息選擇器
* @param objects 參數(shù)數(shù)組
*
* @return 返回值
*/
- (id)performSelector:(SEL)selector withObjects:(NSArray *)objects
{
// 方法簽名(方法的描述),方法簽名和方法一樣對應(yīng)
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
if (signature == nil) {
//可以拋出異常也可以不操作
return nil;
}
// NSInvocation : 利用一個NSInvocation對象包裝一次方法調(diào)用(方法調(diào)用者、方法名、方法參數(shù)、方法返回值)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// 設(shè)置參數(shù)
NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的參數(shù)個數(shù),獲取當(dāng)前的方法真正的參數(shù)個數(shù)
paramsCount = MIN(paramsCount, objects.count);//兩者之間取小值,不然會參數(shù)個數(shù)不一樣崩潰
for (NSInteger i = 0; i < paramsCount; i++) {
id object = objects[i];
if ([object isKindOfClass:[NSNull class]])
continue;
[invocation setArgument:&object atIndex:i + 2];//用invocation對象設(shè)置參數(shù)的位置
}
// 調(diào)用方法
[invocation invoke];//調(diào)用
// 獲取返回值
id returnValue = nil;
if (signature.methodReturnLength) { // 有返回值類型,才去獲得返回值
[invocation getReturnValue:&returnValue];
}
//原本以為調(diào)用和獲取返回值是一體的 類似于 returnValue = [invocation invoke],其實是上面的
return returnValue;
}
@end
Pen類主要的所有是驗證運行時的消息轉(zhuǎn)發(fā),其他類替代自己處理事件,動態(tài)添加屬性。
@interface Pen : NSObject
-(NSString *)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString*)second;
-(NSString*)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString *)second;
//替代BOOK實現(xiàn)的方法和這個方法參數(shù)不一致
-(NSString *)executeDefalut;
@end
@implementation Pen
-(NSString*)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString *)second
{
return @"這是Pen類替代Book類實現(xiàn)了未實現(xiàn)的方法";
}
-(NSString*)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString *)second
{
return @"這是在forwardInvocation方法中實現(xiàn)由Pen對象替代BOOK對象執(zhí)行未定義方法";
}
-(NSString*)executeDefalut
{
return @"Pen代替Book實現(xiàn)了參數(shù)類型不一致的方法";
}
@end
@interface Pen (PenCate)
@property (nonatomic,strong) NSString * catePen;//類擴展屬性
@end
static const char * catePen_Key ="cate_nameKey";
@implementation Pen (PenCate)
- (void)setCatePen:(NSString *)catePen
{
//這個函數(shù):Sets an associated value for a given object using a given key and association policy.
//把一個值和一個對象用一個key和關(guān)聯(lián)規(guī)則關(guān)聯(lián)起來。第一個參數(shù)關(guān)聯(lián)對象,第二個是key,第三個是關(guān)聯(lián)的值,第四個關(guān)聯(lián)規(guī)則,類似于
//nonatomic,assin,copy,retain
objc_setAssociatedObject(self, catePen_Key, catePen, OBJC_ASSOCIATION_RETAIN);
}
-(NSString*)catePen
{
NSString * cateName= objc_getAssociatedObject(self, catePen_Key);//通過一個鍵值和對象取得對應(yīng)的值。
return cateName;
}
@end