Runtime API01 - 類
- 獲取isa指向的Class(類對(duì)象):
Class object_getClass(id obj) - 設(shè)置isa指向的Class :
Class object_setClass(id obj, Class cls) - 判斷一個(gè)OC對(duì)象是否為Class:
Bool object_isClass(Class cls) - 判斷一個(gè)Class是否為元類:
Bool class_isMetaClass(Class cls) - 獲取父類:
Class class_getSuperclass(Class cls) - 動(dòng)態(tài)創(chuàng)建一個(gè)類(參數(shù):父類,類名,額外的內(nèi)存空間):
Class objc_allocateClassPair(Class superclass,const char *name,size_t extraBytes) - 注冊(cè)一個(gè)類(要在類注冊(cè)之前添加成員變量):
void objc_registerClassPair(Class cls) - 銷毀一個(gè)類:
void objc_disposeClassPair(Class cls)
Person.h文件
@interface Person : NSObject
- (void)run;
@end
Person.m文件
#import "Person.h"
@implementation Person
- (void)run{
NSLog(@"%s",__func__);
}
@end
Car.h文件
#import <Foundation/Foundation.h>
@interface Car : NSObject
- (void)run;
@end
Car.m文件
#import "Car.h"
@implementation Car
- (void)run{
NSLog(@"%s",__func__);
}
@end
main文件
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];//打印結(jié)果:-[Person run]
NSLog(@"%p %p",object_getClass(person),[Person class]);//打印分別為類對(duì)象地址,類對(duì)象地址:0x100008230 0x100008230
NSLog(@"%p %p",object_getClass([Person class]),[Person class]);//打印分別為元類對(duì)象地址,類對(duì)象地址:0x100008208 0x100008230
object_setClass(person, [Car class]);//設(shè)置person的isa指向Car
[person run];//打印結(jié)果為-[Car run]
NSLog(@"%d %d %d",object_isClass(person),object_isClass([Person class]),object_isClass(object_getClass([Person class])));//object_isClass是否為類對(duì)象,元類對(duì)象是特殊的類對(duì)象,所以打印結(jié)果為:0 1 1
}
return 0;
}
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
void run(id self,SEL _cmd){
NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//動(dòng)態(tài)創(chuàng)建類
Class newClass = objc_allocateClassPair([NSObject class], "Dog", 0);
//添加成員變量
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
//注冊(cè)類:類一旦注冊(cè)完畢,類對(duì)象和元類對(duì)象里邊的結(jié)構(gòu)就已經(jīng)創(chuàng)建好,所以添加成員變量要放在注冊(cè)類前邊。方法是可以放在注冊(cè)類之后的。
objc_registerClassPair(newClass);
id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];//打印結(jié)果為:<Dog: 0x108f2f070> run
NSLog(@"%zd",class_getInstanceSize(newClass));//打印結(jié)果為:16
NSLog(@"%@ %@",[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);//打印結(jié)果為:10 20
Person *person = [[Person alloc] init];
object_setClass(person, newClass);
[person run];//打印結(jié)果為:<Dog: 0x108f0f050> run
//在不需要這個(gè)類時(shí)釋放
objc_disposeClassPair(newClass);
}
return 0;
}
Runtime API02 - 成員變量
- 獲取一個(gè)實(shí)例變量信息:
Ivar class_getInstanceVariable(Class cls,const char *name) - 拷貝實(shí)例變量列表(最后需要調(diào)用free釋放):
Ivar *class_copyIvarList(Class cls,unsigned int *outCount) - 設(shè)置和獲取成員變量的值:
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar) - 動(dòng)態(tài)添加成員變量(已經(jīng)注冊(cè)的類是不能動(dòng)態(tài)添加成員變量的):
BOOL class_addIvar(Class cls,const char *name,size_t size,uint8 alignment,const char *types) - 獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
void run(id self,SEL _cmd){
NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//獲取成員變量信息
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));//打印結(jié)果為:_age i
//設(shè)置和獲取成員變量的值
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"123");
object_getIvar(person, nameIvar);
NSLog(@"name = %@",person.name);//打印結(jié)果為:name = 123
//成員變量的數(shù)量
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i=0; i<count; i++) {
//取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
//打印結(jié)果:
//_age i
//_name @"NSString"
free(ivars);
}
return 0;
}
Runtime API04 - 方法
- 獲得一個(gè)實(shí)例方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls,SEL name) - 方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls,SEL name)
IMP method_setImplemention(Method m,IMP imp)
void method_exchangeImplementations(Method m1,Method m2) - 拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount) - 動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types) - 動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types) - 獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method unsigned int index) - 選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str) - 用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
那么這些東西在實(shí)際項(xiàng)目中有什么作用呢?
Runtime的應(yīng)用01 - 查看私有成員變量
- 用runtime方法獲取并打印UITextField的成員變量,知道內(nèi)部的很多細(xì)節(jié)
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i=0; i<count; i++) {
//取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
free(ivars);
Runtime的應(yīng)用02 - 字典轉(zhuǎn)模型
- 利用Runtime遍歷所有的屬性或者成員變量
- 利用KVC設(shè)值
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,assign) int age;
@property(nonatomic,copy) NSString *name;
@property (nonatomic,assign) int weight;
@end
Person.m文件
#import "Person.h"
@implementation Person
@end
main文件
//字典轉(zhuǎn)模型
NSDictionary *json = @{
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack"
};
Person *person = [[Person alloc] init];
person.age = [json[@"age"] intValue];
person.weight = [json[@"weight"] intValue];
person.name = json[@"name"];
NSLog(@"----------");
但是如果模型里邊有很多屬性,就要寫很多設(shè)置的代碼,這里就可以給NSObject寫一個(gè)分類,處理字典轉(zhuǎn)模型的問(wèn)題,用runtime的方法實(shí)現(xiàn)
NSObject+Json.h文件
#import <Foundation/Foundation.h>
@interface NSObject (Json)
+ (instancetype)ld_objectWithJson:(NSDictionary *)json;
@end
NSObject+Json.m文件
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+(instancetype)mj_objectWithJson:(NSDictionary *)json{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i=0; i<count; i++) {
//取出i位置的成員變量
Ivar ivar = ivars[i];
// NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
//帶有下劃線的成員變量的名字
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
//刪除最開(kāi)始的下劃線,就可以去字典取東西了
[name deleteCharactersInRange:NSMakeRange(0, 1)];
//設(shè)值
[obj setValue:json[name] forKey:name];
}
free(ivars);
return obj;
}
@end
這里只是一個(gè)很簡(jiǎn)單的字典轉(zhuǎn)模型,沒(méi)有考慮所有情況。不是完整的字典轉(zhuǎn)模型的代碼。只是舉一個(gè)簡(jiǎn)單runtime的例子用
- (void)encodeWithCoder:(NSCoder *)coder{
[coder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)coder{
if (self = [super init]) {
self.name = [coder decodeObjectForKey:@"name"];
}
return self;
}
這里也可以用字典轉(zhuǎn)模型的思路實(shí)現(xiàn)歸檔、解檔
Runtime的應(yīng)用03 - 替換方法實(shí)現(xiàn)
- class_replaceMethod
- method_exchangeImplementations
void myrun(){
NSLog(@"-----myrun");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
//替換方法
class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
[person run];
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];//打印結(jié)果為:test ----
[person test];//打印結(jié)果為:run ----
}
return 0;
}
實(shí)現(xiàn)一個(gè)功能:攔截項(xiàng)目中所有按鈕的點(diǎn)擊事件
UIControl的分類
UIControl+ Extension.h文件
#import <UIKit/UIKit.h>
@interface UIControl (Extension)
@end
UIControl+ Extension.m文件
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
+(void)load{
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(ld_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)ld_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSLog(@"%@ - %@ - %@ ---",self,target,NSStringFromSelector(action));
//調(diào)用回系統(tǒng)原來(lái)的實(shí)現(xiàn)
[self ld_sendAction:action to:target forEvent:event];//這里為什么調(diào)用的是自己寫的而不是系統(tǒng)之前的方法名呢?因?yàn)樵谏线叺拇a里已經(jīng)交換了這兩個(gè)方法。所以這里調(diào)用自己寫的就是在調(diào)用系統(tǒng)自帶的方法
if ([self isKindOfClass:[UIButton class]]) {
//攔截了所有按鈕的事件
}
}
@end
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
- (IBAction)click1 {
NSLog(@"%s",__func__);
}
- (IBAction)click2 {
NSLog(@"%s",__func__);
}
- (IBAction)click3 {
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
/*
UIButton繼承自UIControl,UIControl中有如下一個(gè)方法,每個(gè)button點(diǎn)擊的時(shí)候都會(huì)先走下邊的方法。
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
那么想攔截所有按鈕的點(diǎn)擊,我們用runtime替換一下上邊系統(tǒng)自帶的方法就可以了
*/
}
@end
再舉一個(gè)數(shù)組的例子,如下:
NSString *str = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"jack"];
[array addObject:str];
//報(bào)錯(cuò):-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
NSLog(@"%zd",array.count);
如果我們不想因?yàn)閿?shù)組添加了nil報(bào)錯(cuò)崩掉,那么我們就需要每次添加數(shù)據(jù)之前都要進(jìn)行判斷是否等于nil,太麻煩了,這個(gè)時(shí)候我們就可以用runtime的方法,hook 住insertObject:atInde這個(gè)函數(shù),代碼如下:
NSString *str = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"jack"];
[array addObject:str];
//報(bào)錯(cuò):-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
NSLog(@"%@",array);
NSMutableArray+ Extensions.h文件
#import <Foundation/Foundation.h>
@interface NSMutableArray (Extensions)
@end
NSMutableArray+ Extensions.m文件
#import "NSMutableArray+Extensions.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Extensions)
+(void)load{
//類簇:NSString、NSArray、NSDictionary
//也就是說(shuō)在寫交換的方法時(shí),類對(duì)象那里一點(diǎn)要傳對(duì),雖然這里是要給NSMutableArray交換,但是他的底層依然是NSArray,所以直接寫self的話,是交換不成功的。所以這里的類對(duì)象需要放_(tái)_NSArrayM,從崩潰的提示能看出來(lái):-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(ld_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
}
- (void)ld_insertObject:(id)anObject atIndex:(NSUInteger)index{
if (anObject == nil) {
return;
}
[self ld_insertObject:anObject atIndex:index];
}
@end
這樣就不會(huì)崩潰報(bào)錯(cuò)了。
這里需要注意一點(diǎn)的是:
交換方法放在dispatch_once里邊,為了以防萬(wàn)一交換之后又交換一遍,
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//類簇:NSString、NSArray、NSDictionary
//也就是說(shuō)在寫交換的方法時(shí),類對(duì)象那里一點(diǎn)要傳對(duì),雖然這里是要給NSMutableArray交換,但是他的底層依然是NSArray,所以直接寫self的話,是交換不成功的。所以這里的類對(duì)象需要放_(tái)_NSArrayM,從崩潰的提示能看出來(lái):-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(ld_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}