IOS開發中,UIViewController是最常用的一個類,在Push和Pop的進程中也會常常呈現一些UI卡死、App閃退的問題,本文總結了開發中遇到的一些坑。
大局部視圖控制器切換招致的問題,基本緣由都是運用了動畫,由於執舉動畫需求時間,在動畫未完成的時分又停止另一個切換動畫,容易發生異常,假設在 Push 和 Pop 的進程不運用動畫,世界會喧囂很多。所以本文只討論運用了動畫的視圖切換。也就是運用以下方式的 Push 和 Pop:
self.navigationController pushViewController:controller animated:YES];
[self.navigationController popViewControllerAnimated:YES];
1. 延續 Push
延續兩次 Push 不同的 ViewController 是沒問題的,比方這樣:
- (void)onPush: {
[self.navigationController pushViewController:vc1 animated:YES];
[self.navigationController pushViewController:vc2 animated:YES];
}
但是,假如不小心延續 Push 了同一個 ViewController,並且 animated 為 YES,則會 Crash:Pushing the same view controller instance more than once is not supported
。
這種狀況很有能夠發作,特別是界面上觸發切換的入口不止一處,並且各個入口的點擊沒有互斥的話,用兩根手指同時點擊屏幕就會同時觸發兩個入口的切換了。多點觸碰招致的同時 Push,根本上是防不勝防,當界面元素很復雜的時分,特別容易呈現這個問題,而指望從用戶交互的角度上防止這個問題是不能夠的,測試美眉以暴力測試、胡亂點擊而著稱,防得了用戶防不住測試。
所以我們需求從基本上處理這個問題:當一個 Push 動畫還沒完成的時分,不允許再 Push 別的 ViewController。這樣處置是沒有問題的,由於延續帶動畫地 Push 多個 ViewController 一定不是開發和產品的志願,就算有這種需求,也可以經過禁用動畫的方式來處理。
1.1 處理方案承繼 UINavigationController 偏重載 pushViewController 辦法。
假如是動畫 Push,並且屬性isSwitching == YES
,則疏忽這次 Push。否則,設置isSwitching = YES
再持續切換。等到動畫切換終了,需求再把isSwitching
改為 NO。
@interface MYNavigationController () <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@property (assign, nonatomic) BOOL isSwitching;
@end
@implementation MYNavigationController
// 重載 push 辦法
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (animated) {
if (self.isSwitching) {
return; // 1. 假如是動畫,並且正在切換,直接疏忽
}
self.isSwitching = YES; // 2. 否則修正形態
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.isSwitching = NO; // 3. 復原形態
}
2. 延續 Pop
延續 Pop ,能夠會招致兩種狀況。
2.1 self 被釋放例如,上面的代碼,執行到第二句的時分,self 曾經被釋放了。
[self.navigationController popViewControllerAnimated:YES]; // self 被 release
[self.navigationController popViewControllerAnimated:YES]; // 持續訪問 self 招致異常
2.2 界面異常、解體
假設你避開了下面那種調用,換成了這樣:
[[AppDelegate sharedObject].navigationController popViewControllerAnimated:YES];
[[AppDelegate sharedObject].navigationController popViewControllerAnimated:YES];
由於訪問的是全局的 AppDelegate,自然防止了調用者被釋放的問題,但是,延續兩次動畫 Pop,在IOS 7.X 零碎會招致界面混亂、卡死、莫明其妙的解體(IOS 8 貌似不存在類似的問題)。比方,上面這個解體的堆棧:
{"bundleID":"com.enterprise.kiwi","app_name":"kiwi","bug_type":"109","name":"kiwi","os_version":"iPhone OS 7.1.1 (11D201)","version":"1190 (3.1.0)"}
Incident Identifier: FE85E864-393C-417D-9EA0-B4324BEEDA2F
CrashReporter Key: a54805586b9487c324ff5f42f4ac93dabbe9f23e
Hardware Model: iPhone6,1
Process: kiwi [1074]
Path: /var/mobile/Applications/D81CE836-3F88-481C-AA5A-21DA530234E0/kiwi.app/kiwi
Identifier: com.yy.enterprise.kiwi
Version: 1190 (3.1.0)
Code Type: ARM-64 (Native)
Parent Process: launchd [1]
Date/Time: 2015-09-08 15:44:57.327 +0800
OS Version: iOS 7.1.1 (11D201)
Report Version: 104
Exception Type: EXC_CRASH (SIGSEGV)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Triggered by Thread: 1
Thread 0:
0 libobjc.A.dylib 0x00000001993781dc objc_msgSend + 28
1 UIKit 0x000000018feacf14 -[UIResponder(Internal) _canBecomeFirstResponder] + 20
2 UIKit 0x000000018feacba0 -[UIResponder becomeFirstResponder] + 240
3 UIKit 0x000000018feacfa0 -[UIView(Hierarchy) becomeFirstResponder] + 120
4 UIKit 0x000000018ff320f8 -[UITextField becomeFirstResponder] + 64
5 UIKit 0x000000018ffe4800 -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) setFirstResponderIfNecessary] + 208
6 UIKit 0x000000018ffe3f84 -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) oneFingerTap:] + 1792
7 UIKit 0x000000018ffcac60 _UIGestureRecognizerSendActions + 212
8 UIKit 0x000000018fe5929c -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 376
9 UIKit 0x000000019025803c ___UIGestureRecognizerUpdate_block_invoke + 56
10 UIKit 0x000000018fe1a258 _UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks + 284
11 UIKit 0x000000018fe18b34 _UIGestureRecognizerUpdate + 208
12 UIKit 0x000000018fe57b1c -[UIWindow _sendGesturesForEvent:] + 1008
13 UIKit 0x000000018fe5722c -[UIWindow sendEvent:] + 824
14 UIKit 0x000000018fe28b64 -[UIApplication sendEvent:] + 252
15 UIKit 0x000000018fe26c54 _UIApplicationHandleEventQueue + 8496
16 CoreFoundation 0x000000018ce1f640 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 20
17 CoreFoundation 0x000000018ce1e99c __CFRunLoopDosources0 + 252
18 CoreFoundation 0x000000018ce1cc34 __CFRunLoopRun + 628
19 CoreFoundation 0x000000018cd5dc1c CFRunLoopRunSpecific + 448
20 GraphicsServices 0x0000000192a45c08 GSEventRunModal + 164
21 UIKit 0x000000018fe8efd8 UIApplicationMain + 1152
22 kiwi 0x000000010026a2b8 main (main.mm:26)
23 libdyld.dylib 0x000000019995ba9c start + 0
Thread 1 Crashed:
0 libsystem_kernel.dylib 0x0000000199a3daa8 kevent64 + 8
1 libdispatch.dylib 0x0000000199941998 _dispatch_mgr_thread + 48
從解體記載完全看不出緣由,非常坑爹。
2.3 處理方案 方案一:第一次 Pop 不運用動畫。方案二:一致管理 Pop 的調用,假如以後正在 Pop,則下一次 Pop 先入棧;等到 Pop 執行完再執行下一次 Pop。 3. Push 的進程中立刻 PopPush 的進程中調用 Pop,會招致界面卡死,表現為:不呼應任何點擊、手勢操作,但是不會解體。這也是在 iOS7 中呈現的問題,iOS 8 之後不存在。
3.1 處理方案同 1.1,重載 Pop 辦法:
Pop 的時分先判別能否在切換中;假如正在切換,則 Pop 的命令先保管到隊列;切換動畫執行終了,判別能否需求處置 Pop 的隊列。#pragma mark - UINavigationController
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (!self.isSwitching) {
return [super popToViewController:viewController animated:animated];
} else {
[self enqueuePopViewController:viewController animate:animated];
return nil;
}
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
if (!self.isSwitching) {
return [super popViewControllerAnimated:animated];
} else {
[self enqueuePopViewController:nil animate:animated];
return nil;
}
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.isSwitching = NO;
// 顯示終了之後判別能否需求Pop
if (self.popVCAnimateQueue.count) {
PopVCInfo *info = [self.popVCAnimateQueue firstObject];
[self.popVCAnimateQueue removeObjectAtIndex:0];
if (info.controller) {
[self.navigationController popToViewController:info.controller animated:info.animate];
} else {
[self.navigationController popViewControllerAnimated:info.animate];
}
}
}
4. Push 的進程中手勢滑動前往
手勢滑動前往實質上調用的還是 Pop,所以,同上。
不過,還可以更基本地制止用戶停止這樣的操作,也就是在切換進程中制止滑動前往手勢。
#pragma mark - UINavigationController
// Hijack the push method to disable the gesture
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.interactivePopGestureRecognizer.enabled = NO;
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.isSwitching = NO;
self.interactivePopGestureRecognizer.enabled = YES;
// 顯示終了之後判別能否需求Pop
if (self.popVCAnimateQueue.count) {
PopVCInfo *info = [self.popVCAnimateQueue firstObject];
[self.popVCAnimateQueue removeObjectAtIndex:0];
if (info.controller) {
[self.navigationController popToViewController:info.controller animated:info.animate];
} else {
[self.navigationController popViewControllerAnimated:info.animate];
}
}
}
【UIViewController Push & Pop 的那些坑】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!