首先導入頭文件
#import <objc/runtime.h>
markdown
經過runtime的一系列方法,能夠獲取類的一些信息, 包括:屬性列表,方法列表,成員變量列表,和遵循的協議列表。函數
有時候會有這樣的需求,咱們須要知道當前類中每一個屬性的名字。spa
unsigned int count;
// 獲取列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
// 獲取屬性名
const char *propertyName = property_getName(propertyList[i]);
// 打印
NSLog(@"property-->%@", [NSString stringWithUTF8String:propertyName]);
}
複製代碼
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method-->%@", NSStringFromSelector(method_getName(method)));
}
複製代碼
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
複製代碼
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
複製代碼
核心方法:class_addMethod ()
首先從外部隱式調用一個不存在
的方法:指針
// 隱式調用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];
複製代碼
而後,在target對象內部重寫攔截調用的方法,動態添加方法。code
// 重寫了攔截調用的方法,並返回YES
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//給本類動態添加一個方法
if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
// 調用新增的方法
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string); //withObject 參數
}
複製代碼
其中
class_addMethod
的四個參數分別是:orm
Class
: cls 給哪一個類添加方法,本例中是self SEL name
: 添加的方法,本例中是重寫的攔截調用傳進來的selector。 IMP imp
: 方法的實現,C方法的方法實現能夠直接得到。若是是OC方法,能夠用+ (IMP)instanceMethodForSelector:(SEL)aSelector;
得到方法的實現。 "v@:*"
:方法的簽名,表明有一個參數的方法。對象
如今你準備用一個系統的類,可是系統的類並不能知足你的需求,你須要額外添加一個屬性。通常解決辦法就是繼承。 可是,只增長一個屬性,就去繼承一個類,以爲太麻煩類。 這個時候,runtime的關聯屬性就發揮它的做用了。在Category中添加屬性。繼承
//首先定義一個全局變量,用它的地址做爲關聯對象的key
static char associatedObjectKey;
//設置關聯對象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //獲取關聯對象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);
複製代碼
objc_setAssociatedObject
的四個參數:ci
id
object給誰設置關聯對象。 const void *key
關聯對象惟一的key,獲取時會用到。 id value
關聯對象。 objc_AssociationPolicy
關聯策略字符串
在沒有一個類的實現源碼的狀況下,想改變其中一個方法的實現,除了繼承它重寫
、和藉助Category重名方法
以外,還有更加靈活的方法 Method Swizzle
。 在OC中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector的名字。 利用OC的動態特性,能夠實如今運行時偷換selector對應的方法實現。
Method Swizzle
指的是,改變一個已存在的選擇器對應的實現過程。OC中方法的調用可以在運行時,改變類的調度表中選擇器到最終函數間的映射關係。
每一個類都有一個方法列表,存放着selector的名字及其方法實現的映射關係。IMP有點相似函數指針,指向具體的方法實現。 利用 method_exchangeImplementations
來交換2個方法中的IMP。 利用 class_replaceMethod
來修改類。 利用 method_setImplementation
來直接設置某個方法的IMP。
歸根結底,都是偷換了selector
的IMP
新建分類
#import <objc/runtime.h>
+ (void)load {
// 方法交換應該被保證,在程序中只會執行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 得到須要替換的系統方法
SEL systemSel = @selector(didDisplay);
// 本身實現的將要被交換的方法
SEL swizzSel = @selector(myDidDisplay);
//兩個方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先動態添加方法,實現是被交換的方法,返回值表示添加成功仍是失敗
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//若是成功,說明類中不存在這個方法的實現
//將被交換方法的實現替換到這個並不存在的實現
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
} else {
//不然,交換兩個方法的實現
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
-(void)myDidDisplay{
//......do
}
複製代碼