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的不足之處。
部落格中若有什麼錯誤或者不足,還望指出
相關文章
- iOS 萬能跳轉介面方法 (runtime實用篇一)iOS
- Runtime-iOS執行時應用篇iOS
- iOS Runtime(一) Runtime的應用iOS
- Runtime原始碼 方法呼叫的過程原始碼
- 用Activity的onTouchEvent方法實現監聽手指上下左右滑動
- 監聽者模式實戰應用模式
- iOS筆記--UIButton常用屬性和監聽方法iOS筆記UI
- iOS開發·KVO用法,原理與底層實現: runtime模擬實現KVO監聽機制(Blcok及Delgate方式)iOS
- iOS開發·runtime原理與實踐: 基本知識篇iOS
- 在 iOS 中實現方法鏈呼叫iOS
- [iOS] ios的runtimeiOS
- java鍵盤監聽之視窗監聽的實現Java
- iOS Runtime 實踐(1)iOS
- iOS ViewController Dealloc監聽iOSViewController
- iOS --runtime理解與應用iOS
- Tomcat監聽443埠的方法Tomcat
- iOS 監聽裝置方向旋轉(iOS 9)iOS
- EditText監聽方法,實時的判斷輸入多少字元字元
- iOS AFN監聽網路狀態iOS
- 【iOS】category重寫方法的呼叫iOSGo
- iOS開發Runtime的理解與應用iOS
- iOS 開發中 runtime 常用的幾種方法iOS
- 監聽檔案修改的四種方法
- iOS Runtime 初識與應用iOS
- ios萬能跳轉介面方法(Runtime)iOS
- vue的監聽鍵盤事件的快捷方法Vue事件
- iOS Runtime的理解iOS
- iOS NSString中實用的方法iOS
- Jbpm4監聽的實現
- iOS 使用 Reachability 監聽網路狀態iOS
- iOS RuntimeiOS
- iOS~runtimeiOS
- Lumen 實現 SQL 監聽SQL
- JAMon監控web工程方法的呼叫效能Web
- Java GUI之事件監聽與處理的匿名類實現方法JavaGUI事件
- iOS runtime實戰應用:成員變數和屬性iOS變數
- 閱讀layim程式碼小記,監聽事件實現方法事件
- flash呼叫攝像頭彈出設定框監聽