使用運行時方法需要引入runtime.h文件。
一纹冤、基礎知識
runtime簡稱運行時洒宝,OC就是運行時機制,也就是在運行時候的一些機制萌京,其中最主要的是消息機制雁歌。
對于C語言,函數(shù)的調(diào)用是在編譯的時候會決定調(diào)用哪個函數(shù)知残。
對于OC的函數(shù)靠瞎,屬于動態(tài)調(diào)用過程,在編譯的時候并不能決定真正調(diào)用哪個函數(shù)求妹,只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用乏盐。
事實證明:
-- 在編譯階段,OC可以調(diào)用任何函數(shù)制恍,即使這個函數(shù)并未實現(xiàn)父能,只要聲明過就不會報錯。
-- 在編譯階段净神,C語言調(diào)用未實現(xiàn)的函數(shù)就會報錯何吝。
Method :成員方法
Ivar : 成員變量
二、常用方法
class_copyPropertyList : 獲取屬性列表
class_copyMethodList : 獲取成員方法列表
class_copyIvarList:獲取成員變量列表
ivar_getName:獲取變量名
property_getName:獲取屬性名
使用示例:
2.1.獲取成員變量list
unsigned int ivarCount = 0; //成員變量數(shù)
Ivar *ivarList = class_copyIvarList([self class], &ivarCount);//ivar數(shù)組
for (int i = 0; i < ivarCount; i++) {//遍歷
Ivar ivar = ivarList[i]; //獲取ivar
const char *name = ivar_getName(ivar);//獲取變量名
NSString *key = [NSString stringWithUTF8String:name];
NSLog(@"%@", key);
}
free(ivarList);
2.2.獲取屬性列表
unsigned int count = 0;
objc_property_t *propertList = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertList[i];
const char *name = property_getName(property);
const char *attrs = property_getAttributes(property);
// property_copyAttributeValue(,) 第一個參數(shù)為objc_property_t,第二個參數(shù)"V"獲取變量名,"T"獲取類型
const char *value = property_copyAttributeValue(property, "V");
NSLog(@"name = %s, attrs = %s, value = %s", name, attrs, value);
/*
各種符號對應類型鹃唯,部分類型在新版SDK中有所變化爱榕,如long 和long long
c char C unsigned char
i int I unsigned int
l long L unsigned long
s short S unsigned short
d double D unsigned double
f float F unsigned float
q long long Q unsigned long long
B BOOL
@ 對象類型 //指針 對象類型 如NSString 是@“NSString”
propertyType,你可以打印出來俯渤,看看它是什么呆细。
要判斷某個屬性的類型型宝,只需要[propertyType hasPrefix:@"T@\"NSString\""]
這代表它是NSString 類型八匠。
*/
}
free(propertList);
2.3.獲取方法列表
unsigned int count = 0;
Method *methodList = class_copyMethodList([self class], &count);
for (int i = 0 ; i < count; i++) {
Method method = methodList[i];
SEL selector = method_getName(method);//方法入口
const char *sel_name = sel_getName(selector);
NSLog(@"方法名 %s", sel_name);
}
free(methodList);
2.4.Runtime-動態(tài)創(chuàng)建類添加屬性和方法
- (void)createClass
{
Class MyClass = objc_allocateClassPair([NSObject class], "myclass", 0);
//添加一個NSString的變量,第四個參數(shù)是對其方式趴酣,第五個參數(shù)是參數(shù)類型
if (class_addIvar(MyClass, "itest", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
//myclasstest是已經(jīng)實現(xiàn)的函數(shù)梨树,"v@:"這種寫法見參數(shù)類型連接
class_addMethod(MyClass, @selector(myclasstest:), (IMP)myclasstest, "v@:");
//注冊這個類到runtime系統(tǒng)中就可以使用他了
objc_registerClassPair(MyClass);
//生成了一個實例化對象
id myobj = [[MyClass alloc] init];
NSString *str = @"asdb";
//給剛剛添加的變量賦值
// object_setInstanceVariable(myobj, "itest", (void *)&str);在ARC下不允許使用
[myobj setValue:str forKey:@"itest"];
//調(diào)用myclasstest方法,也就是給myobj這個接受者發(fā)送myclasstest這個消息
[myobj myclasstest:10];
}
//這個方法實際上沒有被調(diào)用,但是必須實現(xiàn)否則不會調(diào)用下面的方法
- (void)myclasstest:(int)a
{
}
//調(diào)用的是這個方法
static void myclasstest(id self, SEL _cmd, int a) //self和_cmd是必須的岖寞,在之后可以隨意添加其他參數(shù)
{
Ivar v = class_getInstanceVariable([self class], "itest");
//返回名為itest的ivar的變量的值
id o = object_getIvar(self, v);
//成功打印出結(jié)果
NSLog(@"%@", o);
NSLog(@"int a is %d", a);
}
詳解Objective-C的meta-class:http://www.reibang.com/p/a5ab8d998b9e
三抡四、使用方向:歸檔、字典<---->模型、框架封裝等
3.1. 實現(xiàn)歸檔
encode:編碼 decode:解碼
#define WKCodingImplementing
- (void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int ivarCount = 0;
Ivar *ivarList = class_copyIvarList([self class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivarList[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"%s-----%s", name, type);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivarList);
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
unsigned int ivarCount = 0;
Ivar *ivarList = class_copyIvarList([self class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivarList[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key]; \
NSLog(@"%@ %@", key, value);
[self setValue:value forKey:key];
}
}
return self;
}
3.2.如何快速生成Plist文件屬性名
實現(xiàn)原理:通過遍歷字典,判斷類型,拼接字符串
// 拼接屬性字符串代碼
NSMutableString *strM = [NSMutableString string];
// 1.遍歷字典指巡,把字典中的所有key取出來淑履,生成對應的屬性代碼
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSString *type;
if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
type = @"NSString";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
type = @"NSArray";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
type = @"int";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
type = @"NSDictionary";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
type = @"BooL";
}
// 屬性字符串
NSString *str;
if ([type containsString:@"NS"]) {
str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
}else{
str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
}
// 每生成屬性字符串,就自動換行藻雪。
[strM appendFormat:@"\n%@\n",str];
}];
// 把拼接好的字符串打印出來秘噪,就好了。
NSLog(@"%@",strM);
打印結(jié)果
3.3.KVC實現(xiàn)字典轉(zhuǎn)模型
KVC弊端
模型中屬性必須和字典的key一致,否則就報錯
如果不一致,系統(tǒng)會調(diào)用setValue: forUndefinedKey:
解決辦法,只需要重寫setValue: forUndefinedKey:即可
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
3.4. RunTime實現(xiàn)字典轉(zhuǎn)模型
實現(xiàn)思路:遍歷模型中所有屬性,根據(jù)模型的屬性名去字典中查找key,取出對應的的值,給模型的屬性賦值
3.4.1.一級轉(zhuǎn)換
class_copyIvarList(self, &count)該方法第一個參數(shù)是要獲取哪個類中的成員屬性,第二個參數(shù)是這個類中有多少成員屬性,需要傳入地址,返回值Ivar是個數(shù)組,會將所有成員屬性放入這個數(shù)組中
@implementation NSObject (Model)
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
// 思路:遍歷模型中所有屬性-》使用運行時
// 0.創(chuàng)建對應的對象
id objc = [[self alloc] init];
// 1.利用runtime給對象中的成員屬性賦值
// class_copyIvarList:獲取類中的所有成員屬性
// Ivar:成員屬性的意思
// 第一個參數(shù):表示獲取哪個類中的成員屬性
// 第二個參數(shù):表示這個類有多少成員屬性勉耀,傳入一個Int變量地址指煎,會自動給這個變量賦值
// 返回值Ivar *:指的是一個ivar數(shù)組,會把所有成員屬性放在一個數(shù)組中便斥,通過返回的數(shù)組就能全部獲取到至壤。
/* 類似下面這種寫法
Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定義一個ivar的數(shù)組a
Ivar a[] = {ivar,ivar1,ivar2};
// 用一個Ivar *指針指向數(shù)組第一個元素
Ivar *ivarList = a;
// 根據(jù)指針訪問數(shù)組第一個元素
ivarList[0];
*/
unsigned int count;
// 獲取類中的所有成員屬性
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
// 獲取成員屬性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 處理成員屬性名->字典中的key
// (去掉 _ ,從第一個角標開始截取)
NSString *key = [name substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應的value
id value = dict[key];
3.4.2.二級轉(zhuǎn)換
判斷字典中是否存在字典,如果存在,轉(zhuǎn)為模型
字典屬性生成的是@"@"xxxx""類型,需要裁減為@"xxxx"
if ([value isKindOfClass:[NSDictionary class]]) {
// 獲取成員屬性類型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是這種@"@\"User\"" 類型 -》 @"User" 在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思枢纠,不占用字符
type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
NSLog(@"type-----------%@",type);
type = [type stringByReplacingOccurrencesOfString:@"@" withString:@""];
NSLog(@"type-----------%@",type);
// 根據(jù)字符串類名生成類對象
Class modelClass = NSClassFromString(type);
if (modelClass) {
value = [modelClass modelWithDict:value];
}
}
3.4.3.三級轉(zhuǎn)換
通過給分類添加一個協(xié)議,來實現(xiàn)將數(shù)組中的字典轉(zhuǎn)為模型
// 三級轉(zhuǎn)換:NSArray中也是字典像街,把數(shù)組中的字典轉(zhuǎn)換成模型.
// 判斷值是否是數(shù)組
if ([value isKindOfClass:[NSArray class]]) {
// 判斷對應類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 轉(zhuǎn)換成id類型,就能調(diào)用任何對象的方法
id idSelf = self;
// 獲取數(shù)組中字典對應的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組晋渺,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel modelWithDict:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
if (value) { // 有值宅广,才需要給模型的屬性賦值
// 利用KVC給模型中的屬性賦值
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
3.5. 使用runtime實現(xiàn)方法的交換
3.5.1 前言
在開發(fā)過程中,我們常常會遇到一種問題: 即當我們使用系統(tǒng)自帶的方法時,發(fā)現(xiàn)系統(tǒng)方法的功能不能滿足我們的需求,這時候需要我們給系統(tǒng)的方法添加額外的功能.
下面以一個例子來實現(xiàn)給系統(tǒng)自帶的方法添加額外的功能.
項目背景 : 公司有個開發(fā)了很久的項目,之前使用添加圖片的方法用的是:imageName:方法, 現(xiàn)在的項目需求是給系統(tǒng)自帶的imageName:添加一個功能:如果下載的圖片不存在,那么就提示我們當前下載的圖片為空.
思路 : 給系統(tǒng)自帶的方法添加功能的幾種方法
- 自定義類, 重寫系統(tǒng)自帶的imageName:方法,這種方法雖然可以實現(xiàn),但是它的弊端就是必須要使用自己的類,依賴性強.
- 給UIImage添加一個分類, 改變系統(tǒng)類的實現(xiàn),給系統(tǒng)的類添加方法的時候調(diào)用(強烈不推薦)
- 使用runtime的交互方法,給系統(tǒng)的方法添加功能. 具體實現(xiàn) : 添加一個分類 --> 在分類中提供一個需要添加的功能的方法 --> 將這個方法的實現(xiàn)和系統(tǒng)自帶的方法的實現(xiàn)交互.
3.5.2.步驟
3.5.2.1. 新建一個繼承自UIImage的分類,定義一個方法實現(xiàn)給系統(tǒng)的自帶的方法添加功能.
#import <UIKit/UIKit.h>
@interface UIImage (WGImage)
// 聲明方法
// 如果跟系統(tǒng)方法差不多功能,可以采取添加前綴,與系統(tǒng)方法區(qū)分
+ (UIImage *)wg_imageWithName:(NSString *)imageName;
@end
3.5.2.2. 實現(xiàn)方法的交互
定義完畢新方法后,需要弄清楚什么時候?qū)崿F(xiàn)與系統(tǒng)的方法交互?
答 : 既然是給系統(tǒng)的方法添加額外的功能,換句話說,我們以后在開發(fā)中都是使用自己定義的方法,取代系統(tǒng)的方法,所以,當程序一啟動,就要求能使用自己定義的功能方法.說道這里:我們必須要弄明白一下兩個方法 :
+(void)initialize(當類第一次被調(diào)用的時候就會調(diào)用該方法,整個程序運行中只會調(diào)用一次)
- (void)load(當程序啟動的時候就會調(diào)用該方法,換句話說,只要程序一啟動就會調(diào)用load方法,整個程序運行中只會調(diào)用一次)
+ (void)load {
/*
self:UIImage
誰的事情,誰開頭 1.發(fā)送消息(對象:objc) 2.注冊方法(方法編號:sel) 3.交互方法(方法:method) 4.獲取方法(類:class)
Method:方法名
獲取方法,方法保存到類
Class:獲取哪個類方法
SEL:獲取哪個方法
imageName
*/
// 獲取imageName:方法的地址
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
// 獲取wg_imageWithName:方法的地址
Method wg_imageWithNameMethod = class_getClassMethod(self, @selector(wg_imageWithName:));
// 交換方法地址,相當于交換實現(xiàn)方式
method_exchangeImplementations(imageNameMethod, wg_imageWithNameMethod);
}
3.5.2.3. 加載圖片, 判斷當前圖片是否為空
// 加載圖片, 判斷是否為空
+ (UIImage *)wg_imageWithName:(NSString *)imageName
{
// 這里調(diào)用imageWithName些举,相當于調(diào)用imageName
UIImage *image = [UIImage wg_imageWithName:imageName];
if (!image) {
NSLog(@"Alex : 圖片不存在");
}
return image;
}
3.5.2.4. 使用交互后的方法
#import "ViewController.h"
#import "WGStudent.h"
#import "UIImage+WGImage.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// wg_imageWithName ---> imageNamed(底層的操作 : 1, 下載圖片. 2, 判斷圖片是否存在 )
[UIImage imageNamed:@"WilliamAlex.png"];
}
@end
3.5.2.5. 打印結(jié)果
2016-03-08 17:29:50.372 fdsfsdf[1545:96854] Alex : 圖片不存在
知識拓展
不能在分類中重寫系統(tǒng)方法imageNamed跟狱,因為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.
runtime的那點事兒(一)消息機制
http://blog.csdn.net/jq2530469200/article/details/51880836
runtime的那點事兒(二)消息機制
http://blog.csdn.net/jq2530469200/article/details/51886532
runtime的那點事兒(三)消息機制
http://blog.csdn.net/jq2530469200/article/details/51886578
runtime簡單使用之方法的交換:
http://www.reibang.com/p/d28abb706add
使用RunTime實現(xiàn)字典轉(zhuǎn)模型:
http://www.reibang.com/p/cecfe78e9cd8