iOS Runtime幾種常見的應用場景

OC的動態性

OC 的函數是屬於動態調用,在編譯的時候是不能決定真正去調用那個函數的,只有在運行的時候才能決定去調用哪一個函數 ,在編譯階段,OC可以調用任何的函數,即使這個函數沒有實現,只要聲明過也就不會報錯。

場景一:防止button重複事件點擊

Method Swizzle執行點擊事件,我們利用runtime來方法交換,來達到防止重複點擊的目的:

  1. runtime添加時間間隔屬性
//UIButton添加一個Category
//.h文件
@interface UIButton (YY_FixMultiClick)
@property (nonatomic, assign) NSTimeInterval yy_acceptEventInterval; // 時間間隔
@property (nonatomic, assign) NSTimeInterval yy_acceptEventTime;
@end

注意:Category不能給類添加屬性, 所以以上的yy_acceptEventIntervalyy_acceptEventTime只會有對應的gettersetter方法, 不會添加真正的成員變量.
如果我們不在實現文件中添加其gettersetter方法, 則採用*** btn.yy_acceptEventInterval = 0.5; *** 這種方法訪問程序會崩潰.

2019-07-15 08:30:52.538 RuntimeDemo[17380:1387981] -[UIButton setYy_acceptEventInterval:]: unrecognized selector sent to instance 0x7fe8e198e880

因此:實現文件中通過runtime的關聯對象的方式, 爲UIButton添加以上兩個屬性.

#import "UIControl+YY_FixMultiClick"
#import <objc/runtime.h>
@implementation UIButton (YY_FixMultiClick)

// 因category不能添加屬性,只能通過關聯對象的方式。
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";

- (NSTimeInterval)yy_acceptEventInterval {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}

- (void)setYy_acceptEventInterval:(NSTimeInterval)yy_acceptEventInterval {
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(yy_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

- (NSTimeInterval)yy_acceptEventTime {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}

- (void)setYy_acceptEventTime:(NSTimeInterval)yy_acceptEventTime {
    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(yy_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
  1. 方法交換
//文件:.m
// 在load時執行
+ (void)load {
	[super load];
    Method normal = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method newMethod = class_getInstanceMethod(self, @selector(yy_sendAction:to:forEvent:));
    method_exchangeImplementations(normal, newMethod);
}

- (void)yy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSDate date].timeIntervalSince1970 - self.yy_acceptEventTime < self.yy_acceptEventInterval) {
        return;
    }

    if (self.yy_acceptEventInterval > 0) {
        self.yy_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }
    [self yy_sendAction:action to:target forEvent:event];
}
  1. button防止重複點擊使用:
btn.yy_acceptEventInterval = 0.5f; //UIButton指定了0.5s的時間間隔用於防止重複點擊.

場景二:訪問NSArray越界問題

爲NSArray添加Category代碼如下:

//
//  NSArray+Crash.m
//  YMallShop
//
//  Created by bruce on 2018/8/8.
//  Copyright © 2018年 bruce. All rights reserved.
//
#import "NSArray+Crash.h"
@implementation NSArray (Crash)
+ (void)load{
    [super load];
    //替換不可變數組方法
    Method oldObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method newObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtSafeIndex:));
    method_exchangeImplementations(oldObjectAtIndex, newObjectAtIndex);
    
    //替換可變數組方法
    Method oldMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:));
    Method newMutableObjectAtIndex =  class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(mutableObjectAtSafeIndex:));
    method_exchangeImplementations(oldMutableObjectAtIndex, newMutableObjectAtIndex);
}


- (id)objectAtSafeIndex:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self objectAtSafeIndex:index];
        }
        @catch (NSException *exception) {
            NSLog(@"exception: %@", exception.reason);
            return nil;
        }
    }else {
        return [self objectAtSafeIndex:index];
    }
}

- (id)mutableObjectAtSafeIndex:(NSUInteger)index{
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self mutableObjectAtSafeIndex:index];
        }
        @catch (NSException *exception) {
            NSLog(@"exception: %@", exception.reason);
            return nil;
        }
    }else {
        return [self mutableObjectAtSafeIndex:index];
    }
}
@end

場景三:快速接手新工程ViewController中添加Hook

//
//  UIViewController+Swizzling.m
//  qzy
//
//  Created by BruceYao on 2017/1/17.
//  Copyright © 2017年 BruceYao. All rights reserved.
//

#import "UIViewController+Swizzling.h"

@implementation UIViewController (Swizzling)

+ (void)load {
// 只在開發模式下,纔會出現交換
#ifdef DEBUG
    
//    原來的viewWillAppear方法
    Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
//    需要替換成,能夠輸出的日誌的viewWillAppear
    Method logViewWillAppear = class_getInstanceMethod(self, @selector(logViewWillAppear:));
//    兩個方法進行交換
    method_exchangeImplementations(viewWillAppear, logViewWillAppear);
    
    //    原來的viewWillDisappear方法
    Method viewWillDisappear = class_getInstanceMethod(self, @selector(viewWillDisappear:));
    //    需要替換成,能夠輸出的日誌的viewWillAppear
    Method logViewWillDisAppear = class_getInstanceMethod(self, @selector(logViewWillDisAppear:));
    //    兩個方法進行交換
    method_exchangeImplementations(viewWillDisappear, logViewWillDisAppear);
#endif
}

- (void)logViewWillAppear:(BOOL)animater {
    
    NSString *className = NSStringFromClass([self class]);
    //[MobClick beginLogPageView:className]; 統計的時候會用到、友盟統計
    NSLog(@"%@ will appear", className);
}

- (void)logViewWillDisAppear:(BOOL)animater {
    NSString *className = NSStringFromClass([self class]);
    //[MobClick endLogPageView:className];統計的時候會用到
}
@end

###場景四:歸檔、反歸檔

#import "EncodeAndUnEncode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation EncodeAndUnEncode
//解
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar * ivarlist = class_copyIvarList([self class], &count);
        for (int i = 0; i<count; i++) {
            Ivar alvar = ivarlist[i];
            const char * name = ivar_getName(alvar);
            id value = [aDecoder decodeObjectForKey:[NSString stringWithUTF8String:name]];
            if value != nil {
             [self setValue:value forKey:[NSString stringWithUTF8String:name]];   
            }
        }
        free(ivarlist);
    }
    return self;
}
//歸
-(void)encodeWithCoder:(NSCoder *)aCoder{
    
    //獲取某個類的成員變量
    unsigned int count = 0;
    Ivar * ivarlist = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar alvar = ivarlist[i];
        //獲取成員變量的名稱
        const char * ivarname = ivar_getName(alvar);
        id value = [self valueForKey:[NSString stringWithUTF8String:ivarname]];
        if value != nil {
            [aCoder encodeObject:value forKey:[NSString stringWithUTF8String:ivarname]];
        }
    }
    free(ivarlist);   
}

動態的添加方法

開發使用場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。
調用:

Person *per = [[Person alloc] init];
    per.name = [[NSString alloc] initWithFormat:@"張三"];
    [per eat];
    objc_msgSend(per, @selector(eat));
    [per performSelector:@selector(sleeping)];

對應的類中:Person,或者在分類中添加也可以

void sleeping(id self, SEL sel) {
NSLog(@"%@ %@", self, NSStringFromSelector(sel));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sleeping)) {
// type: 方法類型:void用v來表示,id參數用@來表示,SEL用:來表示
class_addMethod(self, @selector(sleeping), sleeping, "v@:");
}
return [super resolveInstanceMethod:sel];
}

發送消息

注意點:在工程配置頁面中:Enable Strict Checking of objc_msgSend Calls 設置爲NO即可,要不然會引起crash
在這裏插入圖片描述

 Person *per = [[Person alloc] init];
    per.name = [[NSString alloc] initWithFormat:@"張三"];
    [per eat];
    objc_msgSend(per, @selector(eat));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章