標籤:style blog http color os io strong 資料
這篇文章是我的【iOS開發每日小筆記】系列中的一片,記錄的是今天在開發工作中遇到的,可以用很短的文章或很小的demo示範解釋出來的小心得小技巧。該分類的文章,內容涉及的知識點可能是很簡單的、或是用很短程式碼片段就能實現的,但在我看來它們可能會給使用者體驗、代碼效率得到一些提升,或是之前自己沒有接觸過的技術,很開心的學到了,放在這裡得瑟一下。其實,90%的作用是協助自己回顧、記憶、複習。如果看官覺得太easy,太片段,則可以有兩個選擇:1,移步【iOS探究】分類,對那裡的文章進行斧正;2,在本文的評論裡狠狠吐槽,再關掉頁面!感謝!
測試組的同學想盡辦法測我們的bug,盡心儘力。今天他們發現,在某些時候UIAlertView彈出後,不關閉UIAlertView直接點擊home鍵退到iOS主介面,再次進入程式,點擊剛剛的UIAlertView上的按鈕,程式crash。根據我的經驗,一看便知肯定是訊息傳給了被釋放的對象,造成的crash。
首先來解釋一下背景,目前我所做的項目,是一個幾十人同時線上的教育類項目,介面層次關係也比較冗雜。先且不談設計是否合理,為了同步重新整理狀態等原因,我們在程式退出到後台時,會刪除當前的若干介面,程式再次進入時,會從伺服器擷取當前資料和狀態,再據此重新建立若干介面。而UIAlertView本身是不會因為你退出到後台後再進入而自動消失(dismiss)的。因此問題就來了,假如我的UIAlertView的回調對象(delegate)設定的是某個將要銷毀的View,那麼點擊home鍵程式退到後台,這個View被銷毀,delegate指向的記憶體地區就是被釋放的地區,再進入程式,點擊UIAlertView上的按鈕,發送訊息就必然crash。(可以參考我的demo:在這裡https://github.com/pigpigdaddy/ClearAlertViewDemo)
1 #import "MySubView.h" 2 3 @implementation MySubView 4 5 - (id)initWithFrame:(CGRect)frame 6 { 7 self = [super initWithFrame:frame]; 8 if (self) { 9 // Initialization code10 11 self.backgroundColor = [UIColor darkGrayColor];12 13 UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];14 [alertView show];15 }16 return self;17 }18 19 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex20 {21 22 }23 24 @end
可以看到在我的自訂View中,建立了一個UIAlertView,他的代理指向了self。
而在AppDelegate中,我在每次退到後台時,將自訂View給刪除了:
1 - (void)applicationWillResignActive:(UIApplication *)application2 {3 ViewController *rootViewController = (ViewController *)self.window.rootViewController;4 [rootViewController removeSubView];5 }
重新進來後,點擊確定crash,這樣就類比了這個crash。
於是想到解決方案,1,程式退出到後台時設定delegate為nil,(請注意,OC中,向nil發送訊息,是可以的。不會crash,也什麼都不會發生);2,程式退到後台時,手動dismiss當前的UIAlertView。
嘗試了第二條,因為第二條更合理,設為nil的確可以規避掉crash,但是你的點擊只能將UIAlertView給dismiss掉,而點擊選擇的功能將失效。
我們知道iOS 7之前,可以通過UIApplication 的 windows擷取到UIAlertView所在的window,然後消除UIAlertView(具體可看這篇文章:http://blog.csdn.net/u010889390/article/details/18499691 不過很抱歉此刻我手邊沒有iOS 6 的 SDK不能去再次驗證,但是我的確在iOS5或iOS6這麼做過)。但是iOS 7開始,你無法擷取到這個windows了,因為UIAlertView展現方式做出了變化,簡而言之就是你不能通過UIApplication拿到UIAlertView了。你唯一能做的就是在建立的地方儲存一份UIAlertView執行個體的引用,然後在適合的地方再去做其他動作(如本例中的銷毀View時,手動dismiss掉UIAlertView)。
回到程式中,將我們的自訂View的做這樣的改變:
1 #import <UIKit/UIKit.h>2 3 @interface MySubView : UIView<UIAlertViewDelegate>4 5 @property (nonatomic, strong)UIAlertView *alertView;6 7 @end
1 #import "MySubView.h" 2 3 @implementation MySubView 4 5 - (id)initWithFrame:(CGRect)frame 6 { 7 self = [super initWithFrame:frame]; 8 if (self) { 9 // Initialization code10 11 self.backgroundColor = [UIColor darkGrayColor];12 13 // UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];14 // [alertView show];15 16 self.alertView = [[UIAlertView alloc] initWithTitle:@"testAlert" message:@"TEST" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];17 [self.alertView show];18 }19 return self;20 }21 22 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex23 {24 25 }26 27 @end
然後重新定義ViewController中的removeSubView方法:
1 - (void)removeSubView2 {3 MySubView *view = (MySubView *)[self.view viewWithTag:1000];4 if (view) {5 [view.alertView dismissWithClickedButtonIndex:0 animated:YES];6 [view removeFromSuperview];7 }8 }
運行後,退出到後台,再進入,此時的UIAlertView已經dismiss。不會再有使用者因為點擊了殘留的UIAlertView而Crash了。