iOS runtime實用篇--監聽方法的呼叫
若覺得前面的廢話太多,大家可直接看
- 3、第三種方法也是這篇部落格重點需要講解的方法,用runtime來處理,大致步驟如下幾點
原始碼地址:
https://github.com/chenfanfang/CollectionsOfExample
背景
在以前的app專案中,由於懶得封裝載入的蒙版,就直接使用了MBProgressHUD和SVProgressHUD,不得不說用得的確很方便,特別是SVProgressHUD。不過不知大家有沒有發現這樣顯示蒙版和隱藏蒙版非常突兀嗎?特別是網路請求非常快的時候,可能1s的時間都不到,給人有種一閃的感覺,當然處理方法很多(比如自己寫個方法進行延遲隱藏.....)這裡不做過多的闡述。
由於個人實在不喜歡那種一閃的感覺,所以打算在新啟動的專案中自己寫一個提示載入的蒙版,思來想去,最終還是覺得類似微信載入網頁的那種進度條比較符合我的胃口,於是封裝了個簡易的進度條,並且新增到整個專案的基類控制器(百分百AOP主義者不必做過多關於基類與AOP的評論,本文主要進行runtime監聽方法呼叫的實踐)。
老樣子先附上效果圖一張
下面先附上簡單的程式碼(由於部分用到巨集,為了直觀看到效果圖,大家可以下載原始碼檢視)
封裝的進度條.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 ()
/** 網路請求的進度條 */
@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];
//網路請求前開始載入進度條
[self starProgress];
//模擬網路延遲
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//網路請求完成結束進度條的載入
[self endProgress];
});
}
遇到問題
怎樣保持進度條始終在控制器view的最前面呢?(若不做處理,可能進度條會在控制器的view addsubView的時候給遮擋住) 我的第一反應是用KVO監聽做處理,最終以失敗告終(若簡書朋友發現用KVO可以處理的話,麻煩留下處理的方法)。
解決方案
想了三種解決方案,並且列出了可行性。
- 1、用UIWindow來處理進度條,這樣進度條就永遠不會被遮擋。
可行性分析:若整個專案中只用一個進度條就可以用這個方式解決(什麼叫只用一個進度條呢?就是一個頁面若沒有載入完成進入不了別的介面。舉個例子,兩個介面同時處於載入狀態,若共用一個進度條則不合理。)
- 2、在基類控制器重寫
-(void)loadView
方法,自定義self.view即可,簡易步驟如下
- (void)loadView {
self.view = [[FFCustomView alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[self.view class]);
}
//控制器輸出為 :FFCustomView
這樣子就很容易監聽到addSubView了,在FFCustomView的.h定義一個回撥block,在FFCustomView的.m重寫addSubview,簡易程式碼如下FFCustomView.h
#import <UIKit/UIKit.h>
@interface FFCustomView : UIView
/** addSubView完成的回撥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
在控制器中監聽addSubView簡易程式碼如下
FFCustomView *customView = (FFCustomView *)self.view;
customView.didAddSubView = ^ {
NSLog(@"addSubView啦");
//addSubView之後需要做何處理的程式碼寫在這即可
};
[self.view addSubview:[UIView new]];
//控制器輸出:addSubView啦
可行性分析:在解決監聽控制器的view 新增子控制元件的情景下是可行的。這種方法只是作為一種普通的解決方案,本文主要介紹如何用runtime監聽所有方法呼叫。所以大家重點請看第三點解決方案。
- 3、第三種方法也是這篇部落格重點需要講解的方法,用runtime來處理,大致步驟如下幾點
- 新建一個UIView的category
- 通過runtime關聯的方式新增屬性 objc_setAssociatedObject、objc_getAssociatedObject,新增一個addSubview之後的回撥block的屬性:void(^didAddsubView)()
- 通過runtime進行方法的交換,監聽addSubView
下面直接附上程式碼
category的.h檔案
#import <UIKit/UIKit.h>
@interface UIView (FFExtension)
/** addSubview之後的回撥block */
@property (nonatomic, copy) void(^didAddsubView)();
@end
category的.m檔案
#import "UIView+FFExtension.h"
#import <objc/runtime.h>
@implementation UIView (FFExtension)
/// 新增屬性
// 定義關聯的key
static const char *key_didAddsubView = "didAddsubView";
- (void (^)())didAddsubView {
return objc_getAssociatedObject(self, key_didAddsubView);
}
- (void)setDidAddsubView:(void (^)())didAddsubView {
// 第一個引數:給哪個物件新增關聯
// 第二個引數:關聯的key,通過這個key獲取
// 第三個引數:關聯的value
// 第四個引數:關聯的策略
objc_setAssociatedObject(self, key_didAddsubView, didAddsubView, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/// 黑魔法交換方法
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//進行方法交換,目的:讓UIView addSubView的時候可以被監聽到
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
這樣就可以監聽所有的view的addSubview了,監聽方式如下
- (void)viewDidLoad {
[super viewDidLoad];
self.view.didAddsubView = ^ {
NSLog(@"我是控制器的view,我新增了一個新控制元件");
};
[self.view addSubview:[UIView new]];
}
//控制檯輸出:我是控制器的view,我新增了一個新控制元件
可行性分析:個人是比較推薦用這種方法進行處理。按理說,要監聽iOS中方法的呼叫,用此方法即可實現。萬能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的方式可以監聽到所有方法的呼叫,這樣也就補充了KVO的不足之處。
部落格中若有什麼錯誤或者不足,還望指出
相關文章
- Runtime-iOS執行時應用篇iOS
- Runtime原始碼 方法呼叫的過程原始碼
- iOS ViewController Dealloc監聽iOSViewController
- iOS開發·runtime原理與實踐: 基本知識篇iOS
- iOS開發·KVO用法,原理與底層實現: runtime模擬實現KVO監聽機制(Blcok及Delgate方式)iOS
- 監聽者模式實戰應用模式
- iOS開發Runtime的理解與應用iOS
- iOS 開發中 runtime 常用的幾種方法iOS
- iOS Runtime 初識與應用iOS
- iOS NSString中實用的方法iOS
- java鍵盤監聽之視窗監聽的實現Java
- 監聽檔案修改的四種方法
- vue的監聽鍵盤事件的快捷方法Vue事件
- 玩轉iOS開發:裝逼技術RunTime的總結篇iOS
- iOS 陣列中那些實用的方法iOS陣列
- iOS runtime執行時的作用和應用場景iOS
- iOS RunTime 總結iOS
- iOS Runtime詳解iOS
- iOS 利用Runtime實現萬能歸檔iOS
- Lumen 實現 SQL 監聽SQL
- JavaScript如何呼叫Native iOS/Android 方法JavaScriptiOSAndroid
- ios runtime之Method Swizzling及其應用場景iOS
- 監聽 watch props物件屬性監聽 或深度監聽物件
- windows10怎麼檢視監聽埠_windows10檢視監聽埠的方法Windows
- iOS開發·runtime原理與實踐: 基本知識篇(類,超類,元類,super_class,isa,物件,方法,SEL,IMP)iOS物件
- localeStorage 當前標籤頁變化監聽不到,只能監聽不同標籤頁變化,自己寫方法監聽
- Vue:watch 監聽多個屬性值的方法Vue
- vue計算屬性 監聽 方法的區別Vue
- 用這招監聽 Vue 的插槽變化Vue
- iOS 防止Crash之runtimeiOS
- H5 呼叫 Android 和 iOS 方法H5AndroidiOS
- Laravel 實時監聽列印 SQLLaravelSQL
- 玩轉iOS開發:裝逼技術RunTime的應用(一)iOS
- iOS底層原理 runtime-object_class拾遺基礎篇--(6)iOSObject
- 「Python實用祕技06」逐行監聽Python程式的記憶體消耗Python記憶體
- OkHttp優雅的實現下載監聽HTTP
- 實戰監聽Eureka client的快取更新client快取
- JS監聽手機物理返回鍵(及IOS微信端的bug)JSiOS
- FreeSwtich的監聽功能