500px技术周报009

概览

阿里云直传的相关技术,主要研究如何使用及不使用SDK的时候如何只使用接口来上传

SDK的使用

只用sdk上传代码

 id<OSSCredentialProvider> credential = [[OSSStsTokenCredentialProvider alloc] initWithAccessKeyId:accessKeyId secretKeyId:SecretKeyId securityToken:SecurityToken];
                         
 OSSClient *client = [[OSSClient alloc] initWithEndpoint:endpoint credentialProvider:credential];
 OSSPutObjectRequest * put = [OSSPutObjectRequest new];
 
 put.bucketName = bucketName;
 put.objectKey = objectKey;
 
 put.uploadingData = [NSData dataWithContentsOfFile:filePath]; // Directly upload NSData
 
 put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
     NSLog(@"%lld, %lld, %lld", bytesSent, totalByteSent, totalBytesExpectedToSend);
 };
 
 OSSTask * putTask = [client putObject:put];
 
 [putTask continueWithBlock:^id(OSSTask *task) {
     if (!task.error) {
         NSLog(@"upload object success!");
     } else {
         NSLog(@"upload object failed, error: %@" , task.error);
     }
     return nil;
 }];

有几个问题,失败后重试的次数、允许几个任务一起运行、是否允许后台上传、超时时间是多少?

#define OSSDefaultRetryCount                    3
#define OSSDefaultMaxConcurrentNum              5
#define OSSDefaultTimeoutForRequestInSecond     15
#define OSSDefaultTimeoutForResourceInSecond    7 * 24 * 60 * 60
NSString * const BACKGROUND_SESSION_IDENTIFIER = @"com.aliyun.oss.backgroundsession";

@implementation OSSClientConfiguration

- (instancetype)init {
    if (self = [super init]) {
        self.maxRetryCount = OSSDefaultRetryCount;
        self.maxConcurrentRequestCount = OSSDefaultMaxConcurrentNum;
        self.enableBackgroundTransmitService = NO;
        self.isHttpdnsEnable = YES;
        self.backgroundSesseionIdentifier = BACKGROUND_SESSION_IDENTIFIER;
        self.timeoutIntervalForRequest = OSSDefaultTimeoutForRequestInSecond;
        self.timeoutIntervalForResource = OSSDefaultTimeoutForResourceInSecond;
    }
    return self;
}

由此可以看出如果我们不设置的话,默认上传重试3次,最大并行数量是5,前台运行网络请求的超时时间15秒,后台运行的网络请求的最长时间7天,默认是不允许后台上传的

OSSClientConfiguration * conf = [OSSClientConfiguration new];
conf.maxRetryCount = 3; // 网络请求遇到异常失败后的重试次数
conf.timeoutIntervalForRequest = 30; // 前台运行网络请求超时时间
conf.timeoutIntervalForResource = 24 * 60 * 60; // 后台运行网络请求的最长时间
client = [[OSSClient alloc] initWithEndpoint:endpoint credentialProvider:credential clientConfiguration:conf];

如果同时设置了timeoutIntervalForRequest和timeoutIntervalForResource以哪个为准呢 ?

@implementation OSSNetworking

- (instancetype)initWithConfiguration:(OSSNetworkingConfiguration *)configuration {
    if (self = [super init]) {
        self.configuration = configuration;

        NSOperationQueue * operationQueue = [NSOperationQueue new];
        NSURLSessionConfiguration * dataSessionConfig = nil;
        NSURLSessionConfiguration * uploadSessionConfig = nil;

        if (configuration.enableBackgroundTransmitService) {
            uploadSessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:self.configuration.backgroundSessionIdentifier];
        } else {
            uploadSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
        dataSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];

        if (configuration.timeoutIntervalForRequest > 0) {
            uploadSessionConfig.timeoutIntervalForRequest = configuration.timeoutIntervalForRequest;
            dataSessionConfig.timeoutIntervalForRequest = configuration.timeoutIntervalForRequest;
        }
        if (configuration.timeoutIntervalForResource > 0) {
            uploadSessionConfig.timeoutIntervalForResource = configuration.timeoutIntervalForResource;
            dataSessionConfig.timeoutIntervalForResource = configuration.timeoutIntervalForResource;
        }
        dataSessionConfig.URLCache = nil;
        uploadSessionConfig.URLCache = nil;
        if (configuration.proxyHost && configuration.proxyPort) {
            // Create an NSURLSessionConfiguration that uses the proxy
            NSDictionary *proxyDict = @{
                                        @"HTTPEnable"  : [NSNumber numberWithInt:1],
                                        (NSString *)kCFStreamPropertyHTTPProxyHost  : configuration.proxyHost,
                                        (NSString *)kCFStreamPropertyHTTPProxyPort  : configuration.proxyPort,

                                        @"HTTPSEnable" : [NSNumber numberWithInt:1],
                                        (NSString *)kCFStreamPropertyHTTPSProxyHost : configuration.proxyHost,
                                        (NSString *)kCFStreamPropertyHTTPSProxyPort : configuration.proxyPort,
                                        };
            dataSessionConfig.connectionProxyDictionary = proxyDict;
            uploadSessionConfig.connectionProxyDictionary = proxyDict;
        }

        _dataSession = [NSURLSession sessionWithConfiguration:dataSessionConfig
                                                 delegate:self
                                            delegateQueue:operationQueue];
        _uploadFileSession = [NSURLSession sessionWithConfiguration:uploadSessionConfig
                                                       delegate:self
                                                  delegateQueue:operationQueue];

        self.isUsingBackgroundSession = configuration.enableBackgroundTransmitService;
        _sessionDelagateManager = [OSSSyncMutableDictionary new];

        NSOperationQueue * queue = [NSOperationQueue new];
        if (configuration.maxConcurrentRequestCount) {
            queue.maxConcurrentOperationCount = configuration.maxConcurrentRequestCount;
        }
        self.taskExecutor = [OSSExecutor executorWithOperationQueue:queue];
    }
    return self;
}

由上面可以看到如果同时设置了timeoutIntervalForRequest和timeoutIntervalForResource由timeoutIntervalForResource为准,那么OSSNetworking的dataSession和uploadFileSession有哪些区别呢?

- (void)dataTaskWithDelegate:(OSSNetworkingRequestDelegate *)requestDelegate {

    [[[[[OSSTask taskWithResult:nil] continueWithExecutor:self.taskExecutor withSuccessBlock:^id(OSSTask *task) {
        OSSLogVerbose(@"start to intercept request");
        for (id<OSSRequestInterceptor> interceptor in requestDelegate.interceptors) {
            task = [interceptor interceptRequestMessage:requestDelegate.allNeededMessage];
            if (task.error) {
                return task;
            }
        }
        return task;
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        return [requestDelegate buildInternalHttpRequest];
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        NSURLSessionDataTask * sessionTask = nil;
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 && self.configuration.timeoutIntervalForRequest > 0) {
            requestDelegate.internalRequest.timeoutInterval = self.configuration.timeoutIntervalForRequest;
        }

        if (requestDelegate.uploadingData) {
            [requestDelegate.internalRequest setHTTPBody:requestDelegate.uploadingData];
            sessionTask = [_dataSession dataTaskWithRequest:requestDelegate.internalRequest];
        } else if (requestDelegate.uploadingFileURL) {
            sessionTask = [_uploadFileSession uploadTaskWithRequest:requestDelegate.internalRequest fromFile:requestDelegate.uploadingFileURL];

            if (self.isUsingBackgroundSession) {
                requestDelegate.isBackgroundUploadFileTask = YES;
            }
        } else { // not upload request
            sessionTask = [_dataSession dataTaskWithRequest:requestDelegate.internalRequest];
        }

        requestDelegate.currentSessionTask = sessionTask;
        requestDelegate.httpRequestNotSuccessResponseBody = [NSMutableData new];
        [self.sessionDelagateManager setObject:requestDelegate forKey:@(sessionTask.taskIdentifier)];
        if (requestDelegate.isRequestCancelled) {
            return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain
                                                              code:OSSClientErrorCodeTaskCancelled
                                                          userInfo:nil]];
        }
        [sessionTask resume];
      
        return task;
    }] continueWithBlock:^id(OSSTask *task) {

        // if error occurs before created sessionTask
        if (task.error) {
            requestDelegate.completionHandler(nil, task.error);
        } else if (task.isFaulted) {
            requestDelegate.completionHandler(nil, [NSError errorWithDomain:OSSClientErrorDomain
                                                                       code:OSSClientErrorCodeExcpetionCatched
                                                                   userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", task.exception]}]);
        }
        return nil;
    }];
}

由上面可以可看到requestDelegate如果传uploadingData则会用dataSession来创建任务,如果传uploadingFileURL则会用uploadFileSession来创建任务,两者的超时时间没有区别

如果不用sdk如何上传

- (OSSTask *)interceptRequestMessage:(OSSAllRequestNeededMessage *)requestMessage {
    OSSLogVerbose(@"signing intercepting - ");
    NSError * error = nil;

    /****************************************************************
    * define a constant array to contain all specified subresource */
    static NSArray * OSSSubResourceARRAY = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        OSSSubResourceARRAY = @[@"acl", @"uploadId", @"partNumber", @"uploads", @"logging", @"website", @"location",
                                @"lifecycle", @"referer", @"cors", @"delete", @"append", @"position", @"security-token", @"x-oss-process"];
    });
    /****************************************************************/

    /* initial each part of content to sign */
    NSString * method = requestMessage.httpMethod;
    NSString * contentType = @"";
    NSString * contentMd5 = @"";
    NSString * date = requestMessage.date;
    NSString * xossHeader = @"";
    NSString * resource = @"";

    OSSFederationToken * federationToken = nil;

    if (requestMessage.contentType) {
        contentType = requestMessage.contentType;
    }
    if (requestMessage.contentMd5) {
        contentMd5 = requestMessage.contentMd5;
    }

    /* if credential provider is a federation token provider, it need to specially handle */
    if ([self.credentialProvider isKindOfClass:[OSSFederationCredentialProvider class]]) {
        federationToken = [(OSSFederationCredentialProvider *)self.credentialProvider getToken:&error];
        if (error) {
            return [OSSTask taskWithError:error];
        }
        [requestMessage.headerParams setObject:federationToken.tToken forKey:@"x-oss-security-token"];
    } else if ([self.credentialProvider isKindOfClass:[OSSStsTokenCredentialProvider class]]) {
        federationToken = [(OSSStsTokenCredentialProvider *)self.credentialProvider getToken];
        [requestMessage.headerParams setObject:federationToken.tToken forKey:@"x-oss-security-token"];
    }

    /* construct CanonicalizedOSSHeaders */
    if (requestMessage.headerParams) {
        NSMutableArray * params = [[NSMutableArray alloc] init];
        NSArray * sortedKey = [[requestMessage.headerParams allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            return [obj1 compare:obj2];
        }];
        for (NSString * key in sortedKey) {
            if ([key hasPrefix:@"x-oss-"]) {
                [params addObject:[NSString stringWithFormat:@"%@:%@", key, [requestMessage.headerParams objectForKey:key]]];
            }
        }
        if ([params count]) {
            xossHeader = [NSString stringWithFormat:@"%@\n", [params componentsJoinedByString:@"\n"]];
        }
    }

    /* construct CanonicalizedResource */
    resource = @"/";
    if (requestMessage.bucketName) {
        resource = [NSString stringWithFormat:@"/%@/", requestMessage.bucketName];
    }
    if (requestMessage.objectKey) {
        resource = [resource oss_stringByAppendingPathComponentForURL:requestMessage.objectKey];
    }
    if (requestMessage.querys) {
        NSMutableArray * querys = [[NSMutableArray alloc] init];
        NSArray * sortedKey = [[requestMessage.querys allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            return [obj1 compare:obj2];
        }];
        for (NSString * key in sortedKey) {
            NSString * value = [requestMessage.querys objectForKey:key];

            if (![OSSSubResourceARRAY containsObject:key]) { // notice it's based on content compare
                continue;
            }

            if ([value isEqualToString:@""]) {
                [querys addObject:[NSString stringWithFormat:@"%@", key]];
            } else {
                [querys addObject:[NSString stringWithFormat:@"%@=%@", key, value]];
            }
        }
        if ([querys count]) {
            resource = [resource stringByAppendingString:[NSString stringWithFormat:@"?%@",[querys componentsJoinedByString:@"&"]]];
        }
    }

    /* now, join every part of content and sign */
    NSString * stringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n%@%@", method, contentMd5, contentType, date, xossHeader, resource];
    OSSLogDebug(@"string to sign: %@", stringToSign);
    if ([self.credentialProvider isKindOfClass:[OSSFederationCredentialProvider class]]
        || [self.credentialProvider isKindOfClass:[OSSStsTokenCredentialProvider class]]) {

        NSString * signature = [OSSUtil sign:stringToSign withToken:federationToken];
        [requestMessage.headerParams setObject:signature forKey:@"Authorization"];
    } else { // now we only have two type of credential provider
        NSString * signature = [self.credentialProvider sign:stringToSign error:&error];
        if (error) {
            return [OSSTask taskWithError:error];
        }
        [requestMessage.headerParams setObject:signature forKey:@"Authorization"];
    }
    return [OSSTask taskWithResult:nil];
}

上面主要添加了两个header,一个是Authorization 一个x-oss-security-token,修改完成如下

{
    Authorization = "OSS STS.Jp5kxdKfCsUpnDLDpp76xzQd:LK1IHcoF4kSi+GMQLkWLvIFtYY=";
    "User-Agent" = "aliyun-sdk-ios/2.7.1(/iOS/11.2/en_US)";
    "x-oss-security-token" = "CAIS/QF1q6Ft5B2yfSjIq7OAIMLQrlixJebbGL9oHAlO90al7/6hjz2IHxKf3BrB+8avvsym2pW6PoflqpyTIQdr/BU2Ss0vPpt6gqET9fria7ctM456vCMHWyUFGSMvqv7aPn4S9XwY+qkb0u++AZ43br9c0fNPTGiKobby+QkDLItUxK/cCBNCfpPOwJms7V6D3bKMuu3OROY5Qi1BUFz6A1nkjE9u+btgO/ks0WH1gCgmrJI+divc8f9MPMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs+02c4o/GWgIPvEnWbLeLqIMxcFJjBfViQ7ZUq+PnkPJiqlFgZW4NEvsmGoABQshLylRSTGcTJiugN0XOZD+Z/CadlFagQ2Bu8CmVIbL4B5WE+2vxnJj8fnE02XTG0tJmBxrGMB16uDJ58SuI4gJZLxagvoWWZ/y5g+cgchPIsyw2wkMT+q7HCSqVQkYHHBrM4Wjrhrpp7A9iBDanyhwN2QjH1a8xiRsqDuehOIg=";
}

那么是在哪里创建的urlRequest

- (OSSTask *)buildInternalHttpRequest {

    OSSTask * validateParam = [self validateRequestParams];
    if (validateParam.error) {
        return validateParam;
    }

#define URLENCODE(a) [OSSUtil encodeURL:(a)]
    OSSLogDebug(@"start to build request");
    // build base url string
    NSString * urlString = self.allNeededMessage.endpoint;

    NSURL * endPointURL = [NSURL URLWithString:self.allNeededMessage.endpoint];
    if ([OSSUtil isOssOriginBucketHost:endPointURL.host] && self.allNeededMessage.bucketName) {
        urlString = [NSString stringWithFormat:@"%@://%@.%@", endPointURL.scheme, self.allNeededMessage.bucketName, endPointURL.host];
    }

    endPointURL = [NSURL URLWithString:urlString];
    NSString * urlHost = endPointURL.host;
    if (!self.isAccessViaProxy && [OSSUtil isOssOriginBucketHost:urlHost] && self.isHttpdnsEnable) {
        NSString * httpdnsResolvedResult = [OSSUtil getIpByHost:urlHost];
        urlString = [NSString stringWithFormat:@"%@://%@", endPointURL.scheme, httpdnsResolvedResult];
    }

    if (self.allNeededMessage.objectKey) {
        urlString = [urlString oss_stringByAppendingPathComponentForURL:URLENCODE(self.allNeededMessage.objectKey)];
    }

    // join query string
    if (self.allNeededMessage.querys) {
        NSMutableArray * querys = [[NSMutableArray alloc] init];
        for (NSString * key in [self.allNeededMessage.querys allKeys]) {
            NSString * value = [self.allNeededMessage.querys objectForKey:key];
            if (value) {
                if ([value isEqualToString:@""]) {
                    [querys addObject:URLENCODE(key)];
                } else {
                    [querys addObject:[NSString stringWithFormat:@"%@=%@", URLENCODE(key), URLENCODE(value)]];
                }
            }
        }
        if (querys && [querys count]) {
            NSString * queryString = [querys componentsJoinedByString:@"&"];
            urlString = [NSString stringWithFormat:@"%@?%@", urlString, queryString];
        }
    }
    OSSLogDebug(@"built full url: %@", urlString);

    NSString * headerHost = urlHost;
    if (![OSSUtil isOssOriginBucketHost:urlHost] && self.allNeededMessage.isHostInCnameExcludeList && self.allNeededMessage.bucketName) {
        headerHost = [NSString stringWithFormat:@"%@.%@", self.allNeededMessage.bucketName, urlHost];
    }

    // set header fields
    self.internalRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];

    // override default host
    [self.internalRequest setValue:headerHost forHTTPHeaderField:@"Host"];

    if (self.allNeededMessage.httpMethod) {
        [self.internalRequest setHTTPMethod:self.allNeededMessage.httpMethod];
    }
    if (self.allNeededMessage.contentType) {
        [self.internalRequest setValue:self.allNeededMessage.contentType forHTTPHeaderField:@"Content-Type"];
    }
    if (self.allNeededMessage.contentMd5) {
        [self.internalRequest setValue:self.allNeededMessage.contentMd5 forHTTPHeaderField:@"Content-MD5"];
    }
    if (self.allNeededMessage.date) {
        [self.internalRequest setValue:self.allNeededMessage.date forHTTPHeaderField:@"Date"];
    }
    if (self.allNeededMessage.range) {
        [self.internalRequest setValue:self.allNeededMessage.range forHTTPHeaderField:@"Range"];
    }
    if (self.allNeededMessage.headerParams) {
        for (NSString * key in [self.allNeededMessage.headerParams allKeys]) {
            [self.internalRequest setValue:[self.allNeededMessage.headerParams objectForKey:key] forHTTPHeaderField:key];
        }
    }
    OSSLogVerbose(@"buidlInternalHttpRequest -\nmethod: %@\nurl: %@\nheader: %@", self.internalRequest.HTTPMethod,
                  self.internalRequest.URL, self.internalRequest.allHTTPHeaderFields);

#undef URLENCODE//(a)
    return [OSSTask taskWithResult:nil];
}

执行完代码里的header有

    {
    Authorization = "OSS STS.GnbGTLWJ98yf8HE4JUsFsrh22:AIEHmEbi6bCiwjyrn8+VOEZxCrw=";
    "Content-Type" = "image/jpeg";
    Date = "Mon, 25 Dec 2017 02:10:52 GMT";
    Host = "vcg-community-test.oss-cn-beijing.aliyuncs.com";
    "User-Agent" = "aliyun-sdk-ios/2.7.1(/iOS/11.2/en_US)";
    "x-oss-security-token" = "CAIS/QF1q6Ft5B2yfSjIpq3XDO74upUYj7uNOm700EoAf8lfna2Z0Dz2IHxKf3BrB+8avvsym2pW6PoflqpyTIQd76wx2Cs0vPpt6gqET9fria7ctM456vCMHWyUFGSMvqv7aPn4S9XwY+qkb0u++AZ43br9c0fNPTGiKobby+QkDLItUxK/cCBNCfpPOwJms7V6D3bKMuu3OROY5Qi1BUFz6A1nkjE9u+btgO/ks0WH1gCgmrJI+divc8f9MPMBZskvD42Hu8VtbbfE3SJq7BxHybx7lqQs+02c4o/GWgIPvEnWbLeLqIMxcFJjBfViQ7ZUq+PnkPJiqlFgZW4NEvsmGoABgLcPc39M8voNAAvc+PIM8VgPR8enh+WzZfnZEZZ0FReEcXyXbP32JVjMKB3iEwexJAlJIXhH0rwZaNDN7RjG3YGKvVgYawtajaGr/CpdpxIH5HQFgxNO75/Mm8V1k2VLe3MxbnuRH1MO9SZl+t7f3orimdeNEuyY/DrX3Z/G0Gw=";
	}

url地址为 https://vcg-community-test.oss-cn-beijing.aliyuncs.com/photo/0aae728554b008552534f5950157a9882/54cf36729360435bbfcb41f0520ca0a.jpg

HTTPMethod 为PUT

后台运行

backgroundSessionConfigurationWithIdentifier 和 defaultSessionConfiguration的区别

Background Transfer Service

我做了一个小实验传送门这里的测试是下载一个pdf文档,当使用backgroundSessionConfigurationWithIdentifier的时候下载的过程中切换到后台,下班后到家网络连上wifi后自动又恢复下载成功后收到了本地推送,经历了好几个小时。而使用defaultSessionConfiguration的时候却没有这种情况。也就是说backgroundSessionConfigurationWithIdentifier可以让APP在后台长时间运行。下面来看官方描述

Use this method to initialize a configuration object suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.

用这个方法初始化一个当APP在后台运行时用来传输文件数据的链接的configuration。session可以在系统级别上有一个特殊的线程控制数据的传输,这使得iOS在当app通过home键切换到后台或者APP被系统终止的时候继续网络传输成为可能。

If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session’s background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.

如果APP被系统终止然后又被系统重启,APP能够用相同的identifier创建一个新的session去接收之前的网络传输的状态。如果是用户通过双击home键的时候通过多个APP管理界面手动杀死APP,则系统会取消这个session的后台传输。另外系统不会重启那些被用户手动杀死的APP。

后台下载或上传后完成后的调用流程

1、- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(7_0);

The app calls this method when all background transfers associated with an NSURLSession object are done, whether they finished successfully or resulted in an error. The app also calls this method if authentication is required for one or more transfers.

Use this method to reconnect any URL sessions and to update your app’s user interface. For example, you might use this method to update progress indicators or to incorporate new content into your views. After processing the events, execute the block in the completionHandler parameter so that the app can take a new snapshot of your user interface.

If a URL session finishes its work when your app is not running, the system launches your app in the background so that it can process the event. In that situation, use the provided identifier to create a new NSURLSessionConfiguration and NSURLSession object. You must configure the other options of your NSURLSessionConfiguration object in the same way that you did when you started the uploads or downloads. Upon creating and configuring the new NSURLSession object, that object calls the appropriate delegate methods to process the events.

If your app already has a session object with the specified identifier and is running or suspended, you do not need to create a new session object using this method. Suspended apps are moved into the background. As soon as the app is running again, the NSURLSession object with the identifier receives the events and processes them normally.

At launch time, the app does not call this method if there are uploads or downloads in progress but not yet finished. If you want to display the current progress of those transfers in your app’s user interface, you must recreate the session object yourself. In that situation, cache the identifier value persistently and use it to recreate your session object.

当你的后台下载完成、发生错误、或者鉴权失效的时候会调用这个方法。

在这个方法里重新连接网络或者更新你的界面,比如你需要更新进度条。你当处理完你的工作后,就执行completionHandler.

2、- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

In iOS, when a background transfer completes or requires credentials, if your app is no longer running, your app is automatically relaunched in the background, and the app’s UIApplicationDelegate is sent an application:handleEventsForBackgroundURLSession:completionHandler: message. This call contains the identifier of the session that caused your app to be launched. Your app should then store that completion handler before creating a background configuration object with the same identifier, and creating a session with that configuration. The newly created session is automatically reassociated with ongoing background activity.

When your app later receives a URLSessionDidFinishEventsForBackgroundURLSession: message, this indicates that all messages previously enqueued for this session have been delivered, and that it is now safe to invoke the previously stored completion handler or to begin any internal updates that may result in invoking the completion handler.

在iOS里,当你一个下载或上传完成或者需要认证的是活,你的APP没有在前端运行,你的APP将会在后台刷新,并且会调用APP的UIApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:。并且你要保存completion handler。 然后会调用 URLSessionDidFinishEventsForBackgroundURLSession 。这里面调用上面的completion handler。

Because the provided completion handler is part of UIKit, you must call it on your main thread. For example:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    storedHandler();
}];

后台运行的网络超时时间到底是多少

一般设置超时时间有三个地方

1、是每个NSMutableURLRequest可以通过timeoutInterval来设置

2、是通过NSURLSessionConfiguration的timeoutIntervalForRequest

3、是通过NSURLSessionConfiguration的

1和2的区别是一个是单个设置,一个是统一设置。单个请求可以设置成不同的超时,如果想统一设置必须通过timeoutIntervalForRequest。两者默认都是60s

This property determines the request timeout interval for all tasks within sessions based on this configuration. The request timeout interval controls how long (in seconds) a task should wait for additional data to arrive before giving up. The timer associated with this value is reset whenever new data arrives. When the request timer reaches the specified interval without receiving any new data, it triggers a timeout.

The default value is 60.

3和其他的区别是,3只针对backgroundSessionConfigurationWithIdentifier有效,也就是对于后台的网络请求设置1和2是没有用的,它的默认时间是7天

This property determines the resource timeout interval for all tasks within sessions based on this configuration. The resource timeout interval controls how long (in seconds) to wait for an entire resource to transfer before giving up. The resource timer starts when the request is initiated and counts until either the request completes or this timeout interval is reached, whichever comes first.

The default value is 7 days.

Background Modes

之所以说要提一下是为了我在商店备注里发现了

从APP上传照片使用了NSURLSession的Background模式支持APP后台上传,我们希望APP进入后台之后当前的上传可以继续直到上传完成,以提供给用户更好的体验,并节省流量,故此我们勾选了Background Modes的Background Fetch模式。

那么下面来讲讲 Background Modes里面的 Background Fetch

Background Fetch

后台获取干的事情就是在用户打开应用之前就有机会(没错,就是有机会)执行代码来获取数据,刷新UI。这样在用户打开APP的时候,最新的内容就已经呈现在用户眼前了,而不用当APP启动后再去加载

1、Target -> Capabilities -> Background Modes -> Background fetch (勾选)

2、[[UIApplicationsharedApplication]setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

我们看着这个方法的官方文档

  • (void)setMinimumBackgroundFetchInterval:(NSTimeInterval)minimumBackgroundFetchInterval;

This property has no effect for apps that do not have the UIBackgroundModes key with the fetch value in its Info.plist file.

The default fetch interval for apps is UIApplicationBackgroundFetchIntervalNever. Therefore, you must call this method and set a fetch interval before your app is given background execution time.

当你在没有执行1的时候,你执行这个方法并不会有作用。

默认的值为UIApplicationBackgroundFetchIntervalNever,所以i必须调用这个方法并且给定一个值。

3、- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;

Implement this method if your app supports the fetch background mode. When an opportunity arises to download data, the system calls this method to give your app a chance to download any data it needs. Your implementation of this method should download the data, prepare that data for use, and call the block in the completionHandler parameter.

When this method is called, your app has up to 30 seconds of wall-clock time to perform the download operation and call the specified completion handler block. In practice, your app should call the completion handler block as soon as possible after downloading the needed data. If you do not call the completion handler in time, your app is terminated. More importantly, the system uses the elapsed time to calculate power usage and data costs for your app’s background downloads. If your app takes a long time to call the completion handler, it may be given fewer future opportunities to fetch data in the future.

如果你的APP支持fetch background 模式的话,你必须在AppDelegate里实现这个方法,在这里面你可以下载数据,为APP展示数据做准备,然后在执行completionHandler。

当这个方法被调用,你有30秒中去下载数据,事实上你要在下载完数据尽可能快速的执行completionHandler。如果你没有执行completionHandler,你的APP将会被结束。重要的是,系统会计算你的用电量和下载需要的流量,如果你的APP需要很长的时间才能执行completionHandler,那么以后你的APP会被减少调用Background fetch

Remote-Notification

静默推送不仅在定义上和其他的推送方式不同,在推送内容上也和其他推送不同。在后台给应用的推送内容中只要满足下面的条件,该推送就是静默推送: **如果只携带content-available: 1 不携带任何badge,sound 和消息内容等参数,则可以不打扰用户的情况下进行内容更新等操作即为“Silent Remote Notifications”**

静默推送:收到推送(没有文字没有声音),不用点开通知,不用打开APP,就能执行(void)application:(UIApplication )application)userInfo didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void (^)(UIBackgroundFetchResultresult))handler,用户完全感觉不到

Written on November 17, 2017