iOS runtime實用篇--監(jiān)聽方法的調(diào)用

若覺得前面的廢話太多,大家可直接看

  • 3、第三種方法也是這篇博客重點需要講解的方法,用runtime來處理,大致步驟如下幾點

源碼地址:

https://github.com/chenfanfang/CollectionsOfExample

背景

在以前的app項目中,由于懶得封裝加載的蒙版,就直接使用了MBProgressHUD和SVProgressHUD,不得不說用得的確很方便,特別是SVProgressHUD。不過不知大家有沒有發(fā)現(xiàn)這樣顯示蒙版和隱藏蒙版非常突兀嗎?特別是網(wǎng)絡請求非??斓臅r候,可能1s的時間都不到,給人有種一閃的感覺,當然處理方法很多(比如自己寫個方法進行延遲隱藏.....)這里不做過多的闡述。
由于個人實在不喜歡那種一閃的感覺,所以打算在新啟動的項目中自己寫一個提示加載的蒙版,思來想去,最終還是覺得類似微信加載網(wǎng)頁的那種進度條比較符合我的胃口,于是封裝了個簡易的進度條,并且添加到整個項目的基類控制器(百分百AOP主義者不必做過多關于基類與AOP的評論,本文主要進行runtime監(jiān)聽方法調(diào)用的實踐)。

老樣子先附上效果圖一張

進度條測試.gif

下面先附上簡單的代碼(由于部分用到宏,為了直觀看到效果圖,大家可以下載源碼查看)

封裝的進度條.h文件

#import <UIKit/UIKit.h>

/**
 *  進度條
 */
@interface FFProgressView : UIView


- (void)star;

- (void)end;

@end


封裝的進度條.m文件

#import "FFProgressView.h"


@interface FFProgressView ()

/** 前景view */
@property (nonatomic, strong) UIView *foregroundView;

@end

@implementation FFProgressView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        //背景色
        self.backgroundColor = [UIColor clearColor];
        
        self.foregroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 3)];
        
        //前景色
        self.foregroundView.backgroundColor = FFColor(54, 188, 37);
        [self addSubview:self.foregroundView];
        
    }
    return self;
}

/// 開始加載進度條
- (void)star {
    self.foregroundView.width = 0;
    self.hidden = NO;
    
    [UIView animateWithDuration:10 delay:0 usingSpringWithDamping:1.0f initialSpringVelocity:1.0f options:kNilOptions animations:^{
        self.foregroundView.width = FFScreenW * 0.8;
        
    } completion:nil];
}

/// 結束加載進度條
- (void)end {
    
    [UIView animateWithDuration:0.5 animations:^{
        self.foregroundView.width = FFScreenW;
    } completion:^(BOOL finished) {
        self.hidden = YES;
    }];
}

基類控制器的.h文件

#import <UIKit/UIKit.h>
/**
 *  基類控制器
 */
@interface FFRuntimeBaseController : UIViewController

/** 啟動進度條 */
- (void)starProgress;

/** 結束進度條 */
- (void)endProgress;

@end

基類控制器的.m文件

#import "FFRuntimeBaseController.h"

//view
#import "FFProgressView.h"

@interface FFRuntimeBaseController ()

/** 網(wǎng)絡請求的進度條 */
@property (nonatomic, strong) FFProgressView *progressView;

@end

@implementation FFRuntimeBaseController


- (void)viewDidLoad {
    [super viewDidLoad];
    
    
}

//=================================================================
//                           懶加載
//=================================================================
#pragma mark - 懶加載

- (FFProgressView *)progressView {
    if (_progressView == nil) {
        _progressView = [FFProgressView new];
        [self.view addSubview:_progressView];
        
        _progressView.frame = CGRectMake(0, 0, FFScreenW, 3);
        
        _progressView.hidden = YES;
        
    }
    
    return _progressView;
}



//=================================================================
//                           進度條
//=================================================================
#pragma mark - 進度條

- (void)starProgress {
    
    [self.progressView star];
}

- (void)endProgress {
    
    [self.progressView end];
}


@end

在子類控制器中使用進度條

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //網(wǎng)絡請求前開始加載進度條
    [self starProgress];
    
    
    //模擬網(wǎng)絡延遲
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        //網(wǎng)絡請求完成結束進度條的加載
        [self endProgress];
    });
    
}

遇到問題

怎樣保持進度條始終在控制器view的最前面呢?(若不做處理,可能進度條會在控制器的view addsubView的時候給遮擋住) 我的第一反應是用KVO監(jiān)聽做處理,最終以失敗告終(若簡書朋友發(fā)現(xiàn)用KVO可以處理的話,麻煩留下處理的方法)。

解決方案

想了三種解決方案,并且列出了可行性。

  • 1、用UIWindow來處理進度條,這樣進度條就永遠不會被遮擋。

可行性分析:若整個項目中只用一個進度條就可以用這個方式解決(什么叫只用一個進度條呢?就是一個頁面若沒有加載完成進入不了別的界面。舉個例子,兩個界面同時處于加載狀態(tài),若共用一個進度條則不合理。)


  • 2、在基類控制器重寫-(void)loadView方法,自定義self.view即可,簡易步驟如下

- (void)loadView {
   self.view = [[FFCustomView alloc] init];
}

- (void)viewDidLoad {
   [super viewDidLoad];
   
   NSLog(@"%@",[self.view class]);
}

//控制器輸出為 :FFCustomView

這樣子就很容易監(jiān)聽到addSubView了,在FFCustomView的.h定義一個回調(diào)block,在FFCustomView的.m重寫addSubview,簡易代碼如下
FFCustomView.h

#import <UIKit/UIKit.h>

@interface FFCustomView : UIView

/** addSubView完成的回調(diào)block */
@property (nonatomic, copy) void(^didAddSubView)();

@end

FFCustomView.m

#import "FFCustomView.h"

@implementation FFCustomView

- (void)addSubview:(UIView *)view {
    
    [super addSubview:view];
    
    if (self.didAddSubView) {
        self.didAddSubView();
    }
}

@end

在控制器中監(jiān)聽addSubView簡易代碼如下

    FFCustomView *customView = (FFCustomView *)self.view;
    customView.didAddSubView = ^ {
        NSLog(@"addSubView啦");
        //addSubView之后需要做何處理的代碼寫在這即可
    };
    
    [self.view addSubview:[UIView new]];
    
    //控制器輸出:addSubView啦

可行性分析:在解決監(jiān)聽控制器的view 添加子控件的情景下是可行的。這種方法只是作為一種普通的解決方案,本文主要介紹如何用runtime監(jiān)聽所有方法調(diào)用。所以大家重點請看第三點解決方案。

  • 3、第三種方法也是這篇博客重點需要講解的方法,用runtime來處理,大致步驟如下幾點
    • 新建一個UIView的category
    • 通過runtime關聯(lián)的方式添加屬性 objc_setAssociatedObject、objc_getAssociatedObject,添加一個addSubview之后的回調(diào)block的屬性:void(^didAddsubView)()
    • 通過runtime進行方法的交換,監(jiān)聽addSubView

下面直接附上代碼

category的.h文件

#import <UIKit/UIKit.h>

@interface UIView (FFExtension)

/** addSubview之后的回調(diào)block */
@property (nonatomic, copy) void(^didAddsubView)();

@end

category的.m文件

#import "UIView+FFExtension.h"
#import <objc/runtime.h>

@implementation UIView (FFExtension)

/// 添加屬性

// 定義關聯(lián)的key
static const char *key_didAddsubView = "didAddsubView";

- (void (^)())didAddsubView {
    return objc_getAssociatedObject(self, key_didAddsubView);
}

- (void)setDidAddsubView:(void (^)())didAddsubView {
    // 第一個參數(shù):給哪個對象添加關聯(lián)
    // 第二個參數(shù):關聯(lián)的key,通過這個key獲取
    // 第三個參數(shù):關聯(lián)的value
    // 第四個參數(shù):關聯(lián)的策略
    objc_setAssociatedObject(self, key_didAddsubView, didAddsubView, OBJC_ASSOCIATION_COPY_NONATOMIC);
}



/// 黑魔法交換方法

+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //進行方法交換,目的:讓UIView addSubView的時候可以被監(jiān)聽到
        Class class_UIView = NSClassFromString(@"UIView");
        
        SEL originalSelector = @selector(addSubview:);
        SEL swizzledSelector = @selector(swizzlingAddSubview:);
        
        Method originalMethod = class_getInstanceMethod(class_UIView, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class_UIView, swizzledSelector);
        
        BOOL didAddMethod =
        class_addMethod(class_UIView,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            class_replaceMethod(class_UIView,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}


- (void)swizzlingAddSubview:(UIView *)view {
    
    [self swizzlingAddSubview:view];
    
    if (self.didAddsubView) {
        self.didAddsubView();
    }
}

@end

這樣就可以監(jiān)聽所有的view的addSubview了,監(jiān)聽方式如下

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.didAddsubView = ^ {
        NSLog(@"我是控制器的view,我添加了一個新控件");
    };
    
    
    [self.view addSubview:[UIView new]];
}

//控制臺輸出:我是控制器的view,我添加了一個新控件

可行性分析:個人是比較推薦用這種方法進行處理。按理說,要監(jiān)聽iOS中方法的調(diào)用,用此方法即可實現(xiàn)。萬能KVO.......

解決上面進度條可能被其他view遮擋的問題

將原本進度條的懶加載改成如下即可

//=================================================================
//                           懶加載
//=================================================================
#pragma mark - 懶加載

- (FFProgressView *)progressView {
    if (_progressView == nil) {
        _progressView = [FFProgressView new];
        [self.view addSubview:_progressView];
        _progressView.frame = CGRectMake(0, 0, FFScreenW, 3);
        _progressView.hidden = YES;
        

        //和前面的懶加載相比,主要添加了如下的代碼
        __weak typeof(self) weakSelf = self;
        __weak typeof(_progressView) weakProgressView = _progressView;
        
        self.view.didAddsubView = ^ {
            //將進度條挪到最前面
            [weakSelf.view bringSubviewToFront:weakProgressView];
        };
        
    }
    
    return _progressView;
}

總結

按理說,利用方式3的方式可以監(jiān)聽到所有方法的調(diào)用,這樣也就補充了KVO的不足之處。
博客中若有什么錯誤或者不足,還望指出










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

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

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