Runtime應(yīng)用

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);
    });
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容