Runtime 理解介紹的文章很是多,我只想講講Runtime 能夠用在哪裏,而我在項目裏哪些地方用到了runtime。多以實際使用過程爲主,來介紹runtime的使用。git
那麼runtime 怎麼使用?能夠用在哪些場景下呢?github
首先,使用runtime 相關API,要#import <objc/runtime.h>
objective-c
運行時動態獲取某個類的屬性或者函數等,能夠用來作不少事情,如json 解析、數據庫結果解析、判斷某個類的子類等。數據庫
// 獲取屬性列表
objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
// 獲取屬性名
const char *property_getName(objc_property_t property)
// 獲取屬性類型
const char *property_getAttributes(objc_property_t property)
複製代碼
以上方法能夠用來:json
這裏有動態獲取類的屬性的示例代碼片斷:數組
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
//獲取屬性名
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
//獲取屬性類型等參數
NSString *propertyType = [NSString stringWithCString: property_getAttributes(property) encoding:NSUTF8StringEncoding];
/*
各類符號對應類型,部分類型在新版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:@"Ti"]
這表明它是int 類型。
*/
}
free(properties);
複製代碼
有時候咱們在程序中須要判斷某個類是不是另外一個類的子類。這個功能也能夠利用runtime類實現,這裏有示例代碼:bash
int numClasses;
Class *classes = NULL;
numClasses = objc_getClassList(NULL,0);
if (numClasses >0 )
{
classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
for (int i = 0; i < numClasses; i++) {
if (class_getSuperclass(classes[i]) == [xxxxClass class]){
id class = classes[i];
// 執行某個方法 或者 作其餘事情
[class performSelector:@selector(xxxxMethod) withObject:nil];
}
}
free(classes);
}
複製代碼
以上兩段示例代碼摘自我以前寫的FMDB Model 封裝:JKDBModel,你能夠去看更詳盡的解析和使用過程。服務器
若是你還須要獲取某個類的實例變量作什麼操做的話,可使用以下這幾個API:app
// 獲取實例變量數組
Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
// 獲取實例變量名稱
const char * ivar_getName( Ivar ivar)
// 獲取實例變量類型
const char * ivar_getTypeEncoding( Ivar ivar)
複製代碼
這面有獲取實例變量的示例代碼片斷:函數
unsigned int outCount, i;
Ivar *ivaries = class_copyIvarList([Son class], &outCount);
for (i = 0; i < outCount; i++) {
Ivar ivar = ivaries[i];
NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
NSString *ivarType = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
NSLog(@"名稱:%@---類型:%@",ivarName,ivarType);
/*
各類符號對應類型,部分類型在新版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」
*/
}
free(ivaries);
複製代碼
獲取某個類的方法,會包含這個類的property 的set 和get 方法,可是不包括父類的property set 和get 方法,不包括父類的方法(若是在當前類覆寫,就包括)。
主要API:
// 獲取方法數組
Method * class_copyMethodList(Class cls, unsigned int *outCount)
// 獲取方法的 SEL
SEL method_getName( Method method)
// 獲取方法名
const char* sel_getName(SEL aSelector)
複製代碼
獲取方法數組的示例代碼片斷:
unsigned int outMethodCount, j;
Method *methods = class_copyMethodList([Son class], &outMethodCount);
for (j = 0; j < outMethodCount; j++) {
Method method = methods[j];
SEL selector = method_getName(method);
if (selector) {
NSString *methodName = [NSString stringWithCString:sel_getName(selector) encoding:NSUTF8StringEncoding];
NSLog(@"方法:%@",methodName);
}
}
free(methods);
複製代碼
Method Swizzling 的使用須要謹慎,由於一不當心可能就會致使沒法排查的Bug,畢竟它替換的是官方的API,有些API內部作了什麼事情,很難徹底把握。
使用場景,須要監控用戶常常打開的界面,以及在某界面停留的時長。
咱們能夠怎麼作呢?
寫一個UIViewController 的Category,而後在類別中,添加自定義的方法:如-xxxviewDidAppear:和-xxxviewDidDisappear:方法,而後在-load 方法中,用自定義的方法替換原來的方法。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"xxx_viewWillAppear: %@", self);
// 在這裏,咱們能夠發送一個消息到服務器,或者作其餘事情等。
}
複製代碼
以上示例代碼摘自:Objective-C Runtime 運行時之四:Method Swizzling
關於Method Swizzling,他是把兩個方法的實現部分互換了。
好比上面咱們調用-xxx_viewWillAppear:
,由於-xxx_viewWillAppear:
和-viewWillAppear:
的實現部分互換後,其實執行的時候,並不會執行上面的這個實現,而是調用-viewWillAppear:
的內部實現。因此上面的代碼,徹底不會產生循環調用。
仍是寫段代碼說明吧:
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"這是原來的方法");
}
- (void)xxx_viewWillAppear:(BOOL)animated {
NSLog(@"xxx_viewWillAppear: %@", self);
// 在這裏,咱們能夠發送一個消息到服務器,或者作其餘事情等。
}
複製代碼
假如上面這倆方法用method swizzling 替換後,咱們調用-xxx_viewWillAppear:
會打印這是原來的方法
;而調用-viewWillAppear:
會打印xxx_viewWillAppear:
。這裏須要細細體會一下。
關於Method Swizzling更多的注意點請看原文Method Swizzling
對象關聯(或稱爲關聯引用)原本是Objective-C 運行時的一個重要特性,它能讓開發者對已經存在的類在擴展中添加自定義的屬性。
須要用的如下三個函數:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, void *key)
void objc_removeAssociatedObjects(id object)
複製代碼
衆所周知,OC 中的Category 中不能添加新的屬性,可是咱們經過Associated Objects
能夠間接的實現往類上添加自定義的屬性。
不能添加屬性的根本緣由是不會幫咱們自動添加對象的實例變量,也不會幫咱們生成set 和get方法,雖然set /get 方法能夠本身實現,可是沒有實例變量來存儲數據。
很容易看懂官方文檔對參數的描述,可是key 須要注意一下: 一般推薦的作法是添加的屬性最好是 static char類型的,固然更推薦是指針型的。一般來講該屬性應該是常量、惟一的、在適用範圍內用getter和setter訪問到,因此一般咱們這樣寫:static char kAssociatedObjectKey;
objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(self, &kAssociatedObjectKey);
複製代碼
固然,對於key 還有更好的作法,那就是selector。用selector 的示例在下面。
下面用代碼演示如何在Category中添加一個新的屬性。
這是Son+AssociatedObject.h
#import "Son.h"
@interface Son (AssociatedObject)
/** 家庭住址 */
@property (copy, nonatomic) NSString *address;
/** 身高 */
@property (assign, nonatomic) int height;
@end
複製代碼
這是Son+AssociatedObject.m
#import "Son+AssociatedObject.h"
#import <objc/runtime.h>
@implementation Son (AssociatedObject)
- (void)setAddress:(NSString *)address
{
objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)address
{
return objc_getAssociatedObject(self, @selector(address));
}
- (void)setHeight:(int)height
{
NSNumber *heighNum = [NSNumber numberWithInt:height];
objc_setAssociatedObject(self, @selector(height), heighNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)height
{
NSNumber *heightNum = objc_getAssociatedObject(self, @selector(height));
return heightNum.intValue;
}
@end
複製代碼
雖然上面有提到void objc_removeAssociatedObjects(id object)
,可是不要輕易使用這個函數,由於它會移除全部的關聯對象。咱們通常要移除某個關聯對象,只須要用objc_setAssociatedObject
傳入nil便可。
補充一個關聯對象的使用場景:
你在使用AlertView 或者ActionSheet的時候,有沒有很苦惱不能在點擊的代理方法中方便的獲取到Model對象呢?
除了在控制器中添加一個property 這種方式外,咱們也能夠爲AlertView 或者ActionSheet 添加一個關聯對象,這樣就能夠在代理方法中方便的獲取到Model 對象啦。
這裏若是咱們爲AlertView 或者ActionSheet 添加Category來實現的話,代碼跟上面爲Son 添加類別基本同樣,對象類型改成id 類型便可。
或者咱們在控制器中調用的時候,添加關聯對象也能夠。這時候就用這種方式:
static char kAssociatedObjectKey;
objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &kAssociatedObjectKey);
複製代碼
UIAlertController 也跟上面同樣。
關於Associated Objects
的使用,有兩個爲Category擴展功能,使得Category中也能方便的添加屬性以及相應的getter 和setter 的例子。
我在某控制器中測試寫了這麼一個方法,來建立一個MyClass 類。項目中並不存在叫MyClass 的類文件。
- (void)createClass
{
Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
// 1.添加一個叫name 類型爲NSString的實例變量,第四個參數是對其方式,第五個參數是參數類型
if (class_addIvar(MyClass, "name", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
// 2.添加一個property
// 這裏須要注意,添加property以前須要先添加一個與之對應的實例變量
if (class_addIvar(MyClass, "_address", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_address"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(MyClass, "address", attrs,2);
// 3.添加函數, myclasstest是已經實現的函數,"v@:"這種寫法見參數類型鏈接
class_addMethod(MyClass, @selector(myclasstest:), (IMP)myclasstest, "v@:");
// 4.註冊這個類到runtime系統中就可使用他了
objc_registerClassPair(MyClass);
// 5.生成了一個實例化對象
id myobj = [[MyClass alloc] init];
NSString *str = @"名字";
// 6.給剛剛添加的變量賦值
// object_setInstanceVariable(myobj, "itest", (void *)&str);在ARC下不容許使用
[myobj setValue:str forKey:@"name"];
[myobj setValue:@"這是地址" forKey:@"address"];
// 7.調用myclasstest方法,也就是給myobj這個接受者發送myclasstest這個消息
[myobj myclasstest:10];
}
//這個方法實際上沒有被調用,可是必須實現不然不會調用下面的方法
- (void)myclasstest:(int)a
{
NSLog(@"啊啊啊啊啊");
}
//調用的是這個方法
static void myclasstest(id self, SEL _cmd, int a) //self和_cmd是必須的,在以後能夠添加其餘參數
{
Ivar v = class_getInstanceVariable([self class], "name");
//返回名爲name的ivar的變量的值
id o = object_getIvar(self, v);
//成功打印出結果
NSLog(@"name is %@", o);
NSLog(@"參數 a is %d", a);
objc_property_t property = class_getProperty([self class], "address");
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id value = [self valueForKey:propertyName];
NSLog(@"address is %@", value);
}
複製代碼
關於運行時建立一個新類,上面的註釋已經寫的很詳細了。
Have Fun!