iPhone上のアプリケーションを粉砕する方法、およびこれを防ぐ方法

画像



Surfingbirdで、アプリケーションを安定してクラッシュさせる奇妙なエラーを見つけました。 後に、ほとんどすべてのアプリケーションが非常に単純にクラッシュすることが判明しました(Apple自身が作成したアプリケーションも)。 これがどのようなエラーで、どのように回避するかを説明します。



説明したすべてがiOS 7以下に当てはまることをすぐに明確にします。 iOS 8の変更点について-記事の最後(実際には良いことはありません)。


練習から始めましょう。 2つのボタンがあり、それぞれに新しい画面が表示されます。 両方のボタンを同時に押して(少し練習する必要があります)、次に2回押します。



画像



アプリケーションをドロップするには、navigationControllerが必要です。 (アニメーション付きの)navigationControllerでviewControllerを実行し、アニメーションの終了を待たずに2番目のviewControllerを開始し、戻るボタンを2回押すと、アプリケーションがクラッシュします。 誰もそれをしないので、最初はナンセンスに聞こえます。 ただし、iPhoneにはマルチタッチ機能があり、同時に複数のボタンを押すことができることを忘れないでください。 実際、これにつながるのは複雑なコードではありません。



@interface ViewController () @property (strong, nonatomic) UIButton *buttonL; @property (strong, nonatomic) UIButton *buttonR; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.navigationItem.title = @"root"; self.view.backgroundColor = [UIColor whiteColor]; self.buttonL = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 1.0f, 1.0f)]; self.buttonL.backgroundColor = [UIColor blueColor]; [self.buttonL setTitle:@"push vc #1" forState:UIControlStateNormal]; [self.buttonL addTarget:self action:@selector(pushViewControllerOne) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.buttonL]; self.buttonR = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 1.0f, 1.0f)]; self.buttonR.backgroundColor = [UIColor redColor]; [self.buttonR setTitle:@"push vc #2" forState:UIControlStateNormal]; [self.buttonR addTarget:self action:@selector(pushViewControllerTwo) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.buttonR]; } - (void) viewWillLayoutSubviews { CGFloat width = self.view.bounds.size.width /2; CGFloat height = self.view.bounds.size.height; [self.buttonL setFrame:CGRectMake(0.0f, 0.0f, width, height)]; [self.buttonR setFrame:CGRectMake(width, 0.0f, width, height)]; } - (void) pushViewControllerOne { UIViewController *vc1 = [UIViewController new]; vc1.navigationItem.title = @"#1"; vc1.view.backgroundColor = [UIColor whiteColor]; [self.navigationController pushViewController:vc1 animated:YES]; } - (void) pushViewControllerTwo { UIViewController *vc1 = [UIViewController new]; vc1.navigationItem.title = @"#2"; vc1.view.backgroundColor = [UIColor whiteColor]; [self.navigationController pushViewController:vc1 animated:YES]; } @end
      
      





Xcodeログを見ると、ネストされたアニメーションに関する警告とナビゲーションバーの損傷の可能性を確認できます。

ネストされたプッシュアニメーションにより、ナビゲーションバーが破損する可能性があります

予期しない状態でのナビゲーション遷移の仕上げ。 ナビゲーションバーのサブビューツリーが破損する場合があります。

キャッチされない例外「NSInvalidArgumentException」によるアプリの終了、理由:「サブビューとして自分を追加できません」


ネット上では、このエラーの説明は非常にまれであり、解決策は1つしか見つかりませんでしたが、機能しません。 したがって、私たちはサンタクロースになり、コミュニティにApple が決して解決できない問題の解決策を提供することにしました。



問題の解決策は非常に明白です。UINavigationControllerから継承し、すべてのpushiesをキューに入れてから、それらを順番に実行します。 実装を理解するために必要なコードの部分を以下に説明します。



 // // StackNavigationController.m // #import "StackNavigationController.h" @interface StackNavigationController () <UINavigationControllerDelegate> @property (nonatomic, assign) BOOL isTransitioning; @property (nonatomic, strong) NSMutableArray *tasks; @property (nonatomic, weak) id<UINavigationControllerDelegate> customDelegate; @end @implementation StackNavigationController -(void)viewDidLoad { [super viewDidLoad]; if (self.delegate) { self.customDelegate = self.delegate; } self.delegate = self; self.tasks = [NSMutableArray new]; } // we should save navController.delegate to another property because we need delegate // to prevent multiple push/pop bug -(void)setDelegate:(id<UINavigationControllerDelegate>)delegate { if (delegate == self) { [super setDelegate:delegate]; } else { self.customDelegate = delegate; } } - (void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated { @synchronized(self.tasks) { if (self.isTransitioning) { void (^task)(void) = ^{ [self pushViewController:viewController animated:animated]; }; [self.tasks addObject:task]; } else { self.isTransitioning = YES; [super pushViewController:viewController animated:animated]; } } } - (void) runNextTask { @synchronized(self.tasks) { if (self.tasks.count) { void (^task)(void) = self.tasks[0]; [self.tasks removeObjectAtIndex:0]; task(); } } } #pragma mark UINavigationControllerDelegate -(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { self.isTransitioning = NO; if ([self.customDelegate respondsToSelector:@selector(navigationController:didShowViewController:animated:)]) { [self.customDelegate navigationController:navigationController didShowViewController:viewController animated:animated]; } // black magic :) // if one of push/pop will be without animation - we should place this code to the end of runLoop to prevent bad behavior [self performSelector:@selector(runNextTask) withObject:nil afterDelay:0.0f]; } @end
      
      





すべてのコードはgithubにあります。



iOSの最近のバージョンでは、状況は少し改善されています。 iOS 7以前では、2つのボタンが同時に押されたときにアプリケーションがクラッシュしていましたが、iOS 8では3つのボタンが必要になります 。 しかし、クラッシュは依然として避けられません。



繰り返しますが、このプラクティスを適用すると、ほとんどすべてのアプリケーションを粉砕できます。 たとえば、 App Storeでも安定してクラッシュします。 Appleがなぜこれを問題と見なさず、対処しないのかは明らかではありません。 プロジェクトで同様の問題に遭遇しましたか?どのように解決しましたか?



PSのコメントASkvortsovexclusiveTouchプロパティの使用を提案してます



All Articles