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:
parent
84e420c815
commit
70be74432d
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue