iOS多线程编程之多社交平台同步推送的设计与实现

简介:

功能介绍

在开发快易博的时候,有一个功能叫做“分享心情”【见下图】。它的主要功能是:用户可以一次发表一个微博(在人人网称之为新鲜事)到所有用户选择的绑定平台(其实就是通常所说的微博同步)。
进入之后:

这个功能实现起来并不难,说白了就是依次调用各个开放平台的关于“发表”相关的API就好了。但牵扯到几个给用户提供更好的“用户体验”的需求,就不得不使用多线程了,需求如下:

(1) 在图二对应的功能界面上,用户在发表完成之后,应该可以立即关闭;

(2) 在各个平台的独立发表界面,在发表完成之后,发表界面自动关闭,显示打开发表界面之前的一个界面。

注:分享心情界面其实实现的是一个多功能的发表界面,可以同步推送,也可以选择你想推送的平台。为了给用户提供方便,在你点进各个平台之后(比如新浪微博),它里面有单独的发表到该平台的发表界面来提供更多的功能,比如“@、##”等,见下图。


原先的处理方式

对于上面的需求(1),我原先的做法是,在用户点击右上角的发表按钮之后。用MBProgressHUD强制给当前界面设置一个“遮罩”,以让返回按钮不可点击。这样做的目的是为了停留在当前界面来等待执行各个开放平台提供的诸如“发表成功”、“发表失败”之类的回调协议,并且在回调协议里,通过在状态栏使用提示信息来提示用户是否成功。

假如让返回按钮可点击,用户有可能在发表完成之后立马选择返回,那么当前的UIViewController可能已经被释放,回调的时候就会导致应用崩溃。当然如果用MBProgressHUD hold主整个界面,用户除了选择等待就没有别的选择,而回调协议执行的时机其实取决于很多不确定因素(比如当前的网速,如果附带了图片,还要看图片的大小,以及各个开放平台对于API调用的响应速度等等),可能很快,也可能会等个好几十秒。而一旦你考验用户的耐心,那就是在给自己埋定时炸弹了。

对于需求(2),比需求(1)的关闭速度更快。我需要让用户在发表完成之后,发表界面立即关闭。所以当前UIViewController根本等不到回调协议的调用就已经释放,状态栏提示也无法实现。当然,妥协的办法还是用MBProgressHUD显示个“请稍后…”之类的强制让界面无法响应用户,等回调完成在选择关闭。

现在的处理方式

从上面的分析可以看得出,我希望回调协议与当前发表界面的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];
}

最后,为自己打个广告:


这是本人的第一个“个人iOS应用”,它是一款能够帮助你同时在多个社交平台和好友沟通交流以及分享的应用。支持一键发表状态/心情/照片到所有绑定平台,快速浏览/回复/分享,以及多终端同时在线浏览好友微博与好友互动,为你提供更为便捷的社交体验。




原文发布时间为:2012-11-18


本文作者:vinoYang


本文来自云栖社区合作伙伴CSDN博客,了解相关信息可以关注CSDN博客。

目录
相关文章
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
3月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
32 0
|
4月前
|
Swift iOS开发 UED
揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【9月更文挑战第5天】本文通过具体案例介绍如何在iOS应用中使用Swift与UIKit实现自定义按钮动画,当用户点击按钮时,按钮将从圆形变为椭圆形并从蓝色渐变到绿色,释放后恢复原状。文中详细展示了代码实现过程及动画平滑过渡的技巧,帮助读者提升应用的视觉体验与特色。
75 11
|
4月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
5月前
|
安全 测试技术 调度
iOS开发-多线程编程
【8月更文挑战第12天】在iOS开发中,属性的内存管理至关重要,直接影响应用性能与稳定性。主要策略包括:`strong`(强引用),保持对象不被释放;`weak`(弱引用),不保持对象,有助于避免循环引用;`assign`(赋值),适用于基本数据类型及非指针对象类型;`copy`(复制),复制对象而非引用,确保不变性。内存管理基于引用计数,利用自动引用计数(ARC)自动管理对象生命周期。此外,需注意避免循环引用,特别是在block中。最佳实践包括理解各策略、避免不必要的强引用、及时释放不再使用的对象、注意block中的内存管理,并使用工具进行内存分析。正确管理内存能显著提升应用质量。
|
5月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。
|
5月前
|
编译器 C语言 iOS开发
iOS 16 系统键盘修复问题之确定_lock是否用于保护对_deferredTasks的多线程读写如何解决
iOS 16 系统键盘修复问题之确定_lock是否用于保护对_deferredTasks的多线程读写如何解决
|
13天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
40 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
63 1