七牛文件批量上传之自定义NSOperation

前言:

前阵子遇到七牛文件批量上传的问题,尝试了几种方案,现分享一种目前采用的方案——自定义operation。

为什么要自己实现七牛文件的批量上传

在使用七牛云存储服务的过程中,想要在手机客户端进行图片、视频等文件的上传,只需要直接引入SDK,然后使用QiniuSDK即可。对于iOS端的上传特别简单,只需要使用pod引入SDK:

pod "Qiniu", "~> 7.0"

然后

#import <QiniuSDK.h>
...
NSString *token = @"从服务端SDK获取";
QNUploadManager *upManager = [[QNUploadManager alloc] init];
NSData *data = [@"Hello, World!" dataUsingEncoding : NSUTF8StringEncoding];
[upManager putData:data key:@"hello" token:token
complete: ^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
NSLog(@"%@", info);
NSLog(@"%@", resp);
} option:nil];
...

十分简单的操作就能实现。

然而这种使用方法仅适用于单个或少量的文件上传,一旦文件数量较多,就会出现网络资源竞争导致超时或者其他问题,使文件上传失败。

这是由于这种方式会直接创建一个上传任务,同时开启的任务会同时执行。

如何实现七牛文件批量上传

直接使用七牛SDK创建上传任务的问题在于创建多少执行多少,网络资源被竞争而导致失败。

解决这个问题的关键就是控制上传任务同时执行的个数,保证网络充分利用,又没有过多的竞争。

可选方案:

  1. 手工创建一个上传任务待执行池(数组),控制执行池的任务数量

    创建文件上传任务的同时,将需要上传的文件(路径)放入一个临时数组,文件上传结束后,从数组中移除这个文件(路径)。

    设置同时上传任务最大数量,每次创建新任务之前检查数组中的文件数,没有达到最大数量时允许创建,反之不允许创建。

  2. 自定义NSOperation,将operation加入queue做并发控制

    将每个上传任务封装成一个NSOperation,直接加入NSOperationQueue实例,同时设置最大并发数。

  3. GCD中的dispatch_semaphore_t来控制任务执行数量。

    设置信号量阈值为同时执行的上传任务最大数量,每创建一个任务前wait信号,每完成一个发送signal。

最优方案:

以上三种方案,都能解决批量文件上传与网络资源竞争的问题。

不过方案1需要控制数组的多线程访问(加锁),方案3也需要维护一个全局的dispatch_semaphore_t实例,同时方案1和3都不支持任务取消

方案2直接使用NSOperationQueue可以很方便的取消所有任务。处理一些用户的退出登录等动作就很容易。

方案2最优。

自定义NSOperation

  • 基本概念

    NSOperation用于iOS的多线程编程中,我们想要执行一个异步任务,可以将这个任务封装成一个NSOperation,创建这个NSOperation实例,并启动就可以达到异步执行的目的。

    NSOperation是一个抽象类,不能直接实例化。

    我们想使用NSOperation来执行具体任务,就必须创建NSOperation的子类或者使用系统预定义的两个子类,NSInvocationOperation和 NSBlockOperation。

  • 几个要点
    1. NSOperation有几个执行状态:

      官方文档

    Key Path Description
    isReady The isReady key path lets clients know when an operation is ready to execute. The ready property contains the value YES when the operation is ready to execute now or NO if there are still unfinished operations on which it is dependent./br In most cases, you do not have to manage the state of this key path yourself. If the readiness of your operations is determined by factors other than dependent operations, however—such as by some external condition in your program—you can provide your own implementation of the ready property and track your operation’s readiness yourself. It is often simpler though just to create operation objects only when your external state allows it./brIn OS X v10.6 and later, if you cancel an operation while it is waiting on the completion of one or more dependent operations, those dependencies are thereafter ignored and the value of this property is updated to reflect that it is now ready to run. This behavior gives an operation queue the chance to flush cancelled operations out of its queue more quickly.
    isExecuting The isExecuting key path lets clients know whether the operation is actively working on its assigned task. The executing property must report the value YES if the operation is working on its task or NO if it is not./br If you replace the start method of your operation object, you must also replace the executing property and generate KVO notifications when the execution state of your operation changes.
    isFinished The isFinished key path lets clients know that an operation finished its task successfully or was cancelled and is exiting. An operation object does not clear a dependency until the value at the isFinished key path changes to YES. Similarly, an operation queue does not dequeue an operation until the finished property contains the value YES. Thus, marking operations as finished is critical to keeping queues from backing up with in-progress or cancelled operations./brIf you replace the start method or your operation object, you must also replace the finished property and generate KVO notifications when the operation finishes executing or is cancelled.
    isCancelled The isCancelled key path lets clients know that the cancellation of an operation was requested. Support for cancellation is voluntary but encouraged and your own code should not have to send KVO notifications for this key path. The handling of cancellation notices in an operation is described in more detail in Responding to the Cancel Command.

NSOperation在创建后进入`isReady`状态方可开始需要执行的任务;
任务执行中进入`isExecuting`状态;
执行结束后进入`isFinished`状态,同时如果该NSOperation是在NSOperationQueue中,会从queue中移除;
任务未开始执行前,可以取消NSOperation任务,取消后进入`isCancelled`状态。

需要注意的是,已经cancel掉的NSOperation是不能再执行的,所以你需要在实现自定义NSOperation时经常检查该NSOperation是否被cancel了。
  • KVO的形式改变NSOperation的属性

    KVO-Compliant Properties

    The NSOperation class is key-value coding (KVC) and key-value observing (KVO) compliant for several of its properties. As needed, you can observe these properties to control other parts of your application. To observe the properties, use the following key paths:

    isCancelled - read-only

    isAsynchronous - read-only

    isExecuting - read-only

    isFinished - read-only

    isReady - read-only

    dependencies - read-only

    queuePriority - readable and writable

    completionBlock - readable and writable

  • 子类需要覆盖的方法

    For non-concurrent operations, you typically override only one method:

    • main

    If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:

    • start

      任务开始执行,在这个方法中一般将ready状态的Operation执行任务,进入Executing状态。

    • asynchronous

      是否异步执行,YES异步,NO不是。

    • executing

      是否正在执行,YES正在执行。

    • finished

      是否已经结束,YES结束,从queue中移除。

示例

自定义NSOperation代码。

.h文件

#import <UIKit/UIKit.h>
@class QNUploadManager;

@interface WTOperation : NSOperation

@property (nonatomic, copy) NSString *wt_identifier;

+ (instancetype)operationWithUploadManager:(QNUploadManager *)uploadManager
                                  filePath:(NSString *)filePath
                                  key:(NSString *)key
                                token:(NSString *)token
                              success:(void(^)())success
                              failure:(void(^)(NSError *error))failure;
- (instancetype)initWithUploadManager:(QNUploadManager *)uploadManager
                             filePath:(NSString *)filePath
                             key:(NSString *)key
                           token:(NSString *)token
                         success:(void (^)())success
                         failure:(void (^)(NSError *))failure;

@end

.m文件

#import "WTOperation.h"
#import <QiniuSDK.h>

static NSString * const WTOperationLockName = @"WTOperationLockName";

@interface WTOperation ()

@property (nonatomic, strong) QNUploadManager *uploadManager;
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *token;
@property (nonatomic, copy) void (^success)();
@property (nonatomic, copy) void (^failure)(NSError *error);

@property (nonatomic, strong) NSRecursiveLock *lock;
@property (nonatomic, copy) NSArray *runLoopModes;

@end

@implementation WTOperation

@synthesize executing = _executing;
@synthesize finished  = _finished;
@synthesize cancelled = _cancelled;

#pragma mark - init
+ (instancetype)operationWithUploadManager:(QNUploadManager *)uploadManager filePath:(NSString *)filePath key:(NSString *)key token:(NSString *)token success:(void (^)())success failure:(void (^)(NSError *))failure {
    WTOperation *operation = [[self alloc] initWithUploadManager:uploadManager filePath:filePath key:key token:token success:success failure:failure];
    return operation;
}

- (instancetype)initWithUploadManager:(QNUploadManager *)uploadManager filePath:(NSString *)filePath key:(NSString *)key token:(NSString *)token success:(void (^)())success failure:(void (^)(NSError *))failure {
    if (self = [super init]) {
        self.uploadManager = uploadManager;
        self.filePath = filePath;
        self.key = key;
        self.token = token;
        self.success = success;
        self.failure = failure;

        self.lock = [[NSRecursiveLock alloc] init];
        self.lock.name = WTOperationLockName;
        self.runLoopModes = @[NSRunLoopCommonModes];
    }
    return self;
}

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"WTAsyncOperation"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)operationThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

#pragma mark - operation
- (void)cancel {
    [self.lock lock];
    if (!self.isCancelled && !self.isFinished) {
        [super cancel];
        [self KVONotificationWithNotiKey:@"isCancelled" state:&_cancelled stateValue:YES];
        if (self.isExecuting) {
            [self runSelector:@selector(cancelUpload)];
        }
    }
    [self.lock unlock];
}

- (void)cancelUpload {
    self.success = nil;
    self.failure = nil;
}

- (void)start {
    [self.lock lock];
    if (self.isCancelled) {
        [self finish];
        [self.lock unlock];
        return;
    }
    if (self.isFinished || self.isExecuting) {
        [self.lock unlock];
        return;
    }
    [self runSelector:@selector(startUpload)];
    [self.lock unlock];
}

- (void)startUpload {
    if (self.isCancelled || self.isFinished || self.isExecuting) {
        return;
    }
    [self KVONotificationWithNotiKey:@"isExecuting" state:&_executing stateValue:YES];
    [self uploadFile];
}

- (void)uploadFile {
    __weak typeof(self) weakSelf = self;
    [self.uploadManager putFile:self.filePath key:self.key token:self.token complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
        if (weakSelf) {
            if (resp == nil) {
                if (weakSelf.failure) {
                    weakSelf.failure(info.error);
                }
            } else {
                if (weakSelf.success) {
                    weakSelf.success();
                }
            }
            [weakSelf finish];
        }
    } option:nil];
}

- (void)finish {
    [self.lock lock];
    if (self.isExecuting) {
        [self KVONotificationWithNotiKey:@"isExecuting" state:&_executing stateValue:NO];
    }
    [self KVONotificationWithNotiKey:@"isFinished" state:&_finished stateValue:YES];
    [self.lock unlock];
}

- (BOOL)isAsynchronous {
    return YES;
}

- (void)KVONotificationWithNotiKey:(NSString *)key state:(BOOL *)state stateValue:(BOOL)stateValue {
    [self.lock lock];
    [self willChangeValueForKey:key];
    *state = stateValue;
    [self didChangeValueForKey:key];
    [self.lock unlock];
}

- (void)runSelector:(SEL)selecotr {
    [self performSelector:selecotr onThread:[[self class] operationThread] withObject:nil waitUntilDone:NO modes:self.runLoopModes];
}

@end

完整代码在这里

参考

时间: 2024-07-29 14:21:48

七牛文件批量上传之自定义NSOperation的相关文章

jquery文件批量上传控件Uploadify3.2(java springMVC)

人比較懒  有用为主 不怎么排版了 先放上Uploadify的官网链接:http://www.uploadify.com/  -->里面能够看到PHP的演示样例,属性说明,以及控件下载地址.分flash版(免费)和html5版(收费). 官网上能够看到效果演示. 另外在网上找到了一份Uploadify3.2的中文API文档.能够在我上传的资源里面下载. 以下就是一个基于springMVC的文件上传演示样例,看起来可能比文档更直观一些··· 1.下载插件放到项目中 2.在jsp页面中引入下面JS和

带进度条的文件批量上传插件uploadify

有时项目中需要一个文件批量上传功能时,个人认为uploadify是快速简便的解决方案. 先上效果图: 一. 下载uploadify 从官网下载uploadify的Flash版本(Flash版本免费,另一版本HTML5版本需要付费) 下载地址: http://www.uploadify.com/download/ 下载后直接把文件解压,然后放在项目中 二. 在项目中使用 在页面中引入: <!--引入Jquery--> <script src="js/jquery-1.11.3.m

SNF快速开发平台3.0之--文件批量上传-统一附件管理器-在线预览文件(有互联网和没有两种)

实际上在SNF里使用附件管理是非常简单的事情,一句代码就可以搞定.但我也要在这里记录一下统一附件管理器能满足的需求. 通用的附件管理,不要重复开发,调用尽量简洁. 批量文件上传,并对每个文件大小限制,客户端无刷新 可以对已经上传的附件进行名字变更,改成更友好的名称. 可以对已经上传的文件进行删除. 并记录文件大小,上传人.时间和修改人和时间等. 可以下载附件到本地电脑. 文件的在线预览,支持不安装office软件就可以预览(不管是图片还是office文档都得支持预览) 虽然比专业的图文档管理系统

[Pulgin] 利用swfupload实现java文件批量上传

URL:http://blog.csdn.net/xuweilinjijis/article/details/8876305 之前在网上找过很多相关资料,很多所谓的批量上传都是忽悠人的,真正的批量上传指的是 用户一次性可以选择多个文件,然后上传是将所有选取的文件上传到服务器.java中真正可以实现批量上传的技术大致有两种:一种是通过flash:一种是 通过applet:不过html5也可以支持批量拖选.两种方式有各自的优缺点,其中flash对于浏览器兼容性较好,基本可以满足市面上大众浏览器,缺

利用uploadify+asp.net 实现大文件批量上传。

前言 现在网上文件上传组件随便一搜都是一大堆,不过看大家一般都在用uploadify这个来上传文件.由于项目需要,我在来试了一下.因为第一次使用,也遇到了很多问题,特此记录! ----------------------------------我是分割线---------------------------------我是分割线--------------------------------------------------- 效果图: 启用批量上传开关 大文件上传时的webconfig配置

ASP.NET MVC WebAPI实现文件批量上传

工作中学习,学习中记录~~~~~~ 最下面附上demo 任务需求:需要做一个apI接口让C#的程序调用实现批量文件的上传. 难度: 没有做过通过API上传文件的...之前做过网站前后台上传. 首先当然是百度...没人教就自己百度学习学习,大佬还是多.环境还是好. 先了解WEBAPI,然后了解如何上传文件,最后实现批量上传~~~~ 不会的按照上面步骤遨游百度....我直接上代码,也许写的不咋地,但是先记录下来后续自己继续学习~~~~~~~ 首先WEBAPI部分: WInfrom部分 最后附上dem

文件批量上传

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"></property> <!-- 配置文件的最大上传容量限制 --> <

七牛整合php上传从微信下载接口下载下来的文件

因为ios系统直接读取不了MP3格式的文件,所以从微信接口下载下来的MP3格式音频上传到七牛后要转码. Sample code: public function doMobileUploadToQiniu() {global $_GPC,$_W;$hd=$_GPC['hd'];$weid=$_W['weid'];$from_user = $_GPC['from_user'];$media_id = $_GPC['voiceServerId'];$access_key = 'xxxxxxxxxxx

七牛整合PHP上传文件

七牛支持抓取远程图片 API,用 access_key + secret_key + url 生成 access_token, 把 access_token 加在 header 里,然后向 post url 就完成上传了. Sample code: <?php /* * * @desc URL安全形式的base64编码 * @param string $str * @return string */ function urlsafe_base64_encode($str){ $find = ar