功能介紹json
在開發快易博的時候,有一個功能叫作「分享心情」【見下圖】。它的主要功能是:用戶能夠一次發表一個微博(在人人網稱之爲新鮮事)到全部用戶選擇的綁定平臺(其實就是一般所說的微博同步)。
多線程
進入以後:併發
這個功能實現起來並不難,說白了就是依次調用各個開放平臺的關於「發表」相關的API就行了。但牽扯到幾個給用戶提供更好的「用戶體驗」的需求,就不得不使用多線程了,需求以下:app
(1) 在圖二對應的功能界面上,用戶在發表完成以後,應該能夠當即關閉;異步
(2) 在各個平臺的獨立發表界面,在發表完成以後,發表界面自動關閉,顯示打開發表界面以前的一個界面。工具
注:分享心情界面其實實現的是一個多功能的發表界面,能夠同步推送,也能夠選擇你想推送的平臺。爲了給用戶提供方便,在你點進各個平臺以後(好比新浪微博),它裏面有單獨的發表到該平臺的發表界面來提供更多的功能,好比「@、##」等,見下圖。oop
對於上面的需求(1),我原先的作法是,在用戶點擊右上角的發表按鈕以後。用MBProgressHUD強制給當前界面設置一個「遮罩」,以讓返回按鈕不可點擊。這樣作的目的是爲了停留在當前界面來等待執行各個開放平臺提供的諸如「發表成功」、「發表失敗」之類的回調協議,而且在回調協議裏,經過在狀態欄使用提示信息來提示用戶是否成功。post
假如讓返回按鈕可點擊,用戶有可能在發表完成以後立馬選擇返回,那麼當前的UIViewController可能已經被釋放,回調的時候就會致使應用崩潰。固然若是用MBProgressHUD hold主整個界面,用戶除了選擇等待就沒有別的選擇,而回調協議執行的時機其實取決於不少不肯定因素(好比當前的網速,若是附帶了圖片,還要看圖片的大小,以及各個開放平臺對於API調用的響應速度等等),可能很快,也可能會等個好幾十秒。而一旦你考驗用戶的耐心,那就是在給本身埋定時炸彈了。atom
對於需求(2),比需求(1)的關閉速度更快。我須要讓用戶在發表完成以後,發表界面當即關閉。因此當前UIViewController根本等不到回調協議的調用就已經釋放,狀態欄提示也沒法實現。固然,妥協的辦法仍是用MBProgressHUD顯示個「請稍後…」之類的強制讓界面沒法響應用戶,等回調完成在選擇關閉。spa
從上面的分析能夠看得出,我但願回調協議與當前發表界面的UIViewController對象的生命週期沒有關係。那麼只能讓這些「發表」操做都不在主線程(UI線程)上執行,這也就實現了發表操做與發表功能界面的解耦。
在iOS中實現多線程的方式有幾種,這裏使用NSOperation以及NSOperationQueue來實現。
UML簡圖:
大體代碼以下:
PublishOperation:
#import <Foundation/Foundation.h> #import "MTStatusBarOverlay.h" @interface PublishOperation : NSOperation < MTStatusBarOverlayDelegate > @property (nonatomic,copy) NSString* txt; @property (nonatomic,copy) NSString *publishImgPath; @property (nonatomic,assign) BOOL completed; @property (nonatomic,assign) BOOL canceledAfterError; @property (nonatomic,retain) MTStatusBarOverlay *overlay; - (void)clearPublishedImage; - (id)initWithOperateParams:(NSString*)content; @end
#import "PublishOperation.h" @implementation PublishOperation @synthesize completed; @synthesize canceledAfterError; @synthesize txt=_txt; @synthesize overlay=_overlay; @synthesize publishImgPath=_publishImgPath; - (void)dealloc{ [_txt release],_txt=nil; [_overlay release],_overlay=nil; [_publishImgPath release],_publishImgPath=nil; [super dealloc]; } - (id)init{ if (self=[super init]) { completed=NO; canceledAfterError=NO; [self initMTStatusBarOverlay]; } return self; } - (id)initWithOperateParams:(NSString*)content{ if (self=[self init]) { self.txt=content; } return self; } /* *實例化工具欄提示組件 */ -(void)initMTStatusBarOverlay{ _overlay = [MTStatusBarOverlay sharedInstance]; _overlay.animation = MTStatusBarOverlayAnimationFallDown; _overlay.detailViewMode = MTDetailViewModeHistory; _overlay.delegate=self; } - (void)main{ //操做既沒有完成也沒有由於發生錯誤而取消,則hold住當前run loop //防止返回主線程使得代理方法沒法執行 while (!(self.completed||self.canceledAfterError)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; DebugLog(@"running!"); } } #pragma mark - other methods - - (void)clearPublishedImage{ //清除緩存圖片 if (fileExistsAtPath(self.publishImgPath)) { removerAtItem(self.publishImgPath); } } @end
SinaWeiboPublishOperation
#import "PublishOperation.h" #import "WBEngine.h" @interface SinaWeiboPublishOperation : PublishOperation < WBEngineDelegate > @end
#import "SinaWeiboPublishOperation.h" @interface SinaWeiboPublishOperation() @property (nonatomic,retain) WBEngine *engine; @end @implementation SinaWeiboPublishOperation @synthesize engine=_engine; - (void)dealloc{ [_engine setDelegate:nil]; [_engine release],_engine=nil; [super dealloc]; } /* *business logic */ - (void)main{ self.publishImgPath=PUBLISH_IMAGEPATH_SINAWEIBO; _engine = [[WBEngine alloc]initWithAppKey:kWBSDKDemoAppKey appSecret:kWBSDKDemoAppSecret]; _engine.delegate=self; [self.engine sendWeiBoWithText:self.txt image:[UIImage imageWithContentsOfFile: PUBLISH_IMAGEPATH_SINAWEIBO]]; [self.overlay postImmediateMessage:@"新浪微博發表中..." animated:YES]; [super main]; } #pragma mark - WBEngineDelegate Methods - - (void)engine:(WBEngine *)engine requestDidSucceedWithResult:(id)result{ [self clearPublishedImage]; [GlobalInstance showOverlayMsg:@"新浪微博發表成功!" andDuration:2.0 andOverlay:self.overlay]; self.completed=YES;
[NSThread sleepForTimeInterval:5]; } - (void)engine:(WBEngine *)engine requestDidFailWithError:(NSError *)error{ [self clearPublishedImage]; [GlobalInstance showOverlayErrorMsg:@"新浪微博發表失敗!" andDuration:2.0 andOverlay:self.overlay]; self.canceledAfterError=YES;
[NSThread sleepForTimeInterval:5]; }
RenRenPublishOperation
#import "PublishOperation.h" @interface RenRenPublishOperation : PublishOperation < RenrenDelegate > @end
#import "RenRenPublishOperation.h" @implementation RenRenPublishOperation /* *business logic */ - (void)main{ self.publishImgPath=PUBLISH_IMAGEPATH_RENREN; BOOL hasImage=[[NSFileManager defaultManager]fileExistsAtPath:PUBLISH_IMAGEPATH_RENREN]; if (hasImage) { ROPublishPhotoRequestParam *photoParams=[[[ROPublishPhotoRequestParam alloc]init]autorelease]; photoParams.imageFile=[UIImage imageWithContentsOfFile:PUBLISH_IMAGEPATH_RENREN]; photoParams.caption=self.txt; [[Renren sharedRenren] publishPhoto:photoParams andDelegate:self]; [self.overlay postImmediateMessage:@"人人圖片上傳中..." animated:YES]; }else { NSMutableDictionary *params=[NSMutableDictionary dictionaryWithCapacity:10]; [params setObject:@"status.set" forKey:@"method"]; [params setObject:self.txt forKey:@"status"]; [[Renren sharedRenren]requestWithParams:params andDelegate:self]; [self.overlay postImmediateMessage:@"人人狀態發表中..." animated:YES]; } [super main]; } #pragma mark - RenrenDelegate (ren ren) - -(void)renren:(Renren *)renren requestDidReturnResponse:(ROResponse *)response{ [self clearPublishedImage]; [GlobalInstance showOverlayMsg:@"人人新鮮事發表成功!" andDuration:2.0 andOverlay:self.overlay]; self.completed=YES;
[NSThread sleepForTimeInterval:5];
}
TencentWeiboPublishOperation
#import "PublishOperation.h" #import "TencentWeiboDelegate.h" @interface TencentWeiboPublishOperation : PublishOperation < TencentWeiboDelegate > @end
#import "TencentWeiboPublishOperation.h" #import "TencentWeiboManager.h" #import "OpenApi.h" @implementation TencentWeiboPublishOperation /* *business logic */ - (void)main{ [self.overlay postImmediateMessage:@"騰訊微博發表中..." animated:YES]; self.publishImgPath=PUBLISH_IMAGEPATH_TENCENTWEIBO; BOOL hasImage=[[NSFileManager defaultManager]fileExistsAtPath:PUBLISH_IMAGEPATH_TENCENTWEIBO]; OpenApi *myApi=[TencentWeiboManager getOpenApi]; myApi.delegate=self; if (!hasImage) { [myApi publishWeibo:self.txt jing:@"" wei:@"" format:@"json" clientip:@"127.0.0.1" syncflag:@"0"]; }else{ [myApi publishWeiboWithImageAndContent:self.txt jing:@"" wei:@"" format:@"json" clientip:@"127.0.0.1" syncflag:@"0"]; } } #pragma mark - tencent weibo delegate - - (void)tencentWeiboPublishedSuccessfully{ [self clearPublishedImage]; [GlobalInstance showOverlayMsg:@"騰訊微博發表成功!" andDuration:2.0 andOverlay:self.overlay]; [NSThread sleepForTimeInterval:5]; } - (void)tencentWeiboRequestFailWithError{ [self clearPublishedImage]; [GlobalInstance showOverlayErrorMsg:@"騰訊微博發表失敗!" andDuration:2.0 andOverlay:self.overlay]; [NSThread sleepForTimeInterval:5]; } @end
註解:NSOperation在非併發的執行方式下,只須要從新實現它的main方法便可。
在PublishOperation的main方法中的那段代碼的主要目的hold主當前的RunLoop(通俗一點講其實就是阻塞當前執行的上下文,直到回調協議被執行),因而可知這裏仍然存在回調協議沒法執行的問題。爲何會出現這種狀況,爲何要hold執行的上下文?由於各個平臺的API一般都是異步發送請求的(騰訊微博除外),等到有響應了再來執行實現了的協議。而所謂的異步,其實也是操做系統在後臺用另外一個線程來處理的。而NSOperation的執行模式是它執行完實如今main方法裏的邏輯就會退出,返回主線程。因此一般狀況下這裏的回調協議仍是沒有獲得機會執行(機會實際上是獲得了,只是當前的NSOperation對象已被釋放,致使內存訪問錯誤)。因此這裏用一個while循環hold主當前的runloop,一直保持到各個平臺的異步回調完協議以後,設置completed或者canceledAfterError爲YES,以退出while循環。注意這兩個屬性是異步設置的,也就是說是另外一個線程來設置的。這裏說明一下,在各個平臺調用了[super main];方法以後,其實你不在回調協議中(或者說在另外一個線程中)是沒法設置這兩個屬性的。由於[super main];這句讓當前執行NSOperation的線程阻塞了。
我順便吐槽一下騰訊微博開放平臺,它們開放的Framework中,發出的請求竟然是同步獲取結果的,並且它們竟然都沒有定義任何的回調協議,這裏也讓我很費神。因此在TencentWeiboOperation中的實現會跟新浪、人人有所不一樣。由於調用是同步的,因此在TencentWeiboPublishOperation最後直接調用[super main];是沒用的(由於它會在回調協議以後纔會執行,因此這邊不須要調用它) ,這邊調用線程的Sleep方法,讓線程睡眠5秒鐘,也就是延遲當前的NSOperation上下文5秒,延遲是由於在狀態欄顯示提示信息須要2秒,若是不延遲,那麼在completed設置爲YES以後NSOperation就會執行完成並退出,那MTStatusOverlayDelegate也沒有機會執行。
/* *狀態欄顯示完成提示信息 */ -(void)showOverlayMsg:(NSString*)msg andDuration:(NSTimeInterval)duration andOverlay:(MTStatusBarOverlay*)overlay{ [overlay postImmediateFinishMessage:msg duration:duration animated:YES]; }
你在應用程序的AppDelegate中定義一個NSOperationQueue,用來執行NSOperation,就能夠完成對發表操做與發表功能界面的Controller解耦。這樣就能夠在發表完成以後,直接關閉並釋放Controller,而且因爲功能的封裝,它變成一個獨立的、可複用的總體。在「分享心情」功能界面裏,多平臺同步推送的話,你只須要把各個平臺的「PublishOperation」丟到NSOperationQueue去,其餘就不用管,直接關閉或者返回便可。
新浪微博單獨發表界面示例代碼:
- (void)publishBtn_handle:(id)sender{ NSString *txt=self.publishTxtView.text; PublishOperation *publishOperation=[[[SinaWeiboPublishOperation alloc]initWithOperateParams:txt]autorelease]; [((FastEasyBlogAppDelegate*)appDelegateObj).operationQueue addOperation:publishOperation]; [self dismissModalViewControllerAnimated:YES]; }
分享心情發表界面示例代碼:
/* *發送 */ -(void)publish_click{ //移除高亮效果 UIButton *btn=(UIButton*)self.navigationItem.rightBarButtonItem.customView; [btn setBackgroundImage:[UIImage imageNamed:@"publishBtn.png"] forState:UIControlStateNormal]; if ([self.publishTxtView.text length]==0) { [GlobalInstance showMessageBoxWithMessage:@"請說點什麼吧!"]; return; } NSString *weiboTextContent=self.publishTxtView.text; if (plaformSwitch.isRenRenOpen) { PublishOperation *renrenOperation=[[[RenRenPublishOperation alloc]initWithOperateParams:weiboTextContent]autorelease]; [((FastEasyBlogAppDelegate*)appDelegateObj).operationQueue addOperation:renrenOperation]; } if (plaformSwitch.isTencentWeiboOpen) { PublishOperation *tencentOperation=[[[TencentWeiboPublishOperation alloc]initWithOperateParams:weiboTextContent]autorelease]; [((FastEasyBlogAppDelegate*)appDelegateObj).operationQueue addOperation:tencentOperation]; } //清空內容 [self clearInputFromTextView]; UIButton *imgBtn=(UIButton*)[self.view viewWithTag:4321]; [imgBtn removeFromSuperview]; [self dismissModalViewControllerAnimated:YES]; }