何為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理解更為深刻。