ReactNative Image组件的原理,源码分析

1.简介

image组件是iOS中常用组件之一,是一个由UIImageView导出来的RN控件。那么必然就包括oc端的代码导出与js端的代码配合,我们将从源码角度查看下这个控件是怎么工作的。

2.问题

<Image source={{uri:'http://*****/soft/collection/240/20160705/20160705121955903778.jpg'}}  /> 

<Image source={require('./img/test.png')}/>

为什么配置source就可以下载网络图片呢?为什么可以支持网络图片有可以支持本地图片呢?他们究竟是怎么工作的,接下来我们将一一介绍。

3.源码结构


3.1 image OC层

RCTImageView,RCTImageViewManager配合负责oc端导出。

查看RCTImageView代码会发现,带有一个source,如下

@property(nonatomic,strong)RCTImageSource*source;

而这个source也就是对应的类为RCTImageSource,主要包括了图片的路径imageURL,图片scale,图片大小。
imageURL:需要主要的是imageURL不一定是http类型,在获取bundel包图片资源的时候为file://格式在调试模式下的本地图片和网络图片下载模式下imageURL为http类型。
scale:为图片生成模式 从1 到 3的范围分别对应@1x @2x @3x 图。js端会根据机型进行选择如在iphone6上 调试模式下加载本地图的情况下imageURL为http://*****/image@3x.png(如果以有3x图), scale为3。
总之,js端在代用require图片后就会经过一系列处理转换为一个包含图片信息的RCTImageSource,用来加载图片解码图片。

大体流程是这样:
JS source require --> RCTImageSource -->loadImage


外部输入假设
JS source require --> RCTImageSource 已经获取到了source.
我们先关注: RCTImageSource -->loadImage

- (void)setSource:(RCTImageSource *)source
{
  if (![source isEqual:_source]) {
    _source = source;
    [self reloadImage];
  }
}
- (void)reloadImage{

[selfcancelImageLoad];

if(_source&&self.frame.size.width>0&&self.frame.size.height>0) {

if(_onLoadStart) {

_onLoadStart(nil);

}

RCTImageLoaderProgressBlockprogressHandler =nil;

if(_onProgress) {

progressHandler = ^(int64_tloaded,int64_ttotal) {

_onProgress(@{

@"loaded":@((double)loaded),

@"total":@((double)total),

});

};

}

CGSizeimageSize =self.bounds.size;

CGFloatimageScale =RCTScreenScale();

if(!UIEdgeInsetsEqualToEdgeInsets(_capInsets,UIEdgeInsetsZero)) {

// Don't resize images that use capInsets

imageSize =CGSizeZero;

imageScale =_source.scale;

}

RCTImageSource*source =_source;

CGFloatblurRadius =_blurRadius;

__weakRCTImageView*weakSelf =self;

这里为图片真实获取地址
_reloadImageCancellationBlock= [_bridge.imageLoaderloadImageWithoutClipping:_source.imageURL.absoluteString
size:imageSize
scale:imageScale
resizeMode:(RCTResizeMode)self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError*error,UIImage*image) {
RCTImageView*strongSelf = weakSelf;
if(blurRadius >__FLT_EPSILON__) {

// Do this on the background thread to avoid blocking interaction

image =RCTBlurredImageWithRadius(image, blurRadius);

}

dispatch_async(dispatch_get_main_queue(), ^{

if(![sourceisEqual:strongSelf.source]) {

// Bail out if source has changed since we started loading

return;

}

if(image.reactKeyframeAnimation) {

[strongSelf.layeraddAnimation:image.reactKeyframeAnimationforKey:@"contents"];

}else{

[strongSelf.layerremoveAnimationForKey:@"contents"];

strongSelf.image= image;

}

if(error) {

if(strongSelf->_onError) {

strongSelf->_onError(@{@"error": error.localizedDescription});

}

}else{

if(strongSelf->_onLoad) {

strongSelf->_onLoad(nil);

}

}

if(strongSelf->_onLoadEnd) {

strongSelf->_onLoadEnd(nil);

}

});

}];

}else{

[selfclearImage];

}

}

//imageLoaderloadImageWithoutClipping 函数就就会进行图片下载等内如处理包括缓冲图片界面图片识别是不是本地图片或者还是网络图片,如是本地图片就直接imageName 或者用绝对路径加载,网络图片启动网络下载流程,这样就到达区分到底是要加载本地还是网络图片了,下载成功后设置 strongSelf.image= image;

3.1 image js层

刚刚描述了oc端拿到数据结构后的图片下载流程,那么这个RCTImageSource是如何被处js端处理的呢?也就是回答下这个流程:JS source require --> RCTImageSource 。
主要文件为:image.ios.js ,我们对齐分析发现

    var source = resolveAssetSource(this.props.source) || {};

对我们传进来的require('./img/test.png'),实际为一个数字,进行了处理。
resolveAssetSource,这是干嘛用的我们接着往下跟,路径为:[Libraries/Image/resolveAssetSource.js]
其中一个重要函数如下

/**
 * `source` is either a number (opaque type returned by require('./foo.png'))
 * `source` 就是个数字(从require('./foo.png')返回来的)或者是像
 * { uri: '<http location || file path>' }本地和网络都支持的字典数据格式
 */

function resolveAssetSource(source: any): ?ResolvedAssetSource {

if (typeof source === 'object') {

return source;

}

var asset = AssetRegistry.getAssetByID(source);

if (!asset) {

return null;

}
//这里模块里面将进行 1x 2x 3x图的选择
const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);

if (_customSourceTransformer) {

return _customSourceTransformer(resolver);

}

return resolver.defaultAsset();

}

我们分析下AssetSourceResolver组件里面的主要函数, 1x 2x 3x图的选择是由他来完成的
路径:[Libraries/Image/AssetSourceResolver.js]

/**

 * Returns a path like 'assets/AwesomeModule/icon@2x.png'
 */

function getScaledAssetPath(asset): string {

var scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());

var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';

var assetDir = assetPathUtils.getBasePath(asset);

return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
}

这样js端就处理完成:resolveAssetSource 返回结果为一个字典给OC用,在RCTImageViewManager中会
RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource),当然字典怎么导出为source的,也不难理解,

@implementation RCTConvert (ImageSource)

+ (RCTImageSource *)RCTImageSource:(id)json
{
  if (!json) {
    return nil;
  }

  NSURL *imageURL;
  CGSize size = CGSizeZero;
  CGFloat scale = 1.0;
  BOOL packagerAsset = NO;
  if ([json isKindOfClass:[NSDictionary class]]) {
    if (!(imageURL = [self NSURL:RCTNilIfNull(json[@"uri"])])) {
      return nil;
    }
    size = [self CGSize:json];
    scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0;
    packagerAsset = [self BOOL:json[@"__packager_asset"]];
  } else if ([json isKindOfClass:[NSString class]]) {
    imageURL = [self NSURL:json];
  } else {
    RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?");
    return nil;
  }

  RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL
                                                               size:size
                                                              scale:scale];
  imageSource.packagerAsset = packagerAsset;
  return imageSource;
}

@end

总结:至此一个完整的调用流程就实现了,当然还有很多细节,由此可以看出RN的实现是复杂的,但却合理的,之所以要看明白这些,好比你想把RN的image组件换成SDWebImage的下载逻辑,又比如你想自己造一个类似的控件加载图片,要模拟这个过程,所以了解源码是很有必要的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,977评论 4 60
  • 说起汪涵,大家都不陌生,湖南卫视的台柱子,临场应变能力强,孙楠在我是歌手直播时选择退赛,观众目瞪口呆,汪涵用好口才...
    喜欢麦芽糖阅读 206评论 0 0
  • [梦想家,去评论里谈谈,你怎么看待文中观点?] 知乎上有一个热门话题: 这是一个看脸的世界吗? 截图自知乎 657...
    十年后阅读 2,731评论 19 53
  • 黄金灿烂满心欢, 碧海秋波挂笑颜。 馥郁情深风采写, 熊猫吐蕊暖秋含。
    六月天气阅读 1,234评论 21 21
  • CALayer做时钟demo 首先: 一定要搞明白 position 和 anchorPoint 这一点非常重要 ...
    Dayu大鱼阅读 714评论 0 2