使用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>