From 70be74432d39f8e5289192b5bb116ab47e207615 Mon Sep 17 00:00:00 2001 From: Steven Masini Date: Tue, 23 Apr 2019 10:07:45 +0800 Subject: [PATCH] fix: Fix memory leak on iOS. (#433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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] --- ios/FastImage/FFFastImageView.m | 177 ++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 78 deletions(-) diff --git a/ios/FastImage/FFFastImageView.m b/ios/FastImage/FFFastImageView.m index 5fa8d91ee..581794b7d 100644 --- a/ios/FastImage/FFFastImageView.m +++ b/ios/FastImage/FFFastImageView.m @@ -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 @@ - (id) init { 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 @@ - (void)setSource:(FFFastImageSource *)source { 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 @@ - (void)setSource:(FFFastImageSource *)source { }]; // Set priority. - SDWebImageOptions options = 0; - options |= SDWebImageRetryFailed; + SDWebImageOptions options = SDWebImageRetryFailed; switch (_source.priority) { case FFFPriorityLow: options |= SDWebImageLowPriority; @@ -125,52 +143,55 @@ - (void)setSource:(FFFastImageSource *)source { 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(@{}); + [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(@{}); } - } else { - hasCompleted = YES; - [self sendOnLoad:image]; - if (_onFastImageLoadEnd) { - _onFastImageLoadEnd(@{}); + 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