一.Runtime原理
1.Runtime簡稱運(yùn)行時(shí).OC就是運(yùn)行時(shí)機(jī)制,(就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制)其中最主要的是消息機(jī)制.對(duì)于C語言摘投,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù).對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來調(diào)用.
2.它是一個(gè)主要使用C和匯編寫的庫,為C添加了面相對(duì)象的能力并創(chuàng)造了Objective-C.這就是說它在類信息(Class information)中被加載逞力,完成所有的方法分發(fā),方法轉(zhuǎn)發(fā),等等.Objective-C runtime 創(chuàng)建了所有需要的結(jié)構(gòu)體,讓Objective-C 的面相對(duì)象編程變?yōu)榭赡?
3.是一套比較底層的純C語言API,屬于1個(gè)C語言庫, 包含了很多底層的C語言API.在我們平時(shí)編寫的OC代碼中,程序運(yùn)行過程時(shí),其實(shí)最終都是轉(zhuǎn)成了runtime的C語言代碼,runtime算是OC的幕后工作者.
4.因?yàn)镺bjc是一門動(dòng)態(tài)語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí).也就是說只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(runtime system)來執(zhí)行編譯后的代碼.這就是 Objective-C Runtime 系統(tǒng)存在的意義,它是整個(gè)Objc運(yùn)行框架的一塊基石.
5.Mac和iPhone開發(fā)者關(guān)心的有兩個(gè)runtime:Modern Runtime(現(xiàn)代的 Runtime)和 Legacy Runtime(過時(shí)的Runtime).Modern Runtime:覆蓋所有 64 位的 Mac OS X 應(yīng)用和所有 iPhone OS 的應(yīng)用.Legacy Runtime:覆蓋其他的所有應(yīng)用(所有32位的 Mac OS X 應(yīng)用)Method有2種基本類型的方法.Instance Method(實(shí)例方法).Class Method(類方法)
二.Runtime相關(guān)的頭文件
sr/include/objc中的頭文件
三.Runtime使用
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
@private
float _height;
}
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
#import <objc/runtime.h>
const char* propertiesKey = "propertiesKey";
@implementation Person
/**
應(yīng)用2:NSCoding歸檔和解檔
獲取屬性\成員列表另外一個(gè)重要的應(yīng)用就是進(jìn)行歸檔和解檔凭语,其原理和上面的kvc基本上一樣涧黄,這里只是展示一些代碼:
*/
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(self.class, &count);
for (int i = 0; i < count; i++) {
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
id value = [self valueForKey:key]; // 取出key對(duì)應(yīng)的value
[aCoder encodeObject:value forKey:key]; // 編碼
}
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(self.class, &count);
for (int i = 0; i < count; i++) {
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
id value = [aDecoder decodeObjectForKey:key]; // 解碼
[self setValue:value forKey:key]; // 設(shè)置key對(duì)應(yīng)的value
}
}
return self;
}
/**
3_3.類\對(duì)象的關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象不是為類\對(duì)象添加屬性或者成員變量(因?yàn)樵谠O(shè)置關(guān)聯(lián)后也無法通過ivarList或者propertyList取得) 抄肖,而是為類添加一個(gè)相關(guān)的對(duì)象斯棒,通常用于存儲(chǔ)類信息没龙,例如存儲(chǔ)類的屬性列表數(shù)組链韭,為將來字典轉(zhuǎn)模型的方便排霉。 例如窍株,將屬性的名稱存到數(shù)組中設(shè)置關(guān)聯(lián)
*/
-(void)ws_relevanceObjAction{
const char *propertiesKey = "propertiesKey";
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count; i++) {
// Ivar pty = ivars[I];
// printf("ivar===%p\n",pty);
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1]; // 去掉_
[arrayM addObject:key];
}
free(ivars);
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", arrayM);//(age,height,name)
//objc_setAssociatedObject方法的參數(shù)解釋:
//第一個(gè)參數(shù)id object, 當(dāng)前對(duì)象
//第二個(gè)參數(shù)const void *key, 關(guān)聯(lián)的key,可以是任意類型
//第三個(gè)參數(shù)id value, 被關(guān)聯(lián)的對(duì)象
//第四個(gè)參數(shù)objc_AssociationPolicy policy關(guān)聯(lián)引用的規(guī)則,取值有以下幾種:
// enum {
// OBJC_ASSOCIATION_ASSIGN = 0,
// OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// OBJC_ASSOCIATION_RETAIN = 01401,
// OBJC_ASSOCIATION_COPY = 01403
// };
//如果想要獲取已經(jīng)關(guān)聯(lián)的對(duì)象球订,通過key取得即可
// NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
}
#pragma mark 封裝可以將以上兩種操作封裝起來,為Person類增加一個(gè)properties類方法,封裝上面的操作后裸,用于方便獲取類的屬性列表。
+ (NSArray *)properties {
// 如果已經(jīng)關(guān)聯(lián)了冒滩,就依據(jù)key取出被關(guān)聯(lián)的對(duì)象并返回
NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
if (pList != nil) {
return pList;
}
// 如果沒有關(guān)聯(lián)微驶,則設(shè)置關(guān)聯(lián)對(duì)象,并將對(duì)象返回
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; ++i) {
// Ivar pty = ivars[I];
const char *cname = ivar_getName(ivars[I]);
NSString *name = [NSString stringWithUTF8String:cname];
NSString *key = [name substringFromIndex:1];
[arrayM addObject:key];
}
free(ivars);
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return arrayM.copy;
}
/**
resolve [ri'z?lv] vt. 決定开睡;溶解因苹;使……分解;決心要做……篇恒;[主化]解析 vi. 解決扶檐;決心;分解 n. 堅(jiān)決胁艰;決定要做的事
3_4.動(dòng)態(tài)添加方法款筑,攔截未實(shí)現(xiàn)的方法 移步Person
每個(gè)類都有一下兩個(gè)類方法(來自NSObject)
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
以上兩個(gè)一個(gè)使用于類方法,一個(gè)適用于對(duì)象方法腾么。在代碼中調(diào)用沒有實(shí)現(xiàn)的方法時(shí)奈梳,也就是sel標(biāo)識(shí)的方法沒有實(shí)現(xiàn) 都會(huì)現(xiàn)調(diào)用這兩個(gè)方法中的一個(gè)(如果是類方法就調(diào)用第一個(gè),如果是對(duì)象方法就調(diào)用第二個(gè))攔截哮翘。 通常的做法是在resolve的內(nèi)部指定sel對(duì)應(yīng)的IMP颈嚼,從而完成方法的動(dòng)態(tài)創(chuàng)建和調(diào)用兩個(gè)過程,也可以不指定IMP打印錯(cuò)誤信息后直接返回饭寺。
假如在Person類中沒有-sayHi這個(gè)方法阻课,如果對(duì)象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就會(huì)必須經(jīng)過Person類的resolveInstanceMethod:(SEL)sel方法,在這里為-sayHi指定實(shí)現(xiàn)艰匙。
*/
void abc(id self, SEL _cmd){
NSLog(@"%@說了hello", [self name]);
}
//動(dòng)態(tài)添加方法:在resolve中添加相應(yīng)的方法限煞,注意是類方法還是對(duì)象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {
class_addMethod(self,sel,abc,"v@:"); //為sel指定實(shí)現(xiàn)為abc
// [self performSelector:@selector(ws_testAction)];
// [[[Person alloc] init] performSelector:@selector(ws_testAction)];
}
return YES;
}
-(void)ws_testAction{
NSLog(@"ws_testAction");
}
-(NSString *)description{
return [NSString stringWithFormat:@"{ name=%@, age=%d, height=%f }",self.name,self.age,self->_height];
}
@end
先導(dǎo)入頭文件
#import "RuntimeViewController.h"
#import "Person+Runtime.h"
#import "Person.h"
#import <objc/message.h>//包含消息機(jī)制
#import <objc/runtime.h>//包含對(duì)類员凝、成員變量署驻、屬性、方法的操作
@interface RuntimeViewController ()
@end
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:@"runTime"];//動(dòng)態(tài)添加屬性,修改屬性值(類別"Person+Runtime.h")
[self exchangeAttribute];//
// [self performSelector:@selector(likePlay)];
objc_msgSend(self,@selector(likePlay));// 動(dòng)態(tài)調(diào)用方法(在 LLVM 6.0 中增加了一個(gè) OBJC_OLD_DISPATCH_PROTOTYPES健霹,默認(rèn)配置在 Apple LLVM 6.0 - Preprocessing 中的 Enable Strict Checking of objc_msgSend Calls 中為Yes 改成NO;)
[self getAttribute];// 利用runtime遍歷一個(gè)類的全部成員變量
[self controlVariables];// 動(dòng)態(tài)控制變量
[self addMethod];//動(dòng)態(tài)添加方法
[self exchangeMethod];//動(dòng)態(tài)交換方法實(shí)現(xiàn)
}
1.動(dòng)態(tài)為Category擴(kuò)展加屬性
Person+Runtime.h
#import "Person.h"
@interface Person (Runtime)
@property(nonatomic,copy)NSString *height;// 身高
-(void)setHeight:(NSString *)height;
-(NSString *)height;
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2;
@end
Person+Runtime.m
#import "Person+Runtime.h"
#import <objc/message.h>//包含消息機(jī)制
#import <objc/runtime.h>//包含對(duì)類旺上、成員變量、屬性糖埋、方法的操作
@implementation Person (Runtime)
static char * heightKey = "heightKey";
-(void)setHeight:(NSString *)height{
objc_setAssociatedObject(self, heightKey, height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)height{
return objc_getAssociatedObject(self, heightKey);
}
-(NSString *)addStr1:(NSString *)str1 str2:(NSString *)str2{
return [str1 stringByAppendingString:str2];
}
@end
方法實(shí)現(xiàn)
-(void)exchangeAttribute{
Person *p = [[Person alloc]init];
p.height = @"178";
NSLog(@"身高==%@",p.height);
}
打印結(jié)果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 身高==178
2.動(dòng)態(tài)控制變量
-(void)controlVariables{
Person * p = [Person new];
p.name = @"wym";
NSLog(@"%@",[p name]);
unsigned int count;
//Ivar表示類中的實(shí)例變量宣吱。Ivar其實(shí)就是一個(gè)指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)瞳别、變量類型(ivar_type)等信息征候。
Ivar *ivar = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i ++) {
Ivar var = ivar[I];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithCString:varName encoding:NSUTF8StringEncoding];
if ([name isEqualToString:@"_name"]) {
object_setIvar(p, var, @"ws");
break;
}
}
NSLog(@"%@",p.name);
}
打印結(jié)果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] wym
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ws
3.利用runtime動(dòng)態(tài)遍歷一個(gè)類的全部成員變量
-(void)getAttribute{
//1.導(dǎo)入頭文件 <objc/runtime.h>
unsigned int count = 0;
//獲取指向該類所有屬性的指針
objc_property_t *propeprties = class_copyPropertyList([Person class], &count);
for (int i = 0; i < count; i++) {
//獲得
objc_property_t property = propeprties[I];
//根據(jù)objc_property_t 獲取所有屬性的名稱--->C語言的字符串
const char *name = property_getName(property);
NSString *attributeName = [NSString stringWithUTF8String:name];
NSLog(@"%d-----%@",i,attributeName);
}
}
打印結(jié)果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 0-----height
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 1-----name
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 2-----gender
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 3-----age
4.動(dòng)態(tài)添加方法
void goHome(id self, SEL _cmd){
NSLog(@"回家");
}
- (void)addMethod
{
Person *p = [Person new];
p.name = @"ET";
class_addMethod([Person class], @selector(shise), (IMP)goHome, "v@:");
[p performSelector:@selector(shise) withObject:nil];
}
打印結(jié)果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 回家
5.動(dòng)態(tài)交換方法實(shí)現(xiàn)
-(void)exchangeMethod{
Person *p = [[Person alloc]init];
p.name = @"ET";
[p eat];
[p play];
// 實(shí)現(xiàn)方法交換
Method m1 = class_getInstanceMethod([Person class], @selector(eat));
Method m2 = class_getInstanceMethod([Person class], @selector(play));
method_exchangeImplementations(m1, m2);
[p eat];
[p play];
}
打印結(jié)果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ET玩
2016-10-24 10:45:09.005 WsBlog[11181:4616582] ET吃飯
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET吃飯
2016-10-24 10:45:09.008 WsBlog[11181:4616582] ET玩
6.動(dòng)態(tài)調(diào)取方法
-(void)likePlay{
NSLog(@"喜歡玩");
}
打印結(jié)果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 喜歡玩