Skip to content

Commit 70be744

Browse files
Steven MasiniDylanVann
Steven Masini
authored andcommitted
fix: Fix memory leak on iOS. (DylanVann#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]
1 parent 84e420c commit 70be744

File tree

1 file changed

+99
-78
lines changed

1 file changed

+99
-78
lines changed

ios/FastImage/FFFastImageView.m

+99-78
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
#import "FFFastImageView.h"
22

3-
@implementation FFFastImageView {
4-
BOOL hasSentOnLoadStart;
5-
BOOL hasCompleted;
6-
BOOL hasErrored;
7-
NSDictionary* onLoadEvent;
8-
}
3+
4+
@interface FFFastImageView()
5+
6+
@property (nonatomic, assign) BOOL hasSentOnLoadStart;
7+
@property (nonatomic, assign) BOOL hasCompleted;
8+
@property (nonatomic, assign) BOOL hasErrored;
9+
10+
@property (nonatomic, strong) NSDictionary* onLoadEvent;
11+
12+
@end
13+
14+
@implementation FFFastImageView
915

1016
- (id) init {
1117
self = [super init];
@@ -14,82 +20,95 @@ - (id) init {
1420
return self;
1521
}
1622

17-
- (void)setResizeMode:(RCTResizeMode)resizeMode
18-
{
23+
- (void)dealloc {
24+
[NSNotificationCenter.defaultCenter removeObserver:self];
25+
}
26+
27+
- (void)setResizeMode:(RCTResizeMode)resizeMode {
1928
if (_resizeMode != resizeMode) {
2029
_resizeMode = resizeMode;
2130
self.contentMode = (UIViewContentMode)resizeMode;
2231
}
2332
}
2433

25-
- (void)setOnFastImageLoadEnd:(RCTBubblingEventBlock)onFastImageLoadEnd {
34+
- (void)setOnFastImageLoadEnd:(RCTDirectEventBlock)onFastImageLoadEnd {
2635
_onFastImageLoadEnd = onFastImageLoadEnd;
27-
if (hasCompleted) {
36+
if (self.hasCompleted) {
2837
_onFastImageLoadEnd(@{});
2938
}
3039
}
3140

32-
- (void)setOnFastImageLoad:(RCTBubblingEventBlock)onFastImageLoad {
41+
- (void)setOnFastImageLoad:(RCTDirectEventBlock)onFastImageLoad {
3342
_onFastImageLoad = onFastImageLoad;
34-
if (hasCompleted) {
35-
_onFastImageLoad(onLoadEvent);
43+
if (self.hasCompleted) {
44+
_onFastImageLoad(self.onLoadEvent);
3645
}
3746
}
3847

3948
- (void)setOnFastImageError:(RCTDirectEventBlock)onFastImageError {
4049
_onFastImageError = onFastImageError;
41-
if (hasErrored) {
50+
if (self.hasErrored) {
4251
_onFastImageError(@{});
4352
}
4453
}
4554

46-
- (void)setOnFastImageLoadStart:(RCTBubblingEventBlock)onFastImageLoadStart {
47-
if (_source && !hasSentOnLoadStart) {
55+
- (void)setOnFastImageLoadStart:(RCTDirectEventBlock)onFastImageLoadStart {
56+
if (_source && !self.hasSentOnLoadStart) {
4857
_onFastImageLoadStart = onFastImageLoadStart;
4958
onFastImageLoadStart(@{});
50-
hasSentOnLoadStart = YES;
59+
self.hasSentOnLoadStart = YES;
5160
} else {
5261
_onFastImageLoadStart = onFastImageLoadStart;
53-
hasSentOnLoadStart = NO;
62+
self.hasSentOnLoadStart = NO;
5463
}
5564
}
5665

5766
- (void)sendOnLoad:(UIImage *)image {
58-
onLoadEvent = @{
59-
@"width":[NSNumber numberWithDouble:image.size.width],
60-
@"height":[NSNumber numberWithDouble:image.size.height]
61-
};
62-
if (_onFastImageLoad) {
63-
_onFastImageLoad(onLoadEvent);
67+
self.onLoadEvent = @{
68+
@"width":[NSNumber numberWithDouble:image.size.width],
69+
@"height":[NSNumber numberWithDouble:image.size.height]
70+
};
71+
if (self.onFastImageLoad) {
72+
self.onFastImageLoad(self.onLoadEvent);
73+
}
74+
}
75+
76+
- (void)imageDidLoadObserver:(NSNotification *)notification {
77+
FFFastImageSource *source = notification.object;
78+
if (source != nil && source.url != nil) {
79+
[self sd_setImageWithURL:source.url];
6480
}
6581
}
6682

6783
- (void)setSource:(FFFastImageSource *)source {
6884
if (_source != source) {
6985
_source = source;
7086

87+
// Attach a observer to refresh other FFFastImageView instance sharing the same source
88+
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(imageDidLoadObserver:) name:source.url.absoluteString object:nil];
89+
7190
// Load base64 images.
7291
NSString* url = [_source.url absoluteString];
7392
if (url && [url hasPrefix:@"data:image"]) {
74-
if (_onFastImageLoadStart) {
75-
_onFastImageLoadStart(@{});
76-
hasSentOnLoadStart = YES;
93+
if (self.onFastImageLoadStart) {
94+
self.onFastImageLoadStart(@{});
95+
self.hasSentOnLoadStart = YES;
7796
} {
78-
hasSentOnLoadStart = NO;
97+
self.hasSentOnLoadStart = NO;
7998
}
8099
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_source.url]];
81100
[self setImage:image];
82-
if (_onFastImageProgress) {
83-
_onFastImageProgress(@{
84-
@"loaded": @(1),
85-
@"total": @(1)
86-
});
101+
if (self.onFastImageProgress) {
102+
self.onFastImageProgress(@{
103+
@"loaded": @(1),
104+
@"total": @(1)
105+
});
87106
}
88-
hasCompleted = YES;
107+
self.hasCompleted = YES;
89108
[self sendOnLoad:image];
90109

91-
if (_onFastImageLoadEnd) {
92-
_onFastImageLoadEnd(@{});
110+
if (self.onFastImageLoadEnd) {
111+
self.onFastImageLoadEnd(@{});
93112
}
94113
return;
95114
}
@@ -100,8 +119,7 @@ - (void)setSource:(FFFastImageSource *)source {
100119
}];
101120

102121
// Set priority.
103-
SDWebImageOptions options = 0;
104-
options |= SDWebImageRetryFailed;
122+
SDWebImageOptions options = SDWebImageRetryFailed;
105123
switch (_source.priority) {
106124
case FFFPriorityLow:
107125
options |= SDWebImageLowPriority;
@@ -125,52 +143,55 @@ - (void)setSource:(FFFastImageSource *)source {
125143
break;
126144
}
127145

128-
if (_onFastImageLoadStart) {
129-
_onFastImageLoadStart(@{});
130-
hasSentOnLoadStart = YES;
146+
if (self.onFastImageLoadStart) {
147+
self.onFastImageLoadStart(@{});
148+
self.hasSentOnLoadStart = YES;
131149
} {
132-
hasSentOnLoadStart = NO;
150+
self.hasSentOnLoadStart = NO;
133151
}
134-
hasCompleted = NO;
135-
hasErrored = NO;
152+
self.hasCompleted = NO;
153+
self.hasErrored = NO;
136154

137-
// Load the new source.
138-
// This will work for:
139-
// - https://
140-
// - file:///var/containers/Bundle/Application/50953EA3-CDA8-4367-A595-DE863A012336/ReactNativeFastImageExample.app/assets/src/images/fields.jpg
141-
// - file:///var/containers/Bundle/Application/545685CB-777E-4B07-A956-2D25043BC6EE/ReactNativeFastImageExample.app/assets/src/images/plankton.gif
142-
// - 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"
143-
[self sd_setImageWithURL:_source.url
144-
placeholderImage:nil
145-
options:options
146-
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
147-
if (_onFastImageProgress) {
148-
_onFastImageProgress(@{
149-
@"loaded": @(receivedSize),
150-
@"total": @(expectedSize)
151-
});
152-
}
153-
} completed:^(UIImage * _Nullable image,
154-
NSError * _Nullable error,
155-
SDImageCacheType cacheType,
156-
NSURL * _Nullable imageURL) {
157-
if (error) {
158-
hasErrored = YES;
159-
if (_onFastImageError) {
160-
_onFastImageError(@{});
161-
}
162-
if (_onFastImageLoadEnd) {
163-
_onFastImageLoadEnd(@{});
155+
[self downloadImage:_source options:options];
156+
}
157+
}
158+
159+
- (void)downloadImage:(FFFastImageSource *) source options:(SDWebImageOptions) options {
160+
__weak typeof(self) weakSelf = self; // Always use a weak reference to self in blocks
161+
[self sd_setImageWithURL:_source.url
162+
placeholderImage:nil
163+
options:options
164+
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
165+
if (weakSelf.onFastImageProgress) {
166+
weakSelf.onFastImageProgress(@{
167+
@"loaded": @(receivedSize),
168+
@"total": @(expectedSize)
169+
});
170+
}
171+
} completed:^(UIImage * _Nullable image,
172+
NSError * _Nullable error,
173+
SDImageCacheType cacheType,
174+
NSURL * _Nullable imageURL) {
175+
if (error) {
176+
weakSelf.hasErrored = YES;
177+
if (weakSelf.onFastImageError) {
178+
weakSelf.onFastImageError(@{});
164179
}
165-
} else {
166-
hasCompleted = YES;
167-
[self sendOnLoad:image];
168-
if (_onFastImageLoadEnd) {
169-
_onFastImageLoadEnd(@{});
180+
if (weakSelf.onFastImageLoadEnd) {
181+
weakSelf.onFastImageLoadEnd(@{});
170182
}
183+
} else {
184+
weakSelf.hasCompleted = YES;
185+
[weakSelf sendOnLoad:image];
186+
187+
// Alert other FFFastImageView component sharing the same URL
188+
[NSNotificationCenter.defaultCenter postNotificationName:source.url.absoluteString object:source];
189+
190+
if (weakSelf.onFastImageLoadEnd) {
191+
weakSelf.onFastImageLoadEnd(@{});
171192
}
172-
}];
173-
}
193+
}
194+
}];
174195
}
175196

176197
@end

0 commit comments

Comments
 (0)