Fabric: Introducing ImageManager

Summary:
@public
ImageManager coordinates all work related to loading image bitmaps for <Image> component.
The particular iOS implementation uses RCTImageLoader from RCTImage module under the hood.

Reviewed By: fkgozali

Differential Revision: D8526571

fbshipit-source-id: a0d927972d30113eed6e0cd169fceee17610181d
This commit is contained in:
Valentin Shergin 2018-06-22 07:28:38 -07:00 committed by Facebook Github Bot
parent eabf29e320
commit 979ea2094e
14 changed files with 637 additions and 0 deletions

View File

@ -7,9 +7,12 @@
#import "RCTScheduler.h"
#import <fabric/imagemanager/ImageManager.h>
#import <fabric/uimanager/ContextContainer.h>
#import <fabric/uimanager/Scheduler.h>
#import <fabric/uimanager/SchedulerDelegate.h>
#import <React/RCTImageLoader.h>
#import <React/RCTBridge+Private.h>
#import "RCTConversions.h"
@ -44,6 +47,10 @@ private:
_delegateProxy = std::make_shared<SchedulerDelegateProxy>((__bridge void *)self);
SharedContextContainer contextContainer = std::make_shared<ContextContainer>();
void *imageLoader = (__bridge void *)[[RCTBridge currentBridge] imageLoader];
contextContainer->registerInstance(typeid(ImageManager), std::make_shared<ImageManager>(imageLoader));
_scheduler = std::make_shared<Scheduler>(contextContainer);
_scheduler->setDelegate(_delegateProxy.get());
}

View File

@ -0,0 +1,121 @@
load("//configurations/buck/apple:flag_defs.bzl", "OBJC_ARC_PREPROCESSOR_FLAGS", "get_application_ios_flags", "get_debug_preprocessor_flags")
load("//ReactNative:DEFS.bzl", "ANDROID", "APPLE", "IS_OSS_BUILD", "get_apple_inspector_flags", "react_native_xplat_target", "rn_xplat_cxx_library")
APPLE_COMPILER_FLAGS = []
if not IS_OSS_BUILD:
load("@xplat//configurations/buck/apple:flag_defs.bzl", "flags", "get_static_library_ios_flags")
APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), "compiler_flags")
rn_xplat_cxx_library(
name = "imagemanager",
srcs = glob(
[
"*.cpp",
],
exclude = glob(["tests/**/*.cpp"]),
),
header_namespace = "",
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbandroid_exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "fabric/imagemanager",
),
fbandroid_headers = subdir_glob(
[
("", "*.h"),
("platform/android", "**/*.h"),
],
prefix = "",
),
fbandroid_srcs = glob(
[
"platform/android/**/*.cpp",
],
),
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(),
fbobjc_tests = [
":tests",
],
force_static = True,
frameworks = [
"$SDKROOT/System/Library/Frameworks/CoreGraphics.framework",
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
ios_deps = [
"xplat//js:RCTImage",
"xplat//js/react-native-github:RCTCxxBridge",
],
ios_exported_headers = subdir_glob(
[
("", "*.h"),
("platform/ios", "RCTImagePrimitivesConversions.h"),
],
prefix = "fabric/imagemanager",
),
ios_headers = subdir_glob(
[
("", "*.h"),
("platform/ios", "**/*.h"),
],
prefix = "",
),
ios_srcs = glob(
[
"platform/ios/**/*.cpp",
"platform/ios/**/*.mm",
],
),
macosx_tests_override = [],
platforms = (ANDROID, APPLE),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
tests = [],
visibility = ["PUBLIC"],
deps = [
"xplat//fbsystrace:fbsystrace",
"xplat//folly:futures",
"xplat//folly:headers_only",
"xplat//folly:memory",
"xplat//folly:molly",
"xplat//third-party/glog:glog",
"xplat//yoga:yoga",
react_native_xplat_target("fabric/core:core"),
react_native_xplat_target("fabric/debug:debug"),
react_native_xplat_target("fabric/graphics:graphics"),
],
)
if not IS_OSS_BUILD:
load("@xplat//build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test")
fb_xplat_cxx_test(
name = "tests",
srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]),
contacts = ["oncall+react_native@xmail.facebook.com"],
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
platforms = APPLE,
deps = [
"xplat//folly:molly",
"xplat//third-party/gmock:gtest",
":imagemanager",
],
)

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <fabric/imagemanager/ImageRequest.h>
#include <fabric/imagemanager/primitives.h>
namespace facebook {
namespace react {
class ImageManager;
using SharedImageManager = std::shared_ptr<ImageManager>;
/*
* Cross platform facade for iOS-specific RCTImageManager.
*/
class ImageManager {
public:
ImageManager(void *platformSpecificCounterpart);
~ImageManager();
ImageRequest requestImage(const ImageSource &imageSource) const;
private:
void *self_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageRequest.h"
namespace facebook {
namespace react {
class ImageRequest::ImageNoLongerNeededException:
public std::logic_error {
public:
ImageNoLongerNeededException():
std::logic_error("Image no longer needed.") {}
};
ImageRequest::ImageRequest(const ImageSource &imageSource, folly::Future<ImageResponse> &&responseFuture):
imageSource_(imageSource),
responseFutureSplitter_(folly::splitFuture(std::move(responseFuture))) {}
ImageRequest::ImageRequest(ImageRequest &&other) noexcept:
imageSource_(std::move(other.imageSource_)),
responseFutureSplitter_(std::move(other.responseFutureSplitter_)) {
other.moved_ = true;
};
ImageRequest::~ImageRequest() {
if (!moved_) {
auto future = responseFutureSplitter_.getFuture();
if (!future.isReady()) {
future.raise(ImageNoLongerNeededException());
}
}
}
folly::Future<ImageResponse> ImageRequest::getResponseFuture() const {
if (moved_) {
abort();
}
std::lock_guard<std::mutex> lock(mutex_);
return responseFutureSplitter_.getFuture();
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <mutex>
#include <fabric/imagemanager/ImageResponse.h>
#include <fabric/imagemanager/primitives.h>
#include <folly/futures/Future.h>
#include <folly/futures/FutureSplitter.h>
namespace facebook {
namespace react {
/*
* Represents ongoing request for an image resource.
* The separate object must be constructed for every single separate
* image request. The object cannot be copied because it would make managing of
* event listeners hard and inefficient; the object can be moved though.
* To subscribe for notifications use `getResponseFuture()` method.
* Destroy to cancel the underlying request.
*/
class ImageRequest final {
public:
/*
* The exception which is thrown when `ImageRequest` is being deallocated
* if the future is not ready yet.
*/
class ImageNoLongerNeededException;
/*
* `ImageRequest` is constructed with `ImageSource` and
* `ImageResponse` future which must be moved in inside the object.
*/
ImageRequest(const ImageSource &imageSource, folly::Future<ImageResponse> &&responseFuture);
/*
* The move constructor.
*/
ImageRequest(ImageRequest &&other) noexcept;
/*
* `ImageRequest` does not support copying by design.
*/
ImageRequest(const ImageRequest &) = delete;
~ImageRequest();
/*
* Creates and returns a *new* future object with promised `ImageResponse`
* result. Multiple consumers can call this method many times to create
* their own subscriptions to promised value.
*/
folly::Future<ImageResponse> getResponseFuture() const;
private:
/*
* Mutext to protect an access to the future.
*/
mutable std::mutex mutex_;
/*
* Image source assosiated with the request.
*/
ImageSource imageSource_;
/*
* Future splitter powers factory-like `getResponseFuture()` method.
*/
mutable folly::FutureSplitter<ImageResponse> responseFutureSplitter_;
/*
* Indicates that the object was moved and hence cannot be used anymore.
*/
bool moved_ {false};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageResponse.h"
namespace facebook {
namespace react {
ImageResponse::ImageResponse(const std::shared_ptr<void> &image):
image_(image) {}
std::shared_ptr<void> ImageResponse::getImage() const {
return image_;
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <memory>
namespace facebook {
namespace react {
/*
* Represents retrieved image bitmap and any assotiated platform-specific info.
*/
class ImageResponse final {
public:
ImageResponse(const std::shared_ptr<void> &image);
std::shared_ptr<void> getImage() const;
private:
std::shared_ptr<void> image_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageManager.h"
namespace facebook {
namespace react {
ImageManager::ImageManager(void *platformSpecificCounterpart) {
// Not implemented.
}
ImageManager::~ImageManager() {
// Not implemented.
}
ImageRequest ImageManager::requestImage(const ImageSource &imageSource) const {
// Not implemented.
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageManager.h"
#import <React/RCTImageLoader.h>
#import "RCTImageManager.h"
namespace facebook {
namespace react {
ImageManager::ImageManager(void *platformSpecificCounterpart) {
self_ = (__bridge_retained void *)[[RCTImageManager alloc] initWithImageLoader:(__bridge_transfer RCTImageLoader *)platformSpecificCounterpart];
}
ImageManager::~ImageManager() {
CFRelease(self_);
self_ = nullptr;
}
ImageRequest ImageManager::requestImage(const ImageSource &imageSource) const {
RCTImageManager *imageManager = (__bridge RCTImageManager *)self_;
return [imageManager requestImage:imageSource];
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <fabric/imagemanager/ImageRequest.h>
#import <fabric/imagemanager/primitives.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTImageLoader;
/**
* iOS-specific ImageManager.
*/
@interface RCTImageManager : NSObject
- (instancetype)initWithImageLoader:(RCTImageLoader *)imageLoader;
- (facebook::react::ImageRequest)requestImage:(const facebook::react::ImageSource &)imageSource;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTImageManager.h"
#import <folly/futures/Future.h>
#import <folly/futures/Promise.h>
#import <React/RCTImageLoader.h>
#import "RCTImagePrimitivesConversions.h"
using namespace facebook::react;
@implementation RCTImageManager
{
RCTImageLoader *_imageLoader;
}
- (instancetype)initWithImageLoader:(RCTImageLoader *)imageLoader
{
if (self = [super init]) {
_imageLoader = imageLoader;
}
return self;
}
- (ImageRequest)requestImage:(const ImageSource &)imageSource
{
__block auto promise = folly::Promise<ImageResponse>();
NSURLRequest *request = NSURLRequestFromImageSource(imageSource);
auto completionBlock = ^(NSError *error, UIImage *image) {
auto imageResponse = ImageResponse(std::shared_ptr<void>((__bridge_retained void *)image, CFRelease));
promise.setValue(std::move(imageResponse));
};
auto interruptBlock = ^(const folly::exception_wrapper &exceptionWrapper) {
if (!promise.isFulfilled()) {
promise.setException(exceptionWrapper);
}
};
RCTImageLoaderCancellationBlock cancellationBlock =
[_imageLoader loadImageWithURLRequest:request
size:CGSizeMake(imageSource.size.width, imageSource.size.height)
scale:imageSource.scale
clipped:YES
resizeMode:RCTResizeModeStretch
progressBlock:nil
partialLoadBlock:nil
completionBlock:completionBlock];
promise.setInterruptHandler([cancellationBlock, interruptBlock](const folly::exception_wrapper &exceptionWrapper) {
cancellationBlock();
interruptBlock(exceptionWrapper);
});
return ImageRequest(imageSource, promise.getFuture());
}
@end

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <fabric/imagemanager/primitives.h>
#import <React/RCTImageLoader.h>
using namespace facebook::react;
inline static RCTResizeMode RCTResizeModeFromImageResizeMode(ImageResizeMode imageResizeMode) {
switch (imageResizeMode) {
case ImageResizeMode::Cover: return RCTResizeModeCover;
case ImageResizeMode::Contain: return RCTResizeModeContain;
case ImageResizeMode::Stretch: return RCTResizeModeStretch;
case ImageResizeMode::Center: return RCTResizeModeCenter;
case ImageResizeMode::Repeat: return RCTResizeModeRepeat;
}
}
inline std::string toString(const ImageResizeMode &value) {
switch (value) {
case ImageResizeMode::Cover: return "cover";
case ImageResizeMode::Contain: return "contain";
case ImageResizeMode::Stretch: return "stretch";
case ImageResizeMode::Center: return "center";
case ImageResizeMode::Repeat: return "repeat";
}
}
inline static NSURLRequest *NSURLRequestFromImageSource(const ImageSource &imageSource) {
NSString *urlString = [NSString stringWithCString:imageSource.uri.c_str()
encoding:NSASCIIStringEncoding];
if (!imageSource.bundle.empty()) {
NSString *bundle = [NSString stringWithCString:imageSource.bundle.c_str()
encoding:NSASCIIStringEncoding];
urlString = [NSString stringWithFormat:@"%@.bundle/%@", bundle, urlString];
}
NSURL *url = [[NSURL alloc] initWithString:urlString];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
/*
// TODO(shergin): To be implemented.
request.HTTPBody = ...;
request.HTTPMethod = ...;
request.cachePolicy = ...;
request.allHTTPHeaderFields = ...;
*/
return [request copy];
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include <vector>
#include <fabric/graphics/Geometry.h>
namespace facebook {
namespace react {
class ImageSource {
public:
enum class Type {
Invalid,
Remote,
Local
};
Type type {};
std::string uri {};
std::string bundle {};
Float scale {3};
Size size {0};
bool operator==(const ImageSource &rhs) const {
return
std::tie(this->type, this->uri) ==
std::tie(rhs.type, rhs.uri);
}
bool operator!=(const ImageSource &rhs) const {
return !(*this == rhs);
}
};
using ImageSources = std::vector<ImageSource>;
enum class ImageResizeMode {
Cover,
Contain,
Stretch,
Center,
Repeat,
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
#include <fabric/imagemanager/ImageManager.h>
using namespace facebook::react;
TEST(ImageManagerTest, testSomething) {
// TODO:
}