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" #import "FFFastImageView.h"
@implementation FFFastImageView {
BOOL hasSentOnLoadStart; @interface FFFastImageView()
BOOL hasCompleted;
BOOL hasErrored; @property (nonatomic, assign) BOOL hasSentOnLoadStart;
NSDictionary* onLoadEvent; @property (nonatomic, assign) BOOL hasCompleted;
} @property (nonatomic, assign) BOOL hasErrored;
@property (nonatomic, strong) NSDictionary* onLoadEvent;
@end
@implementation FFFastImageView
- (id) init { - (id) init {
self = [super init]; self = [super init];
@ -14,53 +20,63 @@
return self; return self;
} }
- (void)setResizeMode:(RCTResizeMode)resizeMode - (void)dealloc {
{ [NSNotificationCenter.defaultCenter removeObserver:self];
}
- (void)setResizeMode:(RCTResizeMode)resizeMode {
if (_resizeMode != resizeMode) { if (_resizeMode != resizeMode) {
_resizeMode = resizeMode; _resizeMode = resizeMode;
self.contentMode = (UIViewContentMode)resizeMode; self.contentMode = (UIViewContentMode)resizeMode;
} }
} }
- (void)setOnFastImageLoadEnd:(RCTBubblingEventBlock)onFastImageLoadEnd { - (void)setOnFastImageLoadEnd:(RCTDirectEventBlock)onFastImageLoadEnd {
_onFastImageLoadEnd = onFastImageLoadEnd; _onFastImageLoadEnd = onFastImageLoadEnd;
if (hasCompleted) { if (self.hasCompleted) {
_onFastImageLoadEnd(@{}); _onFastImageLoadEnd(@{});
} }
} }
- (void)setOnFastImageLoad:(RCTBubblingEventBlock)onFastImageLoad { - (void)setOnFastImageLoad:(RCTDirectEventBlock)onFastImageLoad {
_onFastImageLoad = onFastImageLoad; _onFastImageLoad = onFastImageLoad;
if (hasCompleted) { if (self.hasCompleted) {
_onFastImageLoad(onLoadEvent); _onFastImageLoad(self.onLoadEvent);
} }
} }
- (void)setOnFastImageError:(RCTDirectEventBlock)onFastImageError { - (void)setOnFastImageError:(RCTDirectEventBlock)onFastImageError {
_onFastImageError = onFastImageError; _onFastImageError = onFastImageError;
if (hasErrored) { if (self.hasErrored) {
_onFastImageError(@{}); _onFastImageError(@{});
} }
} }
- (void)setOnFastImageLoadStart:(RCTBubblingEventBlock)onFastImageLoadStart { - (void)setOnFastImageLoadStart:(RCTDirectEventBlock)onFastImageLoadStart {
if (_source && !hasSentOnLoadStart) { if (_source && !self.hasSentOnLoadStart) {
_onFastImageLoadStart = onFastImageLoadStart; _onFastImageLoadStart = onFastImageLoadStart;
onFastImageLoadStart(@{}); onFastImageLoadStart(@{});
hasSentOnLoadStart = YES; self.hasSentOnLoadStart = YES;
} else { } else {
_onFastImageLoadStart = onFastImageLoadStart; _onFastImageLoadStart = onFastImageLoadStart;
hasSentOnLoadStart = NO; self.hasSentOnLoadStart = NO;
} }
} }
- (void)sendOnLoad:(UIImage *)image { - (void)sendOnLoad:(UIImage *)image {
onLoadEvent = @{ self.onLoadEvent = @{
@"width":[NSNumber numberWithDouble:image.size.width], @"width":[NSNumber numberWithDouble:image.size.width],
@"height":[NSNumber numberWithDouble:image.size.height] @"height":[NSNumber numberWithDouble:image.size.height]
}; };
if (_onFastImageLoad) { if (self.onFastImageLoad) {
_onFastImageLoad(onLoadEvent); 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) { if (_source != source) {
_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. // Load base64 images.
NSString* url = [_source.url absoluteString]; NSString* url = [_source.url absoluteString];
if (url && [url hasPrefix:@"data:image"]) { if (url && [url hasPrefix:@"data:image"]) {
if (_onFastImageLoadStart) { if (self.onFastImageLoadStart) {
_onFastImageLoadStart(@{}); self.onFastImageLoadStart(@{});
hasSentOnLoadStart = YES; self.hasSentOnLoadStart = YES;
} { } {
hasSentOnLoadStart = NO; self.hasSentOnLoadStart = NO;
} }
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_source.url]]; UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_source.url]];
[self setImage:image]; [self setImage:image];
if (_onFastImageProgress) { if (self.onFastImageProgress) {
_onFastImageProgress(@{ self.onFastImageProgress(@{
@"loaded": @(1), @"loaded": @(1),
@"total": @(1) @"total": @(1)
}); });
} }
hasCompleted = YES; self.hasCompleted = YES;
[self sendOnLoad:image]; [self sendOnLoad:image];
if (_onFastImageLoadEnd) { if (self.onFastImageLoadEnd) {
_onFastImageLoadEnd(@{}); self.onFastImageLoadEnd(@{});
} }
return; return;
} }
@ -100,8 +119,7 @@
}]; }];
// Set priority. // Set priority.
SDWebImageOptions options = 0; SDWebImageOptions options = SDWebImageRetryFailed;
options |= SDWebImageRetryFailed;
switch (_source.priority) { switch (_source.priority) {
case FFFPriorityLow: case FFFPriorityLow:
options |= SDWebImageLowPriority; options |= SDWebImageLowPriority;
@ -125,27 +143,27 @@
break; break;
} }
if (_onFastImageLoadStart) { if (self.onFastImageLoadStart) {
_onFastImageLoadStart(@{}); self.onFastImageLoadStart(@{});
hasSentOnLoadStart = YES; self.hasSentOnLoadStart = YES;
} { } {
hasSentOnLoadStart = NO; self.hasSentOnLoadStart = NO;
} }
hasCompleted = NO; self.hasCompleted = NO;
hasErrored = NO; self.hasErrored = NO;
// Load the new source. [self downloadImage:_source options:options];
// 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 - (void)downloadImage:(FFFastImageSource *) source options:(SDWebImageOptions) options {
// - 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" __weak typeof(self) weakSelf = self; // Always use a weak reference to self in blocks
[self sd_setImageWithURL:_source.url [self sd_setImageWithURL:_source.url
placeholderImage:nil placeholderImage:nil
options:options options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (_onFastImageProgress) { if (weakSelf.onFastImageProgress) {
_onFastImageProgress(@{ weakSelf.onFastImageProgress(@{
@"loaded": @(receivedSize), @"loaded": @(receivedSize),
@"total": @(expectedSize) @"total": @(expectedSize)
}); });
@ -155,23 +173,26 @@
SDImageCacheType cacheType, SDImageCacheType cacheType,
NSURL * _Nullable imageURL) { NSURL * _Nullable imageURL) {
if (error) { if (error) {
hasErrored = YES; weakSelf.hasErrored = YES;
if (_onFastImageError) { if (weakSelf.onFastImageError) {
_onFastImageError(@{}); weakSelf.onFastImageError(@{});
} }
if (_onFastImageLoadEnd) { if (weakSelf.onFastImageLoadEnd) {
_onFastImageLoadEnd(@{}); weakSelf.onFastImageLoadEnd(@{});
} }
} else { } else {
hasCompleted = YES; weakSelf.hasCompleted = YES;
[self sendOnLoad:image]; [weakSelf sendOnLoad:image];
if (_onFastImageLoadEnd) {
_onFastImageLoadEnd(@{}); // Alert other FFFastImageView component sharing the same URL
[NSNotificationCenter.defaultCenter postNotificationName:source.url.absoluteString object:source];
if (weakSelf.onFastImageLoadEnd) {
weakSelf.onFastImageLoadEnd(@{});
} }
} }
}]; }];
} }
}
@end @end