一.Runtime原理
1.Runtime簡稱運行時.OC就是運行時機制,(就是系統在運行的時候的一些機制)其中最主要的是消息機制.對于C語言,函數的調用在編譯的時候會決定調用哪個函數.對于OC的函數,屬于動態調用過程,在編譯的時候并不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用.
2.它是一個主要使用C和匯編寫的庫,為C添加了面相對象的能力并創造了Objective-C.這就是說它在類信息(Class information)中被加載,完成所有的方法分發,方法轉發,等等.Objective-C runtime 創建了所有需要的結構體,讓Objective-C 的面相對象編程變為可能.
3.是一套比較底層的純C語言API,屬于1個C語言庫, 包含了很多底層的C語言API.在我們平時編寫的OC代碼中,程序運行過程時,其實最終都是轉成了runtime的C語言代碼,runtime算是OC的幕后工作者.
4.因為Objc是一門動態語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運行時.也就是說只有編譯器是不夠的,還需要一個運行時系統(runtime system)來執行編譯后的代碼.這就是 Objective-C Runtime 系統存在的意義,它是整個Objc運行框架的一塊基石.
5.Mac和iPhone開發者關心的有兩個runtime:Modern Runtime(現代的 Runtime)和 Legacy Runtime(過時的Runtime).Modern Runtime:覆蓋所有 64 位的 Mac OS X 應用和所有 iPhone OS 的應用.Legacy Runtime:覆蓋其他的所有應用(所有32位的 Mac OS X 應用)Method有2種基本類型的方法.Instance Method(實例方法).Class Method(類方法)
二.Runtime相關的頭文件
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
/**
應用2:NSCoding歸檔和解檔
獲取屬性\成員列表另外一個重要的應用就是進行歸檔和解檔,其原理和上面的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對應的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]; // 設置key對應的value
}
}
return self;
}
/**
3_3.類\對象的關聯對象
關聯對象不是為類\對象添加屬性或者成員變量(因為在設置關聯后也無法通過ivarList或者propertyList取得) ,而是為類添加一個相關的對象,通常用于存儲類信息,例如存儲類的屬性列表數組,為將來字典轉模型的方便。 例如,將屬性的名稱存到數組中設置關聯
*/
-(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方法的參數解釋:
//第一個參數id object, 當前對象
//第二個參數const void *key, 關聯的key,可以是任意類型
//第三個參數id value, 被關聯的對象
//第四個參數objc_AssociationPolicy policy關聯引用的規則,取值有以下幾種:
// enum {
// OBJC_ASSOCIATION_ASSIGN = 0,
// OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// OBJC_ASSOCIATION_RETAIN = 01401,
// OBJC_ASSOCIATION_COPY = 01403
// };
//如果想要獲取已經關聯的對象,通過key取得即可
// NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
}
#pragma mark 封裝可以將以上兩種操作封裝起來,為Person類增加一個properties類方法,封裝上面的操作,用于方便獲取類的屬性列表。
+ (NSArray *)properties {
// 如果已經關聯了,就依據key取出被關聯的對象并返回
NSArray *pList = objc_getAssociatedObject(self, propertiesKey);
if (pList != nil) {
return pList;
}
// 如果沒有關聯,則設置關聯對象,并將對象返回
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. 堅決;決定要做的事
3_4.動態添加方法,攔截未實現的方法 移步Person
每個類都有一下兩個類方法(來自NSObject)
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
以上兩個一個使用于類方法,一個適用于對象方法。在代碼中調用沒有實現的方法時,也就是sel標識的方法沒有實現 都會現調用這兩個方法中的一個(如果是類方法就調用第一個,如果是對象方法就調用第二個)攔截。 通常的做法是在resolve的內部指定sel對應的IMP,從而完成方法的動態創建和調用兩個過程,也可以不指定IMP打印錯誤信息后直接返回。
假如在Person類中沒有-sayHi這個方法,如果對象p使用[p performSelector:@selector(sayHi) withObject:nil];那么就會必須經過Person類的resolveInstanceMethod:(SEL)sel方法,在這里為-sayHi指定實現。
*/
void abc(id self, SEL _cmd){
NSLog(@"%@說了hello", [self name]);
}
//動態添加方法:在resolve中添加相應的方法,注意是類方法還是對象方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"sayHi"]) {
class_addMethod(self,sel,abc,"v@:"); //為sel指定實現為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
先導入頭文件
#import "RuntimeViewController.h"
#import "Person+Runtime.h"
#import "Person.h"
#import <objc/message.h>//包含消息機制
#import <objc/runtime.h>//包含對類、成員變量、屬性、方法的操作
@interface RuntimeViewController ()
@end
@implementation RuntimeViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:@"runTime"];//動態添加屬性,修改屬性值(類別"Person+Runtime.h")
[self exchangeAttribute];//
// [self performSelector:@selector(likePlay)];
objc_msgSend(self,@selector(likePlay));// 動態調用方法(在 LLVM 6.0 中增加了一個 OBJC_OLD_DISPATCH_PROTOTYPES,默認配置在 Apple LLVM 6.0 - Preprocessing 中的 Enable Strict Checking of objc_msgSend Calls 中為Yes 改成NO;)
[self getAttribute];// 利用runtime遍歷一個類的全部成員變量
[self controlVariables];// 動態控制變量
[self addMethod];//動態添加方法
[self exchangeMethod];//動態交換方法實現
}
1.動態為Category擴展加屬性
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>//包含消息機制
#import <objc/runtime.h>//包含對類、成員變量、屬性、方法的操作
@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
方法實現
-(void)exchangeAttribute{
Person *p = [[Person alloc]init];
p.height = @"178";
NSLog(@"身高==%@",p.height);
}
打印結果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 身高==178
2.動態控制變量
-(void)controlVariables{
Person * p = [Person new];
p.name = @"wym";
NSLog(@"%@",[p name]);
unsigned int count;
//Ivar表示類中的實例變量。Ivar其實就是一個指向objc_ivar結構體指針,它包含了變量名(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);
}
打印結果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] wym
2016-10-24 10:45:09.004 WsBlog[11181:4616582] ws
3.利用runtime動態遍歷一個類的全部成員變量
-(void)getAttribute{
//1.導入頭文件 <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];
//根據objc_property_t 獲取所有屬性的名稱--->C語言的字符串
const char *name = property_getName(property);
NSString *attributeName = [NSString stringWithUTF8String:name];
NSLog(@"%d-----%@",i,attributeName);
}
}
打印結果
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.動態添加方法
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];
}
打印結果
2016-10-24 10:45:09.004 WsBlog[11181:4616582] 回家
5.動態交換方法實現
-(void)exchangeMethod{
Person *p = [[Person alloc]init];
p.name = @"ET";
[p eat];
[p play];
// 實現方法交換
Method m1 = class_getInstanceMethod([Person class], @selector(eat));
Method m2 = class_getInstanceMethod([Person class], @selector(play));
method_exchangeImplementations(m1, m2);
[p eat];
[p play];
}
打印結果
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.動態調取方法
-(void)likePlay{
NSLog(@"喜歡玩");
}
打印結果
2016-10-24 10:45:09.003 WsBlog[11181:4616582] 喜歡玩