Skip to content

Commit

Permalink
fix: Fix memory leak on iOS. (#433)
Browse files Browse the repository at this point in the history
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]
  • Loading branch information
Steven Masini authored and DylanVann committed Apr 23, 2019
1 parent 84e420c commit 70be744
Showing 1 changed file with 99 additions and 78 deletions.
177 changes: 99 additions & 78 deletions ios/FastImage/FFFastImageView.m
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -14,82 +20,95 @@ - (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];
}
}

- (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;
}
Expand All @@ -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;
Expand All @@ -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
Expand Down

0 comments on commit 70be744

Please sign in to comment.