本blog除部分譯文外,所有內容均為原創,如有雷同,算我抄你:-)
Update:七牛官方SDK已經加入了對ALAsset的支援。
問題描述
七牛iOS SDK的上傳API只有兩個
objc
@interface QNUploadManager : NSObject - (void)putData:(NSData *)data key:(NSString *)key token:(NSString *)token complete:(QNUpCompletionHandler)completionHandler option:(QNUploadOption *)option; - (void)putFile:(NSString *)filePath key:(NSString *)key token:(NSString *)token complete:(QNUpCompletionHandler)completionHandler option:(QNUploadOption *)option; @end
其中putFileXXX是針對檔案上傳的,這個方法內部是依賴NSFileManager來獲取檔案資訊的
objc
NSError *error = nil; NSDictionary *fileAttr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; NSNumber *fileSizeNumber = fileAttr[NSFileSize]; UInt32 fileSize = [fileSizeNumber intValue]; NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error];
那麼問題來了,對於ALAsset,即系統相簿中的圖片或視訊,獲取到的assetURL是類似於如下形式的:
sh
assets-library://asset/asset.MOV?id=A16D4A3B-664E-4A75-90E8-37EA3F04FF2E&ext=MOV
NSFileManager無法處理,因而無法正確獲取檔案大小等資訊,更不用說上傳了。
解決方案
為便於說明,假定有ALAsset例項asset。
首先,通過asset.defaultRepresentation.size能夠獲取到對應檔案的大小。為QNUploadManager建立一個category,如下
objc
@interface QNUploadManager (ALAssetSupport) - (void)putALasset:(ALAsset *)asset key:(NSString *)key token:(NSString *)token complete:(QNUpCompletionHandler)completionHandler option:(QNUploadOption *)option; @end
具體實現如下
objc
- (void)putALasset:(ALAsset *)asset key:(NSString *)key token:(NSString *)token complete:(QNUpCompletionHandler)completionHandler option:(QNUploadOption *)option { //other code... QNResumeUpload *up = [[QNResumeUpload alloc] initWithData:nil withSize:(UInt32)asset.defaultRepresentation.size withKey:key withToken:token withCompletionHandler:complete withOption:option withModifyTime:modifyTime withRecorder:recorder withRecorderKey:recorderKey withHttpManager:[self getHttpManagerProperty]]; up.asset = asset; } }
從上面的程式碼可以看到,QNUploadManager實際上只是獲取檔案資訊,做一些預處理,而真正的上傳過程是由QNResumeUpload完成的。QNResumeUpload的初始化入參很多,需要注意的是data和size,一個簡化版的initXXX如下
objc
- (instancetype)initWithData:(NSData *)data withSize:(UInt32)size withOtherParameters:(XXX *)XXX;
其中data是檔案控制程式碼開啟後的二進位制資料,size是資料長度。
你一定已經發現我在putALassetXXX裡很弱智地在data這個入參上傳入了nil,這是有原因的。
開啟QNResumeUpload.m,搜尋data後發現,data本身只在2個獲取分塊資料的方法裡涉及到
objc
- (void)makeBlock:(NSString *)uphost offset:(UInt32)offset blockSize:(UInt32)blockSize chunkSize:(UInt32)chunkSize progress:(QNInternalProgressBlock)progressBlock complete:(QNCompleteBlock)complete; - (void)putChunk:(NSString *)uphost offset:(UInt32)offset size:(UInt32)size context:(NSString *)context progress:(QNInternalProgressBlock)progressBlock complete:(QNCompleteBlock)complete; }
只要override它們,讓它們支援ALAsset即可。
首先為QNResumeUpload新增property
objc
@class ALAsset; @interface QNResumeUpload (ALAssetSupport) @property (nonatomic, strong) ALAsset *asset; @end
在putALasset方法裡會為此屬性賦值。
接著寫一個獲取指定offset和length的data的方法
objc
- (NSData *)dataFromALAssetAtOffset:(NSInteger)offset size:(NSInteger)size { ALAssetRepresentation *rep = [self.asset defaultRepresentation]; Byte *buffer = (Byte *)malloc(size); NSUInteger buffered = [rep getBytes:buffer fromOffset:offset length:size error:nil]; return [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES]; }
最後在makeBlock和putChunk裡呼叫此方法即可,以makeBlock為例
objc
- (void)makeBlock:(NSString *)uphost offset:(UInt32)offset blockSize:(UInt32)blockSize chunkSize:(UInt32)chunkSize progress:(QNInternalProgressBlock)progressBlock complete:(QNCompleteBlock)complete { NSData *data = [self dataFromALAssetAtOffset:offset size:chunkSize];//[self.data subdataWithRange:NSMakeRange(offset, (unsigned int)chunkSize)]; NSString *url = [[NSString alloc] initWithFormat:@"http://%@/mkblk/%u", uphost, (unsigned int)blockSize]; UInt32 crc = [QNCrc32 data:data]; [self setChunkCrcValue:crc]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" SEL selector = @selector(post:withData:withCompleteBlock:withProgressBlock:); #pragma clang diagnostic pop void (*typed_msgSend)(id, SEL, NSString *, NSData *, QNCompleteBlock, QNInternalProgressBlock) = (void *)objc_msgSend; typed_msgSend(self, selector, url, data, complete, progressBlock); }