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"
|
||||
|
||||
@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
|
||||
|
||||
|
|
Loading…
Reference in New Issue