Skip to content

Commit b905548

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Fabric: Replace ImageLoader promise implementation with observer model
Summary: Folly promises/futures have been replaced by an observer model which keeps track of loading state. This resolves at least one crash that I can no longer repro and simplifies the code a bit (IMO). Reviewed By: shergin Differential Revision: D13743393 fbshipit-source-id: 2b650841525db98b2f67add85f2097f24259c6cf
1 parent 2ed1bb2 commit b905548

File tree

14 files changed

+388
-87
lines changed

14 files changed

+388
-87
lines changed

React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,18 @@
1111

1212
NS_ASSUME_NONNULL_BEGIN
1313

14+
@protocol RCTImageResponseDelegate <NSObject>
15+
16+
- (void)didReceiveImage:(UIImage *)image fromObserver:(void*)observer;
17+
- (void)didReceiveProgress:(float)progress fromObserver:(void*)observer;
18+
- (void)didReceiveFailureFromObserver:(void*)observer;
19+
20+
@end
21+
1422
/**
1523
* UIView class for root <Image> component.
1624
*/
17-
@interface RCTImageComponentView : RCTViewComponentView
25+
@interface RCTImageComponentView : RCTViewComponentView <RCTImageResponseDelegate>
1826

1927
@end
2028

React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,48 @@
1313
#import <react/components/image/ImageShadowNode.h>
1414
#import <react/imagemanager/ImageRequest.h>
1515
#import <react/imagemanager/ImageResponse.h>
16+
#import <react/imagemanager/ImageResponseObserver.h>
1617
#import <react/imagemanager/RCTImagePrimitivesConversions.h>
1718

1819
#import "RCTConversions.h"
1920
#import "MainQueueExecutor.h"
2021

2122
using namespace facebook::react;
2223

24+
class ImageResponseObserverProxy: public ImageResponseObserver {
25+
public:
26+
ImageResponseObserverProxy(void* delegate): delegate_((__bridge id<RCTImageResponseDelegate>)delegate) {}
27+
28+
void didReceiveImage(const ImageResponse &imageResponse) override {
29+
UIImage *image = (__bridge UIImage *)imageResponse.getImage().get();
30+
void *this_ = this;
31+
dispatch_async(dispatch_get_main_queue(), ^{
32+
[delegate_ didReceiveImage:image fromObserver:this_];
33+
});
34+
}
35+
36+
void didReceiveProgress (float p) override {
37+
void *this_ = this;
38+
dispatch_async(dispatch_get_main_queue(), ^{
39+
[delegate_ didReceiveProgress:p fromObserver:this_];
40+
});
41+
}
42+
void didReceiveFailure() override {
43+
void *this_ = this;
44+
dispatch_async(dispatch_get_main_queue(), ^{
45+
[delegate_ didReceiveFailureFromObserver:this_];
46+
});
47+
}
48+
49+
private:
50+
id<RCTImageResponseDelegate> delegate_;
51+
};
52+
2353
@implementation RCTImageComponentView {
2454
UIImageView *_imageView;
2555
SharedImageLocalData _imageLocalData;
56+
std::shared_ptr<const ImageResponseObserverCoordinator> _coordinator;
57+
std::unique_ptr<ImageResponseObserverProxy> _imageResponseObserverProxy;
2658
}
2759

2860
- (instancetype)initWithFrame:(CGRect)frame
@@ -35,6 +67,8 @@ - (instancetype)initWithFrame:(CGRect)frame
3567
_imageView.clipsToBounds = YES;
3668

3769
_imageView.contentMode = (UIViewContentMode)RCTResizeModeFromImageResizeMode(defaultProps->resizeMode);
70+
71+
_imageResponseObserverProxy = std::make_unique<ImageResponseObserverProxy>((__bridge void *)self);
3872

3973
self.contentView = _imageView;
4074
}
@@ -78,23 +112,42 @@ - (void)updateLocalData:(SharedLocalData)localData
78112
{
79113
_imageLocalData = std::static_pointer_cast<const ImageLocalData>(localData);
80114
assert(_imageLocalData);
81-
auto future = _imageLocalData->getImageRequest().getResponseFuture();
82-
future.via(&MainQueueExecutor::instance()).thenValue([self](ImageResponse &&imageResponse) {
83-
self.image = (__bridge UIImage *)imageResponse.getImage().get();
84-
});
115+
self.coordinator = _imageLocalData->getImageRequest().getObserverCoordinator();
116+
117+
// Loading actually starts a little before this
118+
std::static_pointer_cast<const ImageEventEmitter>(_eventEmitter)->onLoadStart();
119+
}
120+
121+
- (void)setCoordinator:(std::shared_ptr<const ImageResponseObserverCoordinator>)coordinator {
122+
if (_coordinator) {
123+
_coordinator->removeObserver(_imageResponseObserverProxy.get());
124+
}
125+
_coordinator = coordinator;
126+
if (_coordinator != nullptr) {
127+
_coordinator->addObserver(_imageResponseObserverProxy.get());
128+
}
85129
}
86130

87131
- (void)prepareForRecycle
88132
{
89133
[super prepareForRecycle];
134+
self.coordinator = nullptr;
90135
_imageView.image = nil;
91136
_imageLocalData.reset();
92137
}
93138

94-
#pragma mark - Other
139+
-(void)dealloc
140+
{
141+
self.coordinator = nullptr;
142+
_imageResponseObserverProxy.reset();
143+
}
144+
145+
#pragma mark - RCTImageResponseDelegate
95146

96-
- (void)setImage:(UIImage *)image
147+
- (void)didReceiveImage:(UIImage *)image fromObserver:(void*)observer
97148
{
149+
std::static_pointer_cast<const ImageEventEmitter>(_eventEmitter)->onLoad();
150+
98151
const auto &imageProps = *std::static_pointer_cast<const ImageProps>(_props);
99152

100153
if (imageProps.tintColor) {
@@ -110,11 +163,22 @@ - (void)setImage:(UIImage *)image
110163
resizingMode:UIImageResizingModeStretch];
111164
}
112165

113-
_imageView.image = image;
114-
166+
self->_imageView.image = image;
167+
115168
// Apply trilinear filtering to smooth out mis-sized images.
116-
_imageView.layer.minificationFilter = kCAFilterTrilinear;
117-
_imageView.layer.magnificationFilter = kCAFilterTrilinear;
169+
self->_imageView.layer.minificationFilter = kCAFilterTrilinear;
170+
self->_imageView.layer.magnificationFilter = kCAFilterTrilinear;
171+
172+
std::static_pointer_cast<const ImageEventEmitter>(self->_eventEmitter)->onLoadEnd();
118173
}
119174

175+
- (void)didReceiveProgress:(float)progress fromObserver:(void*)observer {
176+
std::static_pointer_cast<const ImageEventEmitter>(_eventEmitter)->onProgress(progress);
177+
}
178+
179+
- (void)didReceiveFailureFromObserver:(void*)observer {
180+
std::static_pointer_cast<const ImageEventEmitter>(_eventEmitter)->onError();
181+
}
182+
183+
120184
@end

React/Fabric/RCTScheduler.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ @implementation RCTScheduler {
4747
std::shared_ptr<SchedulerDelegateProxy> _delegateProxy;
4848
}
4949

50-
- (instancetype)initWithContextContainer:(std::shared_ptr<void>)contextContatiner
50+
- (instancetype)initWithContextContainer:(std::shared_ptr<void>)contextContainer
5151
{
5252
if (self = [super init]) {
5353
_delegateProxy = std::make_shared<SchedulerDelegateProxy>((__bridge void *)self);
54-
_scheduler = std::make_shared<Scheduler>(std::static_pointer_cast<ContextContainer>(contextContatiner), getDefaultComponentRegistryFactory());
54+
_scheduler = std::make_shared<Scheduler>(std::static_pointer_cast<ContextContainer>(contextContainer), getDefaultComponentRegistryFactory());
5555
_scheduler->setDelegate(_delegateProxy.get());
5656
}
5757

ReactCommon/fabric/components/image/ImageEventEmitter.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ void ImageEventEmitter::onLoadEnd() const {
2222
dispatchEvent("loadEnd");
2323
}
2424

25-
void ImageEventEmitter::onProgress() const {
26-
dispatchEvent("progress");
25+
void ImageEventEmitter::onProgress(double progress) const {
26+
dispatchEvent("progress", [=](jsi::Runtime &runtime) {
27+
auto payload = jsi::Object(runtime);
28+
payload.setProperty(runtime, "progress", progress);
29+
return payload;
30+
});
2731
}
2832

2933
void ImageEventEmitter::onError() const {

ReactCommon/fabric/components/image/ImageEventEmitter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ImageEventEmitter : public ViewEventEmitter {
1818
void onLoadStart() const;
1919
void onLoad() const;
2020
void onLoadEnd() const;
21-
void onProgress() const;
21+
void onProgress(double) const;
2222
void onError() const;
2323
void onPartialLoad() const;
2424
};

ReactCommon/fabric/imagemanager/ImageRequest.h

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
#pragma once
99

10-
#include <mutex>
11-
12-
#include <folly/futures/Future.h>
13-
#include <folly/futures/FutureSplitter.h>
1410
#include <react/imagemanager/ImageResponse.h>
11+
#include <react/imagemanager/ImageResponseObserver.h>
12+
#include <react/imagemanager/ImageResponseObserverCoordinator.h>
1513
#include <react/imagemanager/primitives.h>
1614

1715
namespace facebook {
@@ -22,7 +20,6 @@ namespace react {
2220
* The separate object must be constructed for every single separate
2321
* image request. The object cannot be copied because it would make managing of
2422
* event listeners hard and inefficient; the object can be moved though.
25-
* To subscribe for notifications use `getResponseFuture()` method.
2623
* Destroy to cancel the underlying request.
2724
*/
2825
class ImageRequest final {
@@ -33,15 +30,10 @@ class ImageRequest final {
3330
*/
3431
class ImageNoLongerNeededException;
3532

36-
ImageRequest();
37-
3833
/*
39-
* `ImageRequest` is constructed with `ImageSource` and
40-
* `ImageResponse` future which must be moved in inside the object.
34+
* The default constructor
4135
*/
42-
ImageRequest(
43-
const ImageSource &imageSource,
44-
folly::Future<ImageResponse> &&responseFuture);
36+
ImageRequest(const ImageSource &imageSource);
4537

4638
/*
4739
* The move constructor.
@@ -51,32 +43,36 @@ class ImageRequest final {
5143
/*
5244
* `ImageRequest` does not support copying by design.
5345
*/
54-
ImageRequest(const ImageRequest &) = delete;
46+
ImageRequest(const ImageRequest &other) = delete;
5547

5648
~ImageRequest();
5749

58-
/*
59-
* Creates and returns a *new* future object with promised `ImageResponse`
60-
* result. Multiple consumers can call this method many times to create
61-
* their own subscriptions to promised value.
50+
/**
51+
* Set cancelation function.
6252
*/
63-
folly::Future<ImageResponse> getResponseFuture() const;
53+
void setCancelationFunction(std::function<void(void)> cancelationFunction);
6454

65-
private:
6655
/*
67-
* Mutext to protect an access to the future.
56+
* Get observer coordinator.
6857
*/
69-
mutable std::mutex mutex_;
58+
std::shared_ptr<const ImageResponseObserverCoordinator>
59+
getObserverCoordinator() const;
7060

61+
private:
7162
/*
7263
* Image source assosiated with the request.
7364
*/
7465
ImageSource imageSource_;
7566

7667
/*
77-
* Future splitter powers factory-like `getResponseFuture()` method.
68+
* Event coordinator associated with the reqest.
69+
*/
70+
std::shared_ptr<const ImageResponseObserverCoordinator> coordinator_{};
71+
72+
/*
73+
* Function we can call to cancel image request (see destructor).
7874
*/
79-
mutable folly::FutureSplitter<ImageResponse> responseFutureSplitter_;
75+
std::function<void(void)> cancelRequest_;
8076

8177
/*
8278
* Indicates that the object was moved and hence cannot be used anymore.

ReactCommon/fabric/imagemanager/ImageResponse.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
#pragma once
9+
810
#include <memory>
911

1012
namespace facebook {
@@ -15,12 +17,18 @@ namespace react {
1517
*/
1618
class ImageResponse final {
1719
public:
20+
enum class Status {
21+
Loading,
22+
Completed,
23+
Failed,
24+
};
25+
1826
ImageResponse(const std::shared_ptr<void> &image);
1927

2028
std::shared_ptr<void> getImage() const;
2129

2230
private:
23-
std::shared_ptr<void> image_;
31+
std::shared_ptr<void> image_{};
2432
};
2533

2634
} // namespace react
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/imagemanager/ImageResponse.h>
11+
12+
namespace facebook {
13+
namespace react {
14+
15+
/*
16+
* Represents any observer of ImageResponse progression, completion, or failure.
17+
*/
18+
class ImageResponseObserver {
19+
public:
20+
virtual void didReceiveProgress(float) = 0;
21+
virtual void didReceiveImage(const ImageResponse &imageResponse) = 0;
22+
virtual void didReceiveFailure() = 0;
23+
virtual ~ImageResponseObserver() noexcept = default;
24+
};
25+
26+
} // namespace react
27+
} // namespace facebook

0 commit comments

Comments
 (0)