談談Runtime

何為runmtime,字面意思是運行時刻,是指一個程序在運行或者在被執(zhí)行的狀態(tài)。也就是說,當你打開一個程序使它在電腦上運行的時候,那個程序就是處于運行時刻。

runtime實質是一套底層的 C 語言 API,Objective-C是一門動態(tài)語言,runtime在其中扮演至關重要的角色,程序運行時Objective-C的代碼會被轉化成runtime的形式執(zhí)行。

舉個例子:
[receiver message]; 底層運行時會被編譯器轉化為: objc_msgSend(receiver, @selector(message))

作用:

能獲得某個類的所有成員變量
能獲得某個類的所有屬性
能獲得某個類的所有方法
能獲得某一個類的遵循的所有協議
能動態(tài)添加一個成員變量或屬性
能動態(tài)添加一個方法
能動態(tài)添加一個協議
能交換方法
能訪問私有變量并賦值
能注冊一個協議
...
等等

1.獲取成員變量

ivar是一個成員變量

相關函數
獲取所有成員變量:class_copyIvarList
獲取成員變量名:ivar_getName
獲取成員變量類型編碼:ivar_getTypeEncoding
獲取指定名稱的成員變量 :class_getInstanceVariable
設置指定名稱的成員變量 : object_setInstanceVariable,在ARC下不可用
獲取某個對象成員變量的值: object_getIvar
設置某個對象成員變量的值:object_setIvar

例子:

創(chuàng)建一個繼承于Person的Student類

//.h文件
#import "Person.h"

@interface Student : Person{
    NSString * cardId;
}
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *myName;
@end

//.m文件
#import "Student.h"
@interface Student ()
{
    NSArray *classmates;
}
@end
@implementation Student

@end

來看看Student的成員變量

 unsigned int outCount = 0;
 Ivar *ivars = class_copyIvarList([Student class], &outCount);
 for (int i = 0; i < outCount; i ++) {
    // 取出i位置對應的成員變量
    // Ivar ivar = *(ivars+i);
    Ivar ivar = ivars[i];
    // 獲得成員變量的名字
    const char * name = ivar_getName(ivar);
    // 獲取成員變量類型編碼
    const char * type = ivar_getTypeEncoding(ivar);  
    NSLog(@"Student擁有的成員變量的類型為%s,名字為 %s ",type, name);
  }
  free(ivars);
//如果函數名中包含了copy\new\retain\create等字眼,那么這個函數返回的數據就需要手動釋放

打印:

Student擁有的成員變量的類型為@"NSString",名字為 cardId
Student擁有的成員變量的類型為@"NSArray",名字為 classmates
Student擁有的成員變量的類型為q,名字為 _age
Student擁有的成員變量的類型為@"NSString",名字為 _myName

獲取指定名稱的成員變量

Ivar ivar = class_getInstanceVariable([Student class], "_myName");
const char * name = ivar_getName(ivar);
const char * type = ivar_getTypeEncoding(ivar);
    
NSLog(@"Student擁有的成員變量的類型為%s,名字為 %s ",type, name);

打印:

Student擁有的成員變量的類型為@"NSString",名字為 _myName

為指定成員變量賦值

Ivar ivar = class_getInstanceVariable([_student class], "_age");
object_setIvar(_student, ivar, @"13");
NSLog(@"%s %@",ivar_getName(ivar),object_getIvar(_student, ivar));

打印:_age 13

2. 獲取屬性

獲取所有屬性 class_copyPropertyList

注意:class_copyPropertyList 不僅可以獲取@property的屬性,也可以獲取類擴展上的@property屬性

獲取屬性名 property_getName
獲取屬性特性描述字符串 property_getAttributes

例子,還是拿上面那個Student類來說明:

unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Student class], &count);
for (int i = 0; i < count; i++) {
   objc_property_t property = propertyList[i];
   NSString *name = [NSString stringWithUTF8String:property_getName(property)];
   NSString *type = [NSString stringWithUTF8String:property_getAttributes(property)];
   NSLog(@“類型:%@====名字:%@“,type,name);
}
free(propertyList); //立即釋放propertyList指向的內存

打印:

類型:Tq,N,V_age====名字:age
類型:T@“NSString",&,N,V_myName====名字:myName

3.獲取方法

獲取實例方法的信息 class_getInstanceMethod
獲取類方法的信息 class_getClassMethod
獲取方法名 SEL method_getName ( Method m )

例子:

在student.h中添加一個方法

- (void)test:(int)a;
+ (void)test2:(int)a;
Method method1 = class_getInstanceMethod([Student class], @selector(test:));
const char * methodName1 = sel_getName(method_getName(method1));
unsigned int num1  = method_getNumberOfArguments(method1);
NSLog(@“該方法為:%s,有%d個參數:”,methodName1,num1);

Method method2 = class_getClassMethod([Student class], @selector(test2:));
const char * methodName2 = sel_getName(method_getName(method2));
unsigned int num2  = method_getNumberOfArguments(method2);
NSLog(@“該方法為:%s,有%d個參數",methodName2,num2);

打印:

該方法為:test:,有3個參數
該方法為:test2:,有3個參數

疑問:估計參數數量多出來的2個是調用的對象和selector

獲取方法具體實現:

class_getMethodImplementation(Class cls, SEL name)
class_getMethodImplementation_stret(Class cls, SEL name)

例子:

IMP imp = class_getMethodImplementation([Student class], @selector(test2:));

取得IMP后,我們就獲得了執(zhí)行這個test2:方法代碼的入口點,通過取得IMP,我們可以跳過Runtime的消息傳遞機制,直接執(zhí)行IMP指向的函數實現

獲取方法列表:class_copyMethodList

例子:

unsigned int count;
Method *methodList = class_copyMethodList([Student class],&count);
for (int i = 0; i < count2; i++) {
   Method method = methodList[i];
   NSLog(@"%s",sel_getName(method_getName(method)));
        
 }
free(methodList);

打印:

test:
age
setAge: 
myName
setMyName:
.cxx_destruct

由此可知,除了類方法外,聲明的實例方法和屬性的set、get方法和.m中沒有聲明的實例方法都會獲取到,也可以獲取到當前類的分類中的方法

得到方法的返回值 method_copyReturnType
無返回值void 對應v,有返回值對應@
例子:

Method method = class_getInstanceMethod([Student class], @selector(test:));
char *returnType = method_copyReturnType(method);
NSLog(@"返回的類型是%s",returnType);

打印:返回的類型是v

方法描述 method_getDescription

例子:

Method method = class_getInstanceMethod([Student class], @selector(test:));
struct objc_method_description *description = method_getDescription(method);
NSLog(@“%@"NSStringFromSelector(description->name));

打印:test:

返回指定選擇器指定的方法的名稱 sel_getName
例子:

const char *selName = sel_getName(@selector(test:));
NSLog(@“%s",selName);

打印:test:

比較兩個方法選擇器是否相等 sel_isEqual(sel1, sel2)

4.獲取協議

獲取類遵循的協議的列表 class_copyProtocolList

例子:

Student 遵循了三個協議,Protocol1,Protocol2,Protocol3

@interface Student : Person<Protocol1,Protocol2,Protocol3>

unsigned int count;
Protocol * __unsafe_unretained *protocolList = class_copyProtocolList([_student class],&count);
for (int i = 0; i < count; i++) {
   Protocol *protocol = protocolList[i];
   NSLog(@"%s",protocol_getName(protocol));
}
free(protocolList);

打印:

Protocol1  
Protocol2
Protocol3

獲取協議信息 protocol_getName

例子:

const char *name = protocol_getName(protocol1);
Protocol *protocol  =  NSProtocolFromString([NSString stringWithUTF8String:“NewProtocol’])

創(chuàng)建一個新的協議實例 objc_allocateProtocol

例子

Protocol *protocol  =  objc_allocateProtocol(“NewProtocol’);

檢查該協議是否已經注冊 class_conformsToProtocol(class, protocol)

注冊協議 objc_registerProtocol(protocol);

比較兩個協議是否相等 protocol_isEqual(Protocol *proto, Protocol *other)

5.添加屬性、方法、協議

添加屬性 class_addProperty(__unsafe_unretained Class , const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

比如為Student類添加一個school屬性

objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "&", "N" }; 
objc_property_attribute_t backingivar  = { "V", "_" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
// objc_property_attribute_t attrs[] = { { "T", "@\"NSString\"" }, { "&", "N" }, { "V", “_" } };
BOOL add =  class_addProperty([_student class], “country”, attrs, 3)

注意:只能添加新的成員屬性,不包括當前類、分類以及父類的已有屬性

objc_property_attribute_t 是一個包含name和value的結構體
常見格式:T@“NSString",&,N,V_myName

屬性類型 name值:T value:變化
編碼類型 name值:C(copy) &(strong) W(weak)空(assign) 等 value:無
非/原子性 name值:空(atomic) N(Nonatomic) value:無
變量名稱 name值:V value:變化

添加方法 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

  • Class cls 給哪個類添加方法
  • SEL name 添加的方法
  • IMP imp 方法的實現
  • const char * types 方法類型

“v@:@“方法的簽名,表時有一個參數的無返回值的方法類型。
“@@:” 表示一個返回值的方法類型
v 表示void @ 表示一個類型

例子:
比如我們?yōu)镾tudent添加一個test2 方法

class_addMethod([Student class], NSSelectorFromString(@"test2"), nil, “@@:”);

此時再來看看student類的實例方法列表:

unsigned int count;
Method *methodList = class_copyMethodList([Student class],&count);
for (int i = 0; i < count2; i++) {
   Method method = methodList[i];
   NSLog(@"%s",sel_getName(method_getName(method)));
}
free(methodList);

打印:

test2
test:
age
setAge: 
 myName
setMyName:
.cxx_destruct

添加協議 class_addProtocol(class, protocol)

例子:
為Student添加一個NewProtocol協議

BOOL addSuccess =  class_addProtocol([Student class], @protocol(NewProtocol));
if (addSuccess) {
   unsigned int count5;
   Protocol * __unsafe_unretained *protocolList = class_copyProtocolList([_student class],&count5);
   for (int i = 0; i < count5; i++) {
       Protocol *protocol = protocolList[i];
       NSLog(@"%s",protocol_getName(protocol));
    }
    free(protocolList);
 }

打印:NewProtocol

6.關聯對象

獲取關聯對象 objc_getAssociatedObject(id object, const void *key)

  • id object 獲取誰的關聯對象
  • const void *key 根據這個唯一的key獲取關聯對象,這個跟添加關聯對象時的參數 key一致

添加關聯對象 objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

  • id object給誰設置關聯對象。
  • const void *key關聯對象唯一的key,獲取時會用到。
  • id value關聯對象。
  • objc_AssociationPolicy關聯策略,有以下幾種策略:
enum { 

OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.

};

拿一個為分類添加屬性的例子
例子:
為Student一個分類上添加一個name屬性

@interface Student (test)
@property (nonatomic, strong) NSString *name;
@end

@implementation Student (test)
//添加關聯對象 
-(void)setName:(NSString *)name{
 objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 
//獲取關聯對象 
-(NSString *)name{ 
 return objc_getAssociatedObject(self, _cmd)
}; 
@end
Student  *student = [[Student alloc]init];
student.name = @“李四”;
NSLog@(@“%@”, student.name);

這里面我們把@selector(name)的地址作為唯一的key,_cmd代表當前調用方法的地址。當然這個key可以是字符串等,注意的是這兩個key保持一致就可以了。
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC) 的意思就是 給當前這個類(self)添加一個 叫name的 關聯屬性,而且屬性的唯一Id叫 @selector(property)

移除所有關聯 objc_removeAssociatedObjects(id object)

6、交換方法

method_exchangeImplementations(Method m1, Method m2)

例子:

+ (void)print1{
    NSLog(@"print 類方法");
}
- (void)print2{
    NSLog(@"print 實例方法");
}
Method  method1 = class_getClassMethod([self class], @selector(print1));
Method method2 = class_getInstanceMethod([self class], @selector(print2));
method_exchangeImplementations(method1, method2);
   
[self  print1];
NSLog(@"==========”);
[self  print2];

打印:

print 實例方法
==========
print 實例方法

7.其他

獲取類的名稱 const char *class_getName(Class cls)
獲取類的父類 Class class_getSuperclass(Class cls)

常見的應用場景

使用runtime去解析json來給Model賦值

例子:

創(chuàng)建一個Person類

Person.h

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;

+(instancetype)modelWithDict:(NSDictionary *)dict;
- (void)print;
@end
Person.m

#import “Person.h"
#import <objc/runtime.h>

@interface Person ()
@property (nonatomic, strong) NSString *country;
@end

@implementation Person

+ (instancetype)modelWithDict:(NSDictionary *)dict{
    Person *objc = [[Person alloc] init];
    NSMutableArray *keys = [NSMutableArray array];
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList(self, &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = propertyList[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        [keys addObject:name];
    }
    free(propertyList);
    
    for (NSString * key in keys) {
        if ([dict valueForKey:key] == nil) continue;
        [objc setValue:[dict valueForKey:key] forKey:key];
    }
    return objc;
}
- (void)print{
    NSLog(@"name:%@ country:%@",_name,_country);
}
@end
Person *person = [Person modelWithDict :@{@“name":@"tom",@"cardId":@"8888",@"age":@"18",@"country":@"China"}];
[person print];

打印:name:tom country:China

當然這個例子根據runtime獲取類的屬性列表賦值也有很多不足,當類的屬性有其他類或者字典等嵌套時,就需要再作其他邏輯判斷了

交換方法這個也會用到,設想這樣一個場景,當項目中大多地方多用了method1,忽然要改用method2,此時使用runtime交換方法,就不必一個一個在項目里改了,這樣就省下了很多功夫,當然runtime還有很多用途,可能實際項目也很少用到,但多了解底層原理和思想可以對Objective-C理解更為深刻。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,757評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,585評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,230評論 0 7
  • 文中的實驗代碼我放在了這個項目中。 以下內容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 939評論 0 6
  • 很少有app或網站是只采用一種推薦方式的,大部分都是采用混合的推薦機制,應用得比較多的是基于內容的推薦+協同過濾推...
    聰明的真真閱讀 2,395評論 2 6