使用AssetsLibrary和PhotoKit做一个简易的相片选择器

iOS8之后,苹果推出了PhotoKit,让开发者在处理相册相关的业务时,可以更加得心应手。github上的开发者针对PhotoKit做了一层很优秀的封装CTAssetsPickerController,如果只需要支持iOS8+,那么可定制程度非常高的CTAssetsPickerController是个不错的选择。

但是由于现有的业务还是需要支持iOS7,所以并不能完全舍弃使用AssetsLibrary的方式来访问相册。因此也就需要自己封装一套兼容iOS7的相册管理器。

本文涉及代码:TBVAssetsPicker

统一asset以及collection

AssetsLibrary PhotoKit
ALAssetsGroup PHAssetCollection
ALAsset PHAsset
TBVAsset TBVCollection

相片选择器最终需要向外部提供统一的标识相片的结构。同样,统一结构能让相片选择器更加优雅地实现内部逻辑。所以这里我声明了两个对应的类:TBVAssetTBVCollection,并提供一些最基本的功能。

@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

有了这些最基本的功能,在实现相册选择器时,就可以方便地对资源进行操作了。

其实对于这部分的兼容处理,主要就是对两个不同的库进行封装,使其呈现同样的外观,后续的几步大体也是围绕这个目标进行。

封装manager

由于是两个不同版本的库,并且AssetsLibrary已经在iOS9时被弃用,使用时会产生deprecated警告,所以我分别对ALAssetsLibraryPHCachingImageManager进行了封装,然后通过统一的接口TBVAssetsManagerProtocol暴露其功能。

一般相册选择器具有如下页面及对应功能(UI展示):

  • 首页
    • 相簿名
    • 相簿缩略图
    • 相簿拥有相片数
  • 预览页
    • 相片缩略图
    • 选中相片大小
  • 浏览页
    • 相片大图
    • 选中相片大小

所以我提供的接口如下:

//====================================
//              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;

实现以上接口,一般相册选择器的功能点就已经完成大半了。

TBVAssetsPickerManager

由于自定义的相册manager都遵守TBVAssetsManagerProtocolTBVAssetsPickerManager
的实现就变得相对简单,没有一大串令人厌烦的if-else。当然TBVAssetsPickerManager本身也是遵守TBVAssetsManagerProtocol的。

@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;
}

这样_realManager拿到的就是当前版本最新的相册manager了。

接口的实现

其实接口文档描述的还是非常清晰的,所以这里只是罗列了下代码,并没有针对每一步做解释,因为这些基本的操作进去头文件看看就全明白了。

- requestImageForAsset:targetSize:(CGSize)targetSize:contentMode:

这个接口主要用来获取非原图。

  • TBVCachingImageManager
    • 关于PHImageRequestOptions的deliveryMode,
      • 设置为PHImageRequestOptionsDeliveryModeOpportunistic并且synchronous为NO时,请求可能会先返回一张缩略图,然后再返回一张大图,这个可以通过获取请求回调字典中PHImageResultIsDegradedKey对应value来判别
      • PHImageRequestOptionsDeliveryModeHighQualityFormat和PHImageRequestOptionsDeliveryModeFastFormat都返回一张图片,只不过前者返回的图片的质量高于或等于请求的质量,而后者可能返回一张质量稍低的图片
  • TBVAssetsLibrary
    • 由于AssetsLibrary并没有提供获取特定尺寸的相片接口,所以这里只是返回thumbnail、aspectRatioThumbnail、fullScreenImage中尺寸和目标大小最接近的一张图片。
// 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));
    }];
}
-requestPosterImageForCollection:mediaType:
  • TBVCachingImageManager
    • 通过-fetchKeyAssetsInAssetCollection:options:获取相簿keyAssets,最多可以返回三个
    • 最终还是通过requestPosterImageForAsset:获取缩略图
    • 获取keyAssets前,需要设置options对资源进行过滤:
+ (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;
}
  • TBVAssetsLibrary
    • ALAssetsGroup有posterImage属性,直接返回相簿缩略图
    • 和PhotoKit不同,AssetLibrary通过ALAssetsGroup的setAssetsFilter方法进行过滤
[group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]];

这样设置以后,后续针对group的操作都会在过滤的结果中进行了。

// 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;
    }];
}
-requestPosterImageForAsset:

获取asset的缩略图,需要注意的一点就是:在获取缩略图的情况下,Fill比Fit获取的图片要清晰


// 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];
}
-requestFullResolutionImageForAsset:

获取原图时有一点很重要,就是尽量不要快速连续地获取原图,大图也可以列入这个范畴。连续地获取大图或者原图,设备的内存会急剧增高,甚至崩溃,这种情况通常在上传图片时比较常见。
所以在上传图片时,尽量上传一张原图后再获取下一张原图进行上传,而不是全部获取完成之后再上传。

// 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));
        }];
}
-requestSizeForAssets:

请求大小是针对的图片,所以对非图片的asset进行了过滤

// 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]);
            }]];
    
}
-requestAllCollections
// 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;
    }];
}
-requestAssetsForCollection:mediaType:
// 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;
    }];
}
-requestCameraRollCollection
// 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;
    }];
}
-requestVideoForAsset:和-requestURLAssetForAsset:

由于业务上没有视频的需求,所以这一块还没有去实现。

实现过程中的一些小坑

用ALAssetsLibrary申请访问相册权限

这一点貌似有些代码用authorizationStatus就能实现,不过
应用的时候还是发现不能触发请求权限alert,所以这里需要访问下相册的资源ask-permission-to-access-camera-roll
来触发这个请求动作:

+ (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];
}
ALAssetsLibrary请求的ALAsset被置空问题

官方文档里面有这么一句:

The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance.

可以看到,在使用ALAssetsLibrary请求回来的资源时,是不能释放对应的ALAssetsLibrary对象的。在发送多图的场合,如果不注意保持住ALAssetsLibrary对象,很容易发生后面几张图片获取不到的情况。

所以,要么在选择器返回选中的资源时,强引用对应的manager,要么这个manager由调用者传给相册选择器。

更改应用权限并切回前台

如果应用在后台时,更改了权限,当切回前台后,App会重新启动 。这里如果设置了断点,别以为是程序崩了。

不足

一个很明显的问题是使用了RAC的版本后,相册选择器滚动的性能会下降,没有以前通过回调来的顺畅。如果稍微快速一点滚动的话,CPU很容易就上100%。

可能是使用RAC的方式不是很正确造成的,后续尽可能优化这一块。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,551评论 4 58
  • 一、 PhotoKit的变量: PHAdjustmentData: When a user edits an as...
    漓汐Arthur阅读 1,569评论 1 3
  • 一致性协议 基于CAP理论,在分布式系统设计过程中,系统的可用性和数据一致性需要我们反复的去权衡,于是就产生了很多...
    EnjoyTheLife阅读 560评论 0 1
  • 很多人可能觉得,自己努力去自学了那么多知识,报这个班、听那节课,辛辛苦苦一轮学习下来,好像收获甚少,自学好像没有什...
    司马加文阅读 787评论 0 0
  • 如果要追问香港电视剧业发展的黄金时期,我想没有人会否认,是上世纪90年代。从金庸武侠小说最成功的改编黄日华版《天龙...
    齐豆豆儿阅读 1,859评论 1 4