使用AssetsLibrary和PhotoKit做一个简易的相片选择器
jlllx
8年前
<p>iOS8之后,苹果推出了PhotoKit,让开发者在处理相册相关的业务时,可以更加得心应手。github上的开发者针对PhotoKit做了一层很优秀的封装 <a href="/misc/goto?guid=4959635062730211574" rel="nofollow,noindex">CTAssetsPickerController</a> ,如果只需要支持iOS8+,那么可定制程度非常高的 <a href="/misc/goto?guid=4959635062730211574" rel="nofollow,noindex">CTAssetsPickerController</a> 是个不错的选择。</p> <p>但是由于现有的业务还是需要支持iOS7,所以并不能完全舍弃使用 AssetsLibrary 的方式来访问相册。因此也就需要自己封装一套兼容iOS7的相册管理器。</p> <h2><strong>统一asset以及collection</strong></h2> <table> <thead> <tr> <th>AssetsLibrary</th> <th>PhotoKit</th> </tr> </thead> <tbody> <tr> <td>ALAssetsGroup</td> <td>PHAssetCollection</td> </tr> <tr> <td>ALAsset</td> <td>PHAsset</td> </tr> <tr> <td>TBVAsset</td> <td>TBVCollection</td> </tr> </tbody> </table> <p>相片选择器最终需要向外部提供统一的标识相片的结构。同样,统一结构能让相片选择器更加逻辑优雅地实现内部逻辑。所以这里我声明了两个对应的类: TBVAsset 、 TBVCollection ,并提供一些最基本的功能。</p> <pre> <code class="language-objectivec">@interface TBVAsset : NSObject /** * PHAsset or ALAsset */ @property (strong, nonatomic) NSObject *asset; + (instancetype)assetWithOriginAsset:(NSObject *)asset; /** 本地标识 */ - (NSString *)assetLocalIdentifer; /** 源照片尺寸 */ - (CGSize)assetPixelSize; @end @interface TBVCollection : NSObject /** * ALAssetsGroup or PHAssetCollection */ @property (strong, nonatomic) NSObject *collection; + (instancetype)collectionWithOriginCollection:(NSObject *)aCollection; /** 相簿名 */ - (NSString *)collectionTitle; /** 估算的相簿相片个数 */ - (NSInteger)collectionEstimatedAssetCount; /** 精确的相簿相片个数 */ - (NSInteger)collectionAccurateAssetCountWithFetchOptions:(id)filterOptions; - (NSInteger)collectionAccurateAssetCountWithMediaType:(TBVAssetsPickerMediaType)mediaType; @end</code></pre> <p>有了这些最基本的功能,在实现相册选择器时,就可以方便地对资源进行操作了。</p> <p>其实对于这部分的兼容处理,主要就是对两个不同的库进行封装,使其呈现同样的外观,后续的几步大体也是围绕这个目标进行。</p> <h2><strong>封装manager</strong></h2> <p>由于是两个不同版本的库,并且AssetsLibrary已经在iOS9时被弃用,使用时会产生 deprecated 警告,所以我分别对 ALAssetsLibrary 和 PHCachingImageManager 进行了封装,然后通过统一的接口 TBVAssetsManagerProtocol 暴露其功能。</p> <p>一般相册选择器具有如下页面及对应功能(UI展示):</p> <ul> <li>首页 <ul> <li>相簿名</li> <li>相簿缩略图</li> <li>相簿拥有相片数</li> </ul> </li> <li>预览页 <ul> <li>相片缩略图</li> <li>选中相片大小</li> </ul> </li> <li>浏览页 <ul> <li>相片大图</li> <li>选中相片大小</li> </ul> </li> </ul> <p>所以我提供的接口如下:</p> <pre> <code class="language-objectivec">//==================================== // image //==================================== /** requestImage返回都是一个RACTuple,first是Image,second是是否为degraded */ /** 请求特定大小的图片 */ - (RACSignal *)requestImageForAsset:(TBVAsset *)asset targetSize:(CGSize)targetSize contentMode:(TBVAssetsPickerContentMode)contentMode; /** 请求相簿缩略图 */ - (RACSignal *)requestPosterImageForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType; /** 请求相片缩略图 */ - (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset; /** 请求相片原图 */ - (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset; //==================================== // asset / collection //==================================== /** 请求相片资源大小 */ - (RACSignal *)requestSizeForAssets:(NSArray <TBVAsset *> *)assets; /** 请求所有相簿 */ - (RACSignal *)requestAllCollections; /** 请求所有相片资源 */ - (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType; /** 请求相机胶卷相簿(针对一般业务首先进入相机胶卷的预览页) */ - (RACSignal *)requestCameraRollCollection; //==================================== // video //==================================== /** 请求AVPlayerItem */ - (RACSignal *)requestVideoForAsset:(TBVAsset *)asset; /** 请求AVURLAsset */ - (RACSignal *)requestURLAssetForAsset:(TBVAsset *)asset;</code></pre> <p>实现以上接口,一般相册选择器的功能点就已经完成大半了。</p> <h2><strong>TBVAssetsPickerManager</strong></h2> <p>由于自定义的相册manager都遵守 TBVAssetsManagerProtocol , TBVAssetsPickerManager<br> 的实现就变得相对简单,没有一大串令人厌烦的 if-else 。当然 TBVAssetsPickerManager 本身也是遵守 TBVAssetsManagerProtocol 的。</p> <pre> <code class="language-objectivec">@interface TBVAssetsPickerManager() @property (strong, nonatomic) NSObject<TBVAssetsManagerProtocol> *realManager; @property (strong, nonatomic) NSMutableArray *requestIdList; @property (assign, nonatomic) BOOL photoKitAvailable; @end #pragma mark life cycle - (instancetype)init { self = [super init]; if (self) { _photoKitAvailable = NSClassFromString(@"PHImageManager") != nil; } return self; } #pragma mark TBVAssetsManagerProtocol .... #pragma mark getter setter - (NSObject *)realManager { if (_realManager == nil) { if (self.photoKitAvailable) { _realManager = [[TBVCachingImageManager alloc] init]; } else { _realManager = [[TBVAssetsLibrary alloc] init]; } } return _realManager; }</code></pre> <p>这样 _realManager 拿到的就是当前版本最新的相册manager了。</p> <h2><strong>接口的实现</strong></h2> <p>其实接口文档描述的还是非常清晰的,所以这里只是罗列了下代码,并没有针对每一步做解释,因为这些基本的操作进去头文件看看就全明白了。</p> <p>- requestImageForAsset:targetSize:(CGSize)targetSize:contentMode:</p> <p>这个接口主要用来获取非原图。</p> <ul> <li>TBVCachingImageManager <ul> <li>关于PHImageRequestOptions的deliveryMode, <ul> <li>设置为PHImageRequestOptionsDeliveryModeOpportunistic并且synchronous为NO时,请求可能会先返回一张缩略图,然后再返回一张大图,这个可以通过获取请求回调字典中PHImageResultIsDegradedKey对应value来判别</li> <li>PHImageRequestOptionsDeliveryModeHighQualityFormat和PHImageRequestOptionsDeliveryModeFastFormat都返回一张图片,只不过前者返回的图片的质量高于或等于请求的质量,而后者可能返回一张质量稍低的图片</li> </ul> </li> </ul> </li> <li>TBVAssetsLibrary <ul> <li>由于AssetsLibrary并没有提供获取特定尺寸的相片接口,所以这里只是返回thumbnail、aspectRatioThumbnail、fullScreenImage中尺寸和目标大小最接近的一张图片。</li> </ul> </li> </ul> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestImageForAsset:(TBVAsset *)asset targetSize:(CGSize)targetSize contentMode:(TBVAssetsPickerContentMode)contentMode { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:targetSize contentMode:[self contentModeForCustomContentMode:contentMode] options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }]; } // TBVAssetsLibrary - (RACSignal *)requestImageForAsset:(TBVAsset *)aAsset targetSize:(CGSize)targetSize contentMode:(TBVAssetsPickerContentMode)contentMode { return [[[RACSignal return:aAsset.asset] deliverOn:[RACScheduler scheduler]] map:^id(ALAsset * asset) { CGImageRef resultImageRef = nil; BOOL degraded = NO; if (targetSize.width < CGImageGetWidth(asset.thumbnail) && targetSize.height < CGImageGetHeight(asset.thumbnail)) { // TBVAssetsPickerContentModeFill resultImageRef = asset.thumbnail; degraded = YES; } if (!resultImageRef) { CGImageRef aspectRatioThumbnail = asset.aspectRatioThumbnail; if (targetSize.width < CGImageGetWidth(aspectRatioThumbnail) && targetSize.height < CGImageGetHeight(aspectRatioThumbnail)) { // TBVAssetsPickerContentModeFit resultImageRef = aspectRatioThumbnail; } if (!resultImageRef) { ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation]; resultImageRef = [assetRepresentation fullScreenImage]; } } UIImage *resultImage = nil; if (resultImageRef) { resultImage = [UIImage imageWithCGImage:resultImageRef scale:BQAP_SCREEN_SCALE orientation:UIImageOrientationUp]; } return RACTuplePack(resultImage, @(degraded)); }]; }</code></pre> <p>-requestPosterImageForCollection:mediaType:</p> <ul> <li>TBVCachingImageManager <ul> <li>通过-fetchKeyAssetsInAssetCollection:options:获取相簿keyAssets,最多可以返回三个</li> <li>最终还是通过requestPosterImageForAsset:获取缩略图 <ul> <li>获取keyAssets前,需要设置options对资源进行过滤:</li> </ul> </li> </ul> </li> </ul> <pre> <code class="language-objectivec">+ (instancetype)tbv_fetchOptionsWithCustomMediaType:(TBVAssetsPickerMediaType)mediaType { NSArray *mediaTypes = [self tbv_mediaTypesWithCustonMediaType:mediaType]; if (!mediaTypes.count) return nil; PHFetchOptions *options = [[PHFetchOptions alloc] init]; NSMutableString *predicateString = [NSMutableString string]; for (NSNumber *mediaType in mediaTypes) { [predicateString appendFormat:@"mediaType = %@", mediaType]; if (![mediaType isEqual:mediaTypes.lastObject]) [predicateString appendString:@" || "]; } options.predicate = [NSPredicate predicateWithFormat:predicateString]; return options; }</code></pre> <ul> <li>TBVAssetsLibrary <ul> <li>ALAssetsGroup有posterImage属性,直接返回相簿缩略图</li> <li>和PhotoKit不同,AssetLibrary通过ALAssetsGroup的setAssetsFilter方法进行过滤</li> </ul> </li> </ul> <pre> <code class="language-objectivec">[group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]];</code></pre> <p>这样设置以后,后续针对group的操作都会在过滤的结果中进行了。</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestPosterImageForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHFetchOptions *fetchOptions = [PHFetchOptions tbv_fetchOptionsWithCustomMediaType:mediaType]; fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; PHAssetCollection *realCollection = (PHAssetCollection *)collection.collection; /* fetchKeyAssetsInAssetCollection 获取至多三张 */ PHFetchResult *result = [PHAsset fetchKeyAssetsInAssetCollection:realCollection options:fetchOptions]; if (!result.count) { [subscriber sendNext:[RACSignal empty]]; [subscriber sendCompleted]; return nil; } TBVAsset *posterAsset = [TBVAsset assetWithOriginAsset:result.firstObject]; [subscriber sendNext:[self requestPosterImageForAsset:posterAsset]]; [subscriber sendCompleted]; return nil; }] switchToLatest]; } // TBVAssetsLibrary - (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSMutableArray *assets = [NSMutableArray array]; ALAssetsGroup *group = (ALAssetsGroup *)collection.collection; [group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]]; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result) { [assets addObject:[TBVAsset assetWithOriginAsset:result]]; } else { [subscriber sendNext:assets]; [subscriber sendCompleted]; } }]; return nil; }]; }</code></pre> <p>-requestPosterImageForAsset:</p> <p>获取asset的缩略图,需要注意的一点就是:在获取缩略图的情况下,Fill比Fit获取的图片要清晰</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { CGSize posterSize = CGSizeMake(kBQPosterImageWidth * BQAP_SCREEN_SCALE, kBQPosterImageHeight * BQAP_SCREEN_SCALE); /* 在获取缩略图的情况下,Fill比Fit获取的图片要清晰 */ PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:posterSize contentMode:PHImageContentModeAspectFill options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }]; } // TBVAssetsLibrary - (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { CGSize posterSize = CGSizeMake(kBQPosterImageWidth * BQAP_SCREEN_SCALE, kBQPosterImageHeight * BQAP_SCREEN_SCALE); [subscriber sendNext:[self requestImageForAsset:asset targetSize:posterSize contentMode:TBVAssetsPickerContentModeFill]]; [subscriber sendCompleted]; return nil; }] switchToLatest]; }</code></pre> <p>-requestFullResolutionImageForAsset:</p> <p>获取原图时有一点很重要,就是尽量不要快速连续地获取原图,大图也可以列入这个范畴。连续地获取大图或者原图,设备的内存会急剧增高,甚至崩溃,这种情况通常在上传图片时比较常见。</p> <p>所以在上传图片时,尽量上传一张原图后再获取下一张原图进行上传,而不是全部获取完成之后再上传。</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { self.defaultImageRequestOptions.networkAccessAllowed = YES; PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }]; } // TBVAssetsLibrary - (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset { return [[[RACSignal return:asset.asset] deliverOn:[RACScheduler scheduler]] map:^id(ALAsset * asset) { ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation]; CGImageRef fullResolutionImage = [assetRepresentation fullResolutionImage]; UIImage *resultImage = [UIImage imageWithCGImage:fullResolutionImage scale:BQAP_SCREEN_SCALE orientation:UIImageOrientationUp]; return RACTuplePack(resultImage, @(NO)); }]; }</code></pre> <p>-requestSizeForAssets:</p> <p>请求大小是针对的图片,所以对非图片的asset进行了过滤</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestSizeForAssets:(NSArray<TBVAsset *> *)assets { RACSequence *requestSequence = [[assets.rac_sequence filter:^BOOL(TBVAsset *asset) { return ((PHAsset *)asset.asset).mediaType == PHAssetMediaTypeImage; }] map:^id(TBVAsset *asset) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { self.defaultImageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; PHImageRequestID requestId =[self.imageManager requestImageDataForAsset:(PHAsset *)asset.asset options:self.defaultImageRequestOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { [subscriber sendNext:@(imageData.length)]; [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }]; }]; return [[RACSignal zip:requestSequence] map:^id(RACTuple *value) { return [value.rac_sequence foldLeftWithStart:@0 reduce:^id(id accumulator, id value) { return @([accumulator integerValue] + [value integerValue]); }]; }]; } // TBVAssetsLibrary - (RACSignal *)requestSizeForAssets:(NSArray<TBVAsset *> *)assets { return [RACSignal return:[[[[assets.rac_sequence map:^id(TBVAsset *asset) { return asset.asset; }] filter:^BOOL(ALAsset *asset) { return [asset valueForProperty:ALAssetPropertyType] == ALAssetTypePhoto; }] map:^id(ALAsset *asset) { return @([asset defaultRepresentation].size); }] foldLeftWithStart:@(0) reduce:^id(id accumulator, id value) { return @([accumulator integerValue] + [value integerValue]); }]]; }</code></pre> <p>-requestAllCollections</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestAllCollections { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHFetchResult *smartResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil]; PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil]; NSMutableArray *collections = [NSMutableArray array]; for (PHAssetCollection *aCollection in smartResult) { TBVCollection *collection = [TBVCollection collectionWithOriginCollection:aCollection]; [collections addObject:collection]; } for (PHAssetCollection *aCollection in topLevelUserCollections) { TBVCollection *collection = [TBVCollection collectionWithOriginCollection:aCollection]; [collections addObject:collection]; } [subscriber sendNext:collections]; [subscriber sendCompleted]; return nil; }]; } // TBVAssetsLibrary - (RACSignal *)requestAllCollections { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSMutableArray *collections = [NSMutableArray array]; [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { if (group) { TBVCollection *collection = [TBVCollection collectionWithOriginCollection:group]; [collections addObject:collection]; } else { [subscriber sendNext:collections]; [subscriber sendCompleted]; } } failureBlock:^(NSError *error) { [subscriber sendError:error]; }]; return nil; }]; }</code></pre> <p>-requestAssetsForCollection:mediaType:</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHFetchOptions *options = [PHFetchOptions tbv_fetchOptionsWithCustomMediaType:mediaType]; PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:(PHAssetCollection *)collection.collection options:options]; NSMutableArray *assets = [NSMutableArray arrayWithCapacity:fetchResult.count]; for (PHAsset *asset in fetchResult) { [assets addObject:[TBVAsset assetWithOriginAsset:asset]]; } [subscriber sendNext:assets]; [subscriber sendCompleted]; return nil; }]; } // TBVAssetsLibrary - (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSMutableArray *assets = [NSMutableArray array]; ALAssetsGroup *group = (ALAssetsGroup *)collection.collection; [group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]]; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result) { [assets addObject:[TBVAsset assetWithOriginAsset:result]]; } else { [subscriber sendNext:assets]; [subscriber sendCompleted]; } }]; return nil; }]; }</code></pre> <p>-requestCameraRollCollection</p> <pre> <code class="language-objectivec">// TBVCachingImageManager - (RACSignal *)requestCameraRollCollection { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil]; [subscriber sendNext:[TBVCollection collectionWithOriginCollection:smartAlbums.firstObject]]; [subscriber sendCompleted]; return nil; }]; } // TBVAssetsLibrary - (RACSignal *)requestCameraRollCollection { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { [subscriber sendNext:[TBVCollection collectionWithOriginCollection:group]]; [subscriber sendCompleted]; } failureBlock:^(NSError *error) { [subscriber sendError:error]; }]; return nil; }]; }</code></pre> <p>-requestVideoForAsset:和-requestURLAssetForAsset:</p> <p>由于业务上没有视频的需求,所以这一块还没有去实现。</p> <h2><strong>实现过程中的一些小坑</strong></h2> <p>用ALAssetsLibrary申请访问相册权限</p> <p>这一点貌似有些代码用authorizationStatus就能实现,不过</p> <p>应用的时候还是发现不能触发请求权限alert,所以这里需要访问下相册的资源 <a href="/misc/goto?guid=4959715428370545242" rel="nofollow,noindex">ask-permission-to-access-camera-roll</a> ,</p> <p>来触发这个请求动作:</p> <pre> <code class="language-objectivec">+ (RACSignal *)tbv_forceTriggerPermissionAsking { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { void (^sendStatus)() = ^{ [subscriber sendNext:@([self tbv_authorizationStatus])]; [subscriber sendCompleted]; }; if ([self tbv_authorizationStatus] != BQAuthorizationStatusNotDetermined) { sendStatus(); return nil; } ALAssetsLibrary *lib = [[ALAssetsLibrary alloc] init]; [lib enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { sendStatus(); *stop = YES; } failureBlock:^(NSError *error) { sendStatus(); }]; return nil; }] deliverOnMainThread]; }</code></pre> <p>ALAssetsLibrary请求的ALAsset被置空问题</p> <p>官方文档里面有这么一句:</p> <pre> <code class="language-objectivec">The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance.</code></pre> <p>可以看到,在使用ALAssetsLibrary请求回来的资源时,是不能释放对应的ALAssetsLibrary对象的。在发送多图的场合,如果不注意保持住ALAssetsLibrary对象,很容易发生后面几张图片获取不到的情况。</p> <p>所以,要么在选择器返回选中的资源时,强引用对应的manager,要么这个manager由调用者传给相册选择器。</p> <p>更改应用权限并切回前台</p> <p>如果应用在后台时,更改了权限,当切回前台后,App会重新启动 。这里如果设置了断点,别以为是程序崩了。</p> <h2><strong>不足</strong></h2> <p>一个很明显的问题是使用了RAC的版本后,相册选择器滚动的性能会下降,没有以前通过回调来的顺畅。如果稍微快速一点滚动的话,CPU很容易就上100%。</p> <p>可能是使用RAC的方式不是很正确造成的,后续尽可能优化这一块。</p> <p> </p> <p>来自:http://www.jianshu.com/p/78a035ca3800</p> <p> </p>