使用新浪微博开放平台api同步微博内容至自己网站(用MBProgressHUD强制给界面设置一个“遮罩”(组图) )
优采云 发布时间: 2022-03-30 18:02使用新浪微博开放平台api同步微博内容至自己网站(用MBProgressHUD强制给界面设置一个“遮罩”(组图)
)
特征
在开发快易博的时候,有个叫“分享心情”的功能【见下图】。它的主要功能是:用户可以一次发布一条微博(在人人网称为新事物)到用户选择的所有绑定平台(其实就是俗称的微博同步)。
进入后:
这个功能实现起来并不难。说白了就是依次调用各个开放平台的“发布”相关API就够了。但是,为用户提供更好的“用户体验”涉及到几个要求,所以不得不使用多线程。要求如下:
(1)在图2对应的功能界面上,发布完成后用户应该可以立即关闭;
(2)在各个平台的独立发布界面中,发布完成后自动关闭发布界面,并显示打开发布界面前的界面。
注意:分享心情界面实际上实现了一个*敏*感*词*的发布界面,可以同步推送,也可以选择你要推送的平台。为了给用户提供方便,点击各个平台(如新浪微博)后,都有单独的发布界面发布到平台,提供更*敏*感*词*,如“@、##”等,见下图。
原创处理
针对以上需求(1),我原来的做法是使用MBProgressHUD在当前界面强制加一个“遮罩”,让用户点击右上角的发帖按钮后,后退按钮不可点击。目的是停留在当前界面等待各种开放平台提供的“发布成功”、“发布失败”等回调协议的执行,并在回调协议中通过提示信息提示用户是否成功在状态栏中。
如果返回按钮是可点击的,用户可能会选择发布完成后立即返回,那么当前的 UIViewController 可能已经被释放,回调时应用会崩溃。当然,如果使用MBProgressHUD持有主界面,用户只能等待,而回调协议执行的时机其实取决于很多不确定因素(比如当前的网速,如果有图的话,也要看图片的大小),以及各个开放平台对API调用的响应速度等),可能很快,也可能要等几十秒。一旦你测试了用户的耐心,你就是在为自己埋下一颗定时*敏*感*词*。
对于需求(2),关闭速度比需求(1))要快。我需要让用户在发布完成后立即关闭发布界面。所以当前的UIViewController等不及了对于回调协议的调用,根本无法释放,无法实现状态栏提示,当然折衷的方法是使用MBProgressHUD显示“请稍候……”,强制界面无法响应用户,并选择在回调完成时关闭。
从上面的分析可以看出当前的处理方式,希望回调协议与当前发布界面的UIViewController对象的生命周期无关。那么这些“发布”操作只能在主线程(UI线程)上执行,也实现了发布操作与发布函数接口的解耦。
设计和代码实现
在 iOS 中有几种实现多线程的方法。这里使用了 NSOperation 和 NSOperationQueue。
UML图:
大致代码如下:
发布操作:
#import
#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
新浪微博发布运营
#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]initWithA*敏*感*词*ey:kWBSDKDemoA*敏*感*词*ey
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];
}
人人发布操作
#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];
}
腾讯微博发布运营
#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方法中代码的主要目的是保存当前的RunLoop(一般来说就是阻塞当前的执行上下文,直到回调协议执行完毕)。可以看出还是存在回调协议无法执行的问题。为什么会发生这种情况,为什么要保留执行上下文?因为各个平台的API通常都是异步发送请求的(腾讯微博除外),等到有响应后再执行实现的协议。所谓异步,其实是操作系统在后台用另一个线程来处理的。NSOperation的执行方式是在执行完main方法中实现的逻辑后退出,返回到主线程。所以通常情况下,这里的回调协议仍然没有 t 获得执行的机会(实际上获得了机会,但是当前的NSOperation对象已经被释放,导致内存访问错误)。所以这里用了一个while循环来保存当前的主runloop,直到各个平台的异步回调协议完成,然后设置completed或者cancelAfterError为YES退出while循环。请注意,这两个属性是异步设置的,即由另一个线程设置。这里解释一下[super main]之后;在每个平台上调用方法,如果您不在回调协议中(或在另一个线程中),则无法设置这两个属性。因为[超级主];这句话阻塞了当前执行 NSOperation 的线程。所以这里用了一个while循环来保存当前的主runloop,直到各个平台的异步回调协议完成,然后设置completed或者cancelAfterError为YES退出while循环。请注意,这两个属性是异步设置的,即由另一个线程设置。这里解释一下[super main]之后;在每个平台上调用方法,如果您不在回调协议中(或在另一个线程中),则无法设置这两个属性。因为[超级主];这句话阻塞了当前执行 NSOperation 的线程。所以这里用了一个while循环来保存当前的主runloop,直到各个平台的异步回调协议完成,然后设置completed或者cancelAfterError为YES退出while循环。请注意,这两个属性是异步设置的,即由另一个线程设置。这里解释一下[super main]之后;在每个平台上调用方法,如果您不在回调协议中(或在另一个线程中),则无法设置这两个属性。因为[超级主];这句话阻塞了当前执行 NSOperation 的线程。这里解释一下[super main]之后;在每个平台上调用方法,如果您不在回调协议中(或在另一个线程中),则无法设置这两个属性。因为[超级主];这句话阻塞了当前执行 NSOperation 的线程。这里解释一下[super main]之后;在每个平台上调用方法,如果您不在回调协议中(或在另一个线程中),则无法设置这两个属性。因为[超级主];这句话阻塞了当前执行 NSOperation 的线程。
顺便吐槽一下腾讯微博开放平台。在他们开放的框架中,发出的请求都是同步获取的,并且没有定义任何回调协议,这也让我很困扰。所以腾讯微博运营中的实现会与新浪和人人不同。因为调用是同步的,所以在腾讯微博PublishOperation结束时直接调用[super main]是没有用的(因为会在回调协议之后执行,所以这里不需要调用),这里调用的是线程的Sleep方法, 让线程休眠 5 秒,也就是将当前的 NSOperation 上下文延迟 5 秒。延迟是因为在状态栏中显示提示信息需要2秒。如果没有延迟,那么完成后设置为YES,NSOperation会被执行并退出,
/*
*状态栏显示完成提示信息
*/
-(void)showOverlayMsg:(NSString*)msg andDuration:(NSTimeInterval)duration andOverlay:(MTStatusBarOverlay*)overlay{
[overlay postImmediateFinishMessage:msg
duration:duration animated:YES];
}
你在应用的AppDelegate中定义一个NSOperationQueue来执行NSOperation,可以完成发布操作与发布功能接口的Controller的解耦。这样,Controller 可以在发布完成后直接关闭和发布,并且由于对功能的封装,成为一个独立的、可复用的整体。在“Share Mood”功能界面中,如果同步推送多个平台,只需要将各个平台的“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];
}