源码解读RCTImageView(iOS)

[TOC]

源码解读RCTImageView(iOS)

查看官网自定义UI组件的文档, 可以知道每个自定义的UI组件CustomView需要对应一个CustomViewManagerCustomViewManager继承RCTViewManager.

我们可以跟踪查看requireNativeComponent的实现, 最终发现调用的是UIManager.createView(...), 对应原生RCTUIManager的createView方法。

源码解读RCTImageView(iOS)_第1张图片
createView.png

一、RCTUIManager的原理

原生

  • RCTUIManager也是一个module, 在setBridge的时候, 获取bridge中所有RCTViewManager的子类保存下来
- (void)setBridge:(RCTBridge *)bridge {
    ...
      for (Class moduleClass in _bridge.moduleClasses) {
    if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
      RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass
                                                                                bridge:_bridge];
      componentDataByName[componentData.name] = componentData;
    }
  }

  _componentDataByName = [componentDataByName copy];
  ...
}
  • constantsToExport把所有相关ViewManager的信息导出
- (NSDictionary *)constantsToExport
{
    ...
  return constants;
}

具体信息格式为:

{
    ...
    RCTImageView =     {
        Manager = ImageViewManager;
        NativeProps =         {
            blurRadius = CGFloat;
            capInsets = UIEdgeInsets;
            defaultSource = UIImage;
            onError = BOOL;
            onLoad = BOOL;
            onLoadEnd = BOOL;
            onLoadStart = BOOL;
            onPartialLoad = BOOL;
            onProgress = BOOL;
            resizeMode = RCTResizeMode;
            source = "NSArray";
            tintColor = UIColor;
        };
    };
    RCTWebView =     {
        Manager = WebViewManager;
        NativeProps =         {
            allowsInlineMediaPlayback = BOOL;
            automaticallyAdjustContentInsets = BOOL;
            bounces = BOOL;
            contentInset = UIEdgeInsets;
            dataDetectorTypes = UIDataDetectorTypes;
            decelerationRate = CGFloat;
            injectedJavaScript = NSString;
            mediaPlaybackRequiresUserAction = BOOL;
            messagingEnabled = BOOL;
            onLoadingError = BOOL;
            onLoadingFinish = BOOL;
            onLoadingStart = BOOL;
            onMessage = BOOL;
            onShouldStartLoadWithRequest = BOOL;
            scalesPageToFit = BOOL;
            scrollEnabled = BOOL;
            source = NSDictionary;
        };
    };
    customBubblingEventTypes =     {
        topBlur =         {
            phasedRegistrationNames =             {
                bubbled = onBlur;
                captured = onBlurCapture;
            };
        };
        topChange =         {
            phasedRegistrationNames =             {
                bubbled = onChange;
                captured = onChangeCapture;
            };
        };
        ...
    };
    customDirectEventTypes =     {
        topAccessibilityTap =         {
            registrationName = onAccessibilityTap;
        };
        topError =         {
            registrationName = onError;
        };
        ...
    };
    ...
}

每个对应的UI组件 xxxView有manager、NativeProps俩个属性。分别表示对应的ViewManager及属性。

JS

//UIManager.js
// 经过映射 UIManager是一个带有上述jsonkey的对象。 同时 会增加Constants 与 UIManager的属性。

UIManager 数据结构

源码解读RCTImageView(iOS)_第2张图片
UIManagerjs.png

RCTImageView

原生类的关系图:

源码解读RCTImageView(iOS)_第3张图片
RCTImageView.png

js部分

//Image.ios.js
const Image = createReactClass({...,
    render:function() {
        ...
            return (
              
            );
    }
});

... 

const RCTImageView = requireNativeComponent('RCTImageView', Image);

module.exports = Image;

图片定位

Image的图片浏览主要通过source设置, js传递source信息体到原生, 原生根据source信息体生成对应的请求, 通过遍历所有实现的RCTImageURLLoader协议的模块,选择最优的loader获取image,如果没有则尝试使用网络接口获取,失败则报错。 流程如下

源码解读RCTImageView(iOS)_第4张图片
getImage.png

source的解析

RN的Image控件,支持2种形式的source

  • require('filepath') //PropTypes.number
  • {uri:'fileURI', bundle:'bundleName', width:100, height:100, ... } //ImageURISourcePropType

图片控件的JS代码对应Image.ios.js, 原生代码对应RCTImageView

require方式

require方式返回的是一个整型, 对应一个define函数, 在bundle中体现为

//引用的地方  require方式
_react2.default.createElement(_reactNative.Image, { source: require(305                                      ), __source: { // 305 = ./Images/diary_mood_icon_alcohol_32.png
            fileName: _jsxFileName,
            lineNumber: 30
          }
        }),
 // uri 方式
_react2.default.createElement(_reactNative.Image, { source: { uir: 'https://www.baidu.com/img/bd_logo1.png', width: 100, height: 100 }, __source: {
            fileName: _jsxFileName,
            lineNumber: 31
          }
        })
//define地方
__d(/* RN472/Images/diary_mood_icon_alcohol_32.png */function(global, require, module, exports) {module.exports=require(161                                         ).registerAsset({"__packager_asset":true,"httpServerLocation":"/assets/Images","width":16,"height":16,"scales":[2,3],"hash":"7824b2f2a263b0bb181ff482a88fb813","name":"diary_mood_icon_alcohol_32","type":"png"}); // 161 = react-native/Libraries/Image/AssetRegistry
}, 305, null, "RN472/Images/diary_mood_icon_alcohol_32.png");

我们看到打包的时候,require图片会转换成如下格式的对象保存:

{
    "__packager_asset":true,  //是否是asset目录下的资源
    "httpServerLocation":"/assets/Images", //server目录地址
    "width":16, 
    "height":16,
    "scales":[2,3], //图片scales   
    "hash":"7824b2f2a263b0bb181ff482a88fb813", //文件hash值
    "name":"diary_mood_icon_alcohol_32", //文件名
    "type":"png" //文件类型
}

我们看到引用的地方require(305)其实是执行了require(161)的registerAsset的方法。查看161的define

__d(/* AssetRegistry */function(global, require, module, exports) {
'use strict';

var assets = [];

function registerAsset(asset) {
  return assets.push(asset);
}

function getAssetByID(assetId) {
  return assets[assetId - 1];
}

module.exports = { registerAsset: registerAsset, getAssetByID: getAssetByID };
}, 161, null, "AssetRegistry");

161对应的就是AssetRegistry, AssetRegistry.registerAsset把图片信息保存在成员数组assets中。
查看Image.ios.js的render函数

  render: function() {
        const source = resolveAssetSource(this.props.source) || { uri: undefined, width: undefined, height: undefined };
    ...
    return (
      
    );

通过resolveAssetSource函数

function resolveAssetSource(source: any): ?ResolvedAssetSource {
  if (typeof source === 'object') {
    return source;
  }

  var asset = AssetRegistry.getAssetByID(source);
  if (!asset) {
    return null;
  }

  const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
  if (_customSourceTransformer) {
    return _customSourceTransformer(resolver);
  }
  
  return resolver.defaultAsset();

调用AssetRegistry.getAssetByID方法取出对应的信息,传递到原生。

//传递到原生的source信息格式
{
    "__packager_asset" = 1;
    height = 16;
    scale = 2;
    uri = "//Users/huangmingwei/Library/Developer/CoreSimulator/Devices/2A0C4BE4-807B-4000-83EB-342B720A14DE/data/Containers/Bundle/Application/F84F1359-CBCD-4184-B3FD-2C7833B83A60/RN472.app/react-app/assets/Images/[email protected]";
    width = 16;
}

原生通过解析uri信息,获取对应的图片

//RCTImageView.m
- (void)setImageSources:(NSArray *)imageSources
{
  if (![imageSources isEqual:_imageSources]) {
    _imageSources = [imageSources copy];
    [self reloadImage];
  }
}

原生加载过程涉到及几个类

  • RCTImageSource, 通过js传递的source信息,转换出图片地址
  • RCTImageViewManager 管理RCTImageView
  • RCTImageVIew 对应每个Image实例
  • RCTImageLoader bridge的成员,控制图片的加载逻辑
  • RCTImageURLLoader 协议,具体图片的定位执行者需要实现此协议
  • RCTLocalAssetImageLoader 实现bundle统计目录asset下的图片访问 loader

重点在RCTImageURLLoader协议

//判断能否处理
- (BOOL)canLoadImageURL:(NSURL *)requestURL;

//获取图片, 如果是异步处理返回一个可以取消的block
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
                                              size:(CGSize)size
                                             scale:(CGFloat)scale
                                        resizeMode:(RCTResizeMode)resizeMode
                                   progressHandler:(RCTImageLoaderProgressBlock)progressHandler
                                partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
                                 completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;

URI方式

这种方式的加载流程与require相同,只是source的获取信息方式比较简单,js端设置的是什么,到原生端也是一样

function resolveAssetSource(source: any): ?ResolvedAssetSource {
  if (typeof source === 'object') {  // uri 方式直接返回
    return source;
  }
  

原生通过RCTImageSource解析流程是一样的。

Array方式

js端最终将source转换为一个Array,传递到原生。如果有多个uri,可设置为不同尺寸的图片,在原生会选择大小最适合的一个图片展示。 当只有一张图片的时候,默认会转为只有一个元素的Array。目前都是只有一张哦~

RCTImageView.m

- (RCTImageSource *)imageSourceForSize:(CGSize)size
{
  if (![self hasMultipleSources]) {
    return _imageSources.firstObject;
  }

  // Need to wait for layout pass before deciding.
  if (CGSizeEqualToSize(size, CGSizeZero)) {
    return nil;
  }

  const CGFloat scale = RCTScreenScale();
  const CGFloat targetImagePixels = size.width * size.height * scale * scale;

  RCTImageSource *bestSource = nil;
  CGFloat bestFit = CGFLOAT_MAX;
  for (RCTImageSource *source in _imageSources) {
    CGSize imgSize = source.size;
    const CGFloat imagePixels =
      imgSize.width * imgSize.height * source.scale * source.scale;
    const CGFloat fit = ABS(1 - (imagePixels / targetImagePixels));

    if (fit < bestFit) {
      bestFit = fit;
      bestSource = source;
    }
  }
  return bestSource;
}

你可能感兴趣的:(源码解读RCTImageView(iOS))