最近做的iPhone專案中有一如下功能:
app在使用者許可後將本地Photos的照片上傳到伺服器,期間使用者可以做其他任何操作,等上傳成功後彈出一個toast通知使用者。
原先的程式碼結構是:
- 獲取照片的操作放在NSOperation的子類A中
- 獲取完照片後,逐個生成一個上傳類B(此上傳類是ASIFormDataRequest的子類),並把它新增到NSOperationQueue中。
其中operationqueue設定了最大執行數是1,但是實際測試下來發現所有的上傳都是併發的,一查程式碼,發現上傳類B居然沒有實現main,就一個init函式。初始化完之後直接startAsynchronous了,然後返回self。真是奇葩~~
於是將上傳類B修改,新增了main函式,但是執行的時候出錯:
- (void)reportFinished { if (delegate && [delegate respondsToSelector:didFinishSelector]) { [delegate performSelector:didFinishSelector withObject:self]; }---------------------->提示bad_access的錯誤 #if NS_BLOCKS_AVAILABLE if(completionBlock){ completionBlock(); } #endif if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { [queue performSelector:@selector(requestFinished:) withObject:self]; } }
檢視delegate的值,發現已經overrelease了。B在設定的時候,將delegate設定為A的例項了,A的例項怎麼會不等B的返回就結束了呢?
原來A本身是一個operation,假設執行在次執行緒 M中。B因為是繼承ASIFormDataRequest,其實也是一個NSOperation,也就是說B執行的時候也是執行在次執行緒N中的。因為B使用的是非同步執行,N必然不同於M。而A在將上傳操作結束完以後,就結束了,系統就會回收A的記憶體。這個時候在N中執行的B尚未收到響應。等到response返回的時候,A早就已經釋放了,所以就會有如上的錯誤。
怎麼解決呢?有同事是把A設定為property。這樣可以解決,但是當需要呼叫A的類很多的時候,就會比較麻煩。
其實解決的辦法很簡單,就是在A中過載isFinished方法,當確定所有的照片上傳上去後返回YES否則返回NO,這樣我們就可以控制A,避免系統“過早”的釋放。
與此同時我們發現,ASIHttpRequest的delegate響應都會路由到主執行緒:
- (void)requestFinished { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING NSLog(@"[STATUS] Request finished: %@",self); #endif if ([self error] || [self mainRequest]) { return; } if ([self isPACFileRequest]) { [self reportFinished]; } else { [self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]]; } }
stackoverflow上有關於這樣做的討論,這裡要說明的是因為B是執行在後臺,delegate是A,不需要在主執行緒響應。我們可以在B中過載上述函式,將performSelectorOnMainThread:函式去掉,直接呼叫reportFinished。
進一步考慮,iOS上獲取本地照片現在一般用ALAssetsLibrary,這個庫一般是用block去列舉,換言之獲得照片內容的操作已經是在次執行緒中操作的了。
這樣一來A也就可以不需要是NSOperation,是個一般的NSObject即可。
最近專案新增了很多“奇葩”的功能,可是參與的決定權不在自己這邊,雖然我列出了很多不應該這樣做的理由和依據。但是需求人員都以本國的使用者需求為藉口——看來公司越來越成為外資公司在華的外包公司了。這個職位也變得越來越乏味,雖然不見得能馬上跳槽,但是也學會了在“逆境”中堅強:學習產品的設計,和非開發人員的溝通,重構程式碼。覺得有句話說的真好:要想做自己想做的事,就得先做自己不想做的事。
與諸君共勉!