fix: Fix memory leak on iOS. (#433)

It's recommended to never retain the strong reference to self into blocks.

Blocks maintain strong references to any captured objects, including self, which means that it’s easy to end up with a strong reference cycle.

[skip release]
This commit is contained in:
Steven Masini 2019-04-23 10:07:45 +08:00 committed by Dylan Vann
parent 84e420c815
commit 70be74432d
1 changed files with 101 additions and 80 deletions

View File

@ -1,11 +1,17 @@
#import "FFFastImageView.h"
@implementation FFFastImageView {
BOOL hasSentOnLoadStart;
BOOL hasCompleted;
BOOL hasErrored;
NSDictionary* onLoadEvent;
}
@interface FFFastImageView()
@property (nonatomic, assign) BOOL hasSentOnLoadStart;
@property (nonatomic, assign) BOOL hasCompleted;
@property (nonatomic, assign) BOOL hasErrored;
@property (nonatomic, strong) NSDictionary* onLoadEvent;
@end
@implementation FFFastImageView
- (id) init {
self = [super init];
@ -14,53 +20,63 @@
return self;
}
- (void)setResizeMode:(RCTResizeMode)resizeMode
{
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (void)setResizeMode:(RCTResizeMode)resizeMode {
if (_resizeMode != resizeMode) {
_resizeMode = resizeMode;
self.contentMode = (UIViewContentMode)resizeMode;
}
}
- (void)setOnFastImageLoadEnd:(RCTBubblingEventBlock)onFastImageLoadEnd {
- (void)setOnFastImageLoadEnd:(RCTDirectEventBlock)onFastImageLoadEnd {
_onFastImageLoadEnd = onFastImageLoadEnd;
if (hasCompleted) {
if (self.hasCompleted) {
_onFastImageLoadEnd(@{});
}
}
- (void)setOnFastImageLoad:(RCTBubblingEventBlock)onFastImageLoad {
- (void)setOnFastImageLoad:(RCTDirectEventBlock)onFastImageLoad {
_onFastImageLoad = onFastImageLoad;
if (hasCompleted) {
_onFastImageLoad(onLoadEvent);
if (self.hasCompleted) {
_onFastImageLoad(self.onLoadEvent);
}
}
- (void)setOnFastImageError:(RCTDirectEventBlock)onFastImageError {
_onFastImageError = onFastImageError;
if (hasErrored) {
if (self.hasErrored) {
_onFastImageError(@{});
}
}
- (void)setOnFastImageLoadStart:(RCTBubblingEventBlock)onFastImageLoadStart {
if (_source && !hasSentOnLoadStart) {
- (void)setOnFastImageLoadStart:(RCTDirectEventBlock)onFastImageLoadStart {
if (_source && !self.hasSentOnLoadStart) {
_onFastImageLoadStart = onFastImageLoadStart;
onFastImageLoadStart(@{});
hasSentOnLoadStart = YES;
self.hasSentOnLoadStart = YES;
} else {
_onFastImageLoadStart = onFastImageLoadStart;
hasSentOnLoadStart = NO;
self.hasSentOnLoadStart = NO;
}
}
- (void)sendOnLoad:(UIImage *)image {
onLoadEvent = @{
@"width":[NSNumber numberWithDouble:image.size.width],
@"height":[NSNumber numberWithDouble:image.size.height]
};
if (_onFastImageLoad) {
_onFastImageLoad(onLoadEvent);
self.onLoadEvent = @{
@"width":[NSNumber numberWithDouble:image.size.width],
@"height":[NSNumber numberWithDouble:image.size.height]
};
if (self.onFastImageLoad) {
self.onFastImageLoad(self.onLoadEvent);
}
}
- (void)imageDidLoadObserver:(NSNotification *)notification {
FFFastImageSource *source = notification.object;
if (source != nil && source.url != nil) {
[self sd_setImageWithURL:source.url];
}
}
@ -68,28 +84,31 @@
if (_source != source) {
_source = source;
// Attach a observer to refresh other FFFastImageView instance sharing the same source
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(imageDidLoadObserver:) name:source.url.absoluteString object:nil];
// Load base64 images.
NSString* url = [_source.url absoluteString];
if (url && [url hasPrefix:@"data:image"]) {
if (_onFastImageLoadStart) {
_onFastImageLoadStart(@{});
hasSentOnLoadStart = YES;
if (self.onFastImageLoadStart) {
self.onFastImageLoadStart(@{});
self.hasSentOnLoadStart = YES;
} {
hasSentOnLoadStart = NO;
self.hasSentOnLoadStart = NO;
}
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_source.url]];
[self setImage:image];
if (_onFastImageProgress) {
_onFastImageProgress(@{
@"loaded": @(1),
@"total": @(1)
});
if (self.onFastImageProgress) {
self.onFastImageProgress(@{
@"loaded": @(1),
@"total": @(1)
});
}
hasCompleted = YES;
self.hasCompleted = YES;
[self sendOnLoad:image];
if (_onFastImageLoadEnd) {
_onFastImageLoadEnd(@{});
if (self.onFastImageLoadEnd) {
self.onFastImageLoadEnd(@{});
}
return;
}
@ -100,8 +119,7 @@
}];
// Set priority.
SDWebImageOptions options = 0;
options |= SDWebImageRetryFailed;
SDWebImageOptions options = SDWebImageRetryFailed;
switch (_source.priority) {
case FFFPriorityLow:
options |= SDWebImageLowPriority;
@ -125,53 +143,56 @@
break;
}
if (_onFastImageLoadStart) {
_onFastImageLoadStart(@{});
hasSentOnLoadStart = YES;
if (self.onFastImageLoadStart) {
self.onFastImageLoadStart(@{});
self.hasSentOnLoadStart = YES;
} {
hasSentOnLoadStart = NO;
self.hasSentOnLoadStart = NO;
}
hasCompleted = NO;
hasErrored = NO;
self.hasCompleted = NO;
self.hasErrored = NO;
// Load the new source.
// This will work for:
// - https://
// - file:///var/containers/Bundle/Application/50953EA3-CDA8-4367-A595-DE863A012336/ReactNativeFastImageExample.app/assets/src/images/fields.jpg
// - file:///var/containers/Bundle/Application/545685CB-777E-4B07-A956-2D25043BC6EE/ReactNativeFastImageExample.app/assets/src/images/plankton.gif
// - file:///Users/dylan/Library/Developer/CoreSimulator/Devices/61DC182B-3E72-4A18-8908-8A947A63A67F/data/Containers/Data/Application/AFC2A0D2-A1E5-48C1-8447-C42DA9E5299D/Documents/images/E1F1D5FC-88DB-492F-AD33-B35A045D626A.jpg"
[self sd_setImageWithURL:_source.url
placeholderImage:nil
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (_onFastImageProgress) {
_onFastImageProgress(@{
@"loaded": @(receivedSize),
@"total": @(expectedSize)
});
}
} completed:^(UIImage * _Nullable image,
NSError * _Nullable error,
SDImageCacheType cacheType,
NSURL * _Nullable imageURL) {
if (error) {
hasErrored = YES;
if (_onFastImageError) {
_onFastImageError(@{});
}
if (_onFastImageLoadEnd) {
_onFastImageLoadEnd(@{});
}
} else {
hasCompleted = YES;
[self sendOnLoad:image];
if (_onFastImageLoadEnd) {
_onFastImageLoadEnd(@{});
}
}
}];
[self downloadImage:_source options:options];
}
}
- (void)downloadImage:(FFFastImageSource *) source options:(SDWebImageOptions) options {
__weak typeof(self) weakSelf = self; // Always use a weak reference to self in blocks
[self sd_setImageWithURL:_source.url
placeholderImage:nil
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (weakSelf.onFastImageProgress) {
weakSelf.onFastImageProgress(@{
@"loaded": @(receivedSize),
@"total": @(expectedSize)
});
}
} completed:^(UIImage * _Nullable image,
NSError * _Nullable error,
SDImageCacheType cacheType,
NSURL * _Nullable imageURL) {
if (error) {
weakSelf.hasErrored = YES;
if (weakSelf.onFastImageError) {
weakSelf.onFastImageError(@{});
}
if (weakSelf.onFastImageLoadEnd) {
weakSelf.onFastImageLoadEnd(@{});
}
} else {
weakSelf.hasCompleted = YES;
[weakSelf sendOnLoad:image];
// Alert other FFFastImageView component sharing the same URL
[NSNotificationCenter.defaultCenter postNotificationName:source.url.absoluteString object:source];
if (weakSelf.onFastImageLoadEnd) {
weakSelf.onFastImageLoadEnd(@{});
}
}
}];
}
@end