枫叶

枫叶


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索

react-native组件间的通信

发表于 2018-09-03 | 分类于 react-native |

RN与原生之间的通信方式

发表于 2018-07-29 | 分类于 react-native |

从原生到RN的通信

可以通过bridge发送通知的方式来向RN通信

从RN到原生的通信

通过实现RN的一些协议来用原生代码封装RN接口

React Native的性能优化

发表于 2018-06-27 | 分类于 react-native |

React Native的性能优化

众所周知RN由于应用了virtual DOM 、diff算法等一些列调优机制,使RN应用几乎达到了与原生一样的体验,但是毕竟RN只是原生APP的一个线程而已,RN和原生还隔着一道桥梁batch bridge,最终RN的代码还是要通过jscore引擎转换成原生代码来执行,这就决定了RN不可能超越原生,除非RN能越过这道坎,尽管官方替我们做了一些优化,但是有些优化只能交给了用户来决定,比如sholdComponentUpdate是返回true还是false,这是需要我们来决定的,再比如APP的页面如果非常多的话,打包之后bundle非常大,而加载和初始化bundle又很耗时间,这又需要拆分bundle,所以还是有许多需要人工来优化。

既然是性能优化那就肯定得找出RN的性能瓶颈在哪

一、基础优化(目的是尽量减少页面的渲染)

  1. 比较吃性能的、耗时的操作可以放到componentDidMount中,然后再用Interaction manager在包裹一下,比如网络请求。
  2. 尽量少用状态组件,尽可能用无状态组件,无状态组件不会被实例化,可以提升性能
  3. 自定义的有状态组件尽量继承自pure component,这样系统会自动在shouldComponentUpdate中默认做一层浅比较(直接拿两个对象做比较,对象中的子元素不做比较),可以减少一些不必要的渲染,当然你也也可以在该方法中做深层次的比较,如果组件不是继承自PureComponent则该方法默认返回true,这样会导致很多无用的渲染,比如父组件的改变会导致子组件的重新render。
  4. 利用immutable不可变数据,提升性能,它可以避免本来应该渲染而实际却没有发生渲染的问题,因为框架默认在shouldComponentUpdate做的是一层浅比较,如果在state改变的过程中做的是浅拷贝,则state改变之前和改变之后是相等的,指向的是同一个对象,这样浅比较会认为state没有改变而不做渲染。
  5. 对于同层级的相同类型的组件,要给每个组件指定唯一的key值。例如通常我们在一个容器组件中创建多个子组件的时候,我们会把这些子组件放在一个数组里,然后把数组直接放到容器页面中,形如下面的伪代码:

    1
    2
    3
    4
    5
    6
    7
    banner = ()=>{
    let childArr = [];
    while(let i < 10) {
    childArr.push(<Child key={XXXXXX}></Child>)
    }
    return <View> {childArr} </View>
    }

    Child组件的key一定要有,这涉及到diff算法的原理,diff算法是按层级进行比较的,当前的virtual DOM 和之前的virtual DOM进行同层级比较的时候,对于属于同一个父组件的同一层级的子组件,如果没有key值的话,RN需要遍历该父组件的所有子组件来行进对比,才能知道哪一个子组件发生了改变,这样如果子组件的数量很大的时候会很耗性能,RN有可能会因为遍历的耗时而选择放弃对比来重新渲染所有的子组件,但是如果有key值的话可以利用key直接进行两两比较,效率就高出很多。

  6. 列表优化,几乎所有的APP都有列表,所以列表的优化尤为重要,之前RN采用的是listView,数据稍微大的时候会出现明显卡顿,有性能瓶颈,最后RN在xxx版推出了新的列表神器FlatList和SectionList,他俩都是继承自VirtualizedList,比listView的性能更高,并且使用起来也更简单了,无需再想listView一样要先创建一个DataSource对象了。具体优化可以参考官网。。。
  7. 用FlatList替换scrollView,因为在用scrollView的时候它会一下子把他上面的所有子组件都渲染出来,而FlatList可以设置首屏渲染的行数,这样就不会导致在刚进入这一页的时候出现卡顿现象。

二、本地分包优化

从官方给出的RN耗时图可以看出,最耗性能的地方是bundle包的JS环境的初始化和加载,所以这一块的优化也至关重要,bundle体积过大会导致加载慢,其中bundle包括react等基础库以及引入的三方库 和 你自己的业务代码,可以先从两方面着手。第一:需要尽可能的优化bundle的体积,去掉一些不必要的资源。第二:做拆分处理,把bundle拆分成基础bundle和业务bundle。 基础bundle是APP必须依赖的公共基础部分。其实拆分之后还是很耗时间,所以我们又做了另一种处理,那就是预加载,对基础bundle进行预加载,这样可以减少一些加载以及初始化的耗时。

总结如下:

  1. 本地分包: 把bundle拆分成公共基础bundle和业务bundle
  2. 预加载: 预加载公共基础bundle

三、按需延迟加载

可以通过require来实现动态延迟加载,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import xxx from './xxxx'
let test2;
export default class Test extent Componnet {
getTest2 = ()=>{
// 对test2模块进行懒加载(延迟加载),以提高性能
if(test2 === undefined) {
test2 = require('./xxxx/test2');
}
}
}

我们不需要再这个test模块一开始就加载test2模块,这样可以延迟加载一些不是立马需要的其他模块,提升整体加载速度,降低内存。

滴滴面试总结

发表于 2018-06-15 | 分类于 iOS |

下面是面试滴滴的经历,总共出了四道技术面试题,说不出的辛酸。。。
日后有时间在总结答案,先记录下来


1、实现一个游戏人物GameRole,包含年龄、性别、装备(可以动态增减),不是Xcode,是一个没有提示的编辑器,徒手写,类名忘写了。。。

2、GameRole *role = [GameRole alloc] init…];     问:role的内存大小?

3、伪代码如下,这三行代码编译、运行的时候有什么问题,是会出现crash、警告或者出现其他情况,请说明原因

1
2
3
NSString *obj = [[NSData alloc] init];
NSInteger length = obj. length;
NSString *testStr = [obj stringApendString....];

4、appdelegate中的didfinishedLanch方法中,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
-appdelegate:didfinishedLanch {
dispatch_async(globalqueue,^{
[self testTimer];
})
}
-testTimer {
NSTimer *timer = [NSTimer scheduleTimerWith:timerActiom];
}
-timerActiom {
NSLog(@“timer action”);
}

上述代码问题吗?有什么问题?怎么解决?

4、函数指针和block的区别?

答:block的本质是结构体,使用比函数指针方便,函数指针可以作为方法参数实现回调
追问:block为什么设计成结构体的方式?
答:。。。。

写这篇blog内心是崩溃的

发表于 2017-05-13 | 分类于 iOS |

在某一个月黑风高的夜里,他正在伏案敲代码,敲完之后一脸自信的cmd + run,结果不愉快的事情就此发生了,心塞。。😂
事情是这样的,我们美丽的UI设计了一套图,本来想自定义个flowLayout走个捷径的,事实却发现走了个大弯路,图是下面这样子的👇

图.png
约束报错,很是无奈,于是我又到GitHub上查看了一遍masonry的使用方式,现总一下。

iOS布局有这么几种方式:
frame:你要看吗?我并不打算写
autoResize:autoresizingMask是view的一个布局属性,默认值是UIViewAutoresizingNone,这个枚举值有很多值,具体自己查看
autoLayout:自动布局出来以后,很受欢迎,为此苹果还设计了VFL可视化语言,但是程序员是很懒的(不是说不会偷懒的程序员不是好程序员吗,所以我说程序员懒并不是贬义词),然后masonry就诞生了,masonry是一个对NSLayoutConstraint的封装具备链式语法的三方布局库,很受大家欢迎,我不敢说没人用frame布局,但是我敢说没几个人还在用NSLayoutConstraint来布局了吧

masonry的具体用法就不说了,GitHub上有详细的用法,下面就提一下masonry一些你值得注意的地方。

  1. masrony 提供的一个利于debug约束问题的方法,代码摘自masonry的demo上的,我加了注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
[self addSubview:greenView];
UIView *redView = UIView.new;
redView.backgroundColor = UIColor.redColor;
[self addSubview:redView];
UILabel *blueView = UILabel.new;
blueView.backgroundColor = UIColor.blueColor;
[self addSubview:blueView];
UIView *superview = self;
int padding = 10;
// 给视图添加key的方式有两种,如下所示
// 法一:
//you can attach debug keys to views like so:
// greenView.mas_key = @"greenView";
// redView.mas_key = @"redView";
// blueView.mas_key = @"blueView";
// superview.mas_key = @"superview";
// 法二:
//OR you can attach keys automagically like so:
MASAttachKeys(greenView, redView, blueView, superview);
// 给约束添加key
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
//you can also attach debug keys to constaints
make.edges.equalTo(@1).key(@"ConflictingConstraint"); //composite constraint keys will be indexed
make.height.greaterThanOrEqualTo(@5000).key(@"ConstantConstraint");
make.top.equalTo(greenView.mas_bottom).offset(padding);
make.left.equalTo(superview.mas_left).offset(padding);
make.bottom.equalTo(superview.mas_bottom).offset(-padding).key(@"BottomConstraint");
make.right.equalTo(superview.mas_right).offset(-padding);
make.height.equalTo(greenView.mas_height);
make.height.equalTo(redView.mas_height).key(@340954); //anything can be a key
}];
return self;
}

上面的约束有问题,会抛出问题,如果不给视图和约束设置key的话,xcode提示错误如下:

1
2
3
4
5
6
7
8
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<MASLayoutConstraint:0x6000000a5e20 UILabel:0x7fb318c2c990.left == MASExampleDebuggingView:0x7fb318c15c00.left + 1>",
"<MASLayoutConstraint:0x6080000a40e0 UILabel:0x7fb318c2c990.left == MASExampleDebuggingView:0x7fb318c15c00.left + 10>"
)

给视图和约束添加了key之后的提示如下:

1
2
3
4
5
6
7
8
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<MASLayoutConstraint:ConflictingConstraint[0] UILabel:blueView.left == MASExampleDebuggingView:superview.left + 1>",
"<MASLayoutConstraint:0x6000000b7ac0 UILabel:blueView.left == MASExampleDebuggingView:superview.left + 10>"
)

是不是看着爽多了。。
这样你就能看到具体是哪个view的哪个约束可能出现问题了,而不是出现一堆的十六进制地址

2.添加或者更新(update、remake)约束的代码应该放在哪,代码如下一看便知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 当你使用autoLayout布局的时候建议写此方法,防止autoresize布局造成的错误
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
// this is Apple's recommended place for adding/updating constraints
// 苹果推荐添加或者更新(update、remake)约束的地方
- (void)updateConstraints {
[self.growingButton updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
// according to apple super should be called at end of method
[super updateConstraints];
}

3.autoLayout不允许对其属性例如左、右、centerY等被设置为常量,因此如果你要给这些属性传递一个NSNumber类型的值得时候masonry会将他们转换成与父视图相关的约束。

1
2
3
4
5
6
7
However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values.
So if you pass a NSNumber for these attributes
Masonry will turn these into constraints relative to the view’s superview ie:
[view makeConstraints:^(MASConstraintMaker *make) {
make.left.lessThanOrEqualTo(@10)
}];

view的左边距等价于 view.left = view.superview.left + 10

4.按比例布局,如果各占一半的话,也可以不用multipliedBy,直接约束两个视图的width isEqual就行了

1
2
3
4
// topInnerView的宽度是高度的1/3
[self.topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.topInnerView.mas_height).multipliedBy(3);
]

5.你用NSAutoLyoutConstraints布局的时候需要设置视图的view1.translatesAutoresizingMaskIntoConstraints = NO,默认值是YES,等于YES的时候autoresize会影响autolayout布局,有时会发现效果不是自己想要的,不过如果你用masonry设置约束的时候,masonry会帮你把这个属性值设置为NO,你不用管它,写出来就是想提醒你。

6.看完官方的demo,发现他们会把需要的每个约束都写上,但是有时候不需要全写上,如下面被我注释的代码,但是官方是没有注释的,既然人家官方都这样写,你是不是也应该这样写啊,别注释了,这样不容易出错,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
UIView *superview = self;
int padding = 10;
//if you want to use Masonry without the mas_ prefix
//define MAS_SHORTHAND before importing Masonry.h see Masonry iOS Examples-Prefix.pch
[greenView makeConstraints:^(MASConstraintMaker *make) {
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.bottom.equalTo(blueView.top).offset(-padding);
make.right.equalTo(redView.left).offset(-padding);
make.width.equalTo(redView).multipliedBy(1);
make.height.equalTo(redView.height);
make.height.equalTo(blueView.height);
}];
//with is semantic and option
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding); //with with
//make.left.equalTo(greenView.mas_right).offset(padding); //without with
make.bottom.equalTo(blueView.mas_top).offset(-padding);
make.right.equalTo(superview.mas_right).offset(-padding);
// make.width.equalTo(greenView).multipliedBy(1);
make.height.equalTo(@[greenView, blueView]); //can pass array of views
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(greenView.mas_bottom).offset(padding);
make.left.equalTo(superview.mas_left).offset(padding);
make.bottom.equalTo(superview.mas_bottom).offset(-padding);
make.right.equalTo(superview.mas_right).offset(-padding);
make.height.equalTo(@[greenView.mas_height, redView.mas_height]); //can pass array of attributes
}];
return self;
}

要注意blueView设置高度依赖的时候设置的是一个数组这样的用法

7.masonry动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@implementation MASExampleUpdateView
- (id)init {
self = [super init];
if (!self) return nil;
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"Grow Me!" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = 3;
[self.growingButton addTarget:self action:@selector(didTapGrowButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.growingButton];
self.buttonSize = CGSizeMake(100, 100);
return self;
}
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
// this is Apple's recommended place for adding/updating constraints
// 苹果推荐添加或或者更新约束的地方
- (void)updateConstraints {
[self.growingButton updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
// according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapGrowButton:(UIButton *)button {
self.buttonSize = CGSizeMake(self.buttonSize.width * 1.3, self.buttonSize.height * 1.3);
// tell constraints they need updating
// 告诉约束系统要更新,系统会调用上面重写的updateConstraints方法
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change,
// it will be call by system automatically
// 该方法不必手动调用
// [self updateConstraintsIfNeeded];
// 可以用layoutIfNeeded来实现即时更新,还可以添加动画
[UIView animateWithDuration:0.4 animations:^{
[self layoutIfNeeded]; // 需要在此处调用layoutIfNeeded方法才能产生动画
}];
}
@end

react native集成到原有的项目中(iOS)

发表于 2017-05-02 | 分类于 react-native |

接触RN也有一段时间了,基本上来说算是入门了,到目前RN的应用还没有达到期望的广泛度,大部分还是以原生+RN的方式进行混合开发,今天抽空写一下关于RN嵌入到iOS原生项目中的知识点。

前期准备

现在大部分嵌入方式都是采用cocoapods的方式引入RN依赖库到原生项目中,当然你也可以选择手动方式,不过很麻烦,本文采用的cocoapods来管理依赖。
RN所需要的环境也要装好,中文网有,具体我就不说了

集成

用Xcode创建一个项目,然后在项目中创建一个目录,把RN相关的都放在里面,如下图,我创建了一个js目录(这个目录你也可以放到iOS项目的根目录,任意)。
示例.png
然后cd到刚刚创建的js目录中,执行npm init,这时js目录中会多出一个package.json文件,这个文件和iOS中的Podfile类似,是用来记录着RN工程中要安装的依赖,目前你只需要关注dependencies这一项(把下面的内容覆盖到你生成的package.json文件中),该项中记录着RN项目要安装的依赖库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"name": "MixRNAndIOS",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "15.3.2",
"react-native": "^0.36.1"
},
"jest": {
"preset": "jest-react-native"
},
"devDependencies": {
"babel-jest": "16.0.0",
"babel-preset-react-native": "1.9.0",
"jest": "16.0.2",
"jest-react-native": "16.0.0",
"react-test-renderer": "15.3.2"
}
}

紧接着我们用npm包管理器来安装RN的依赖库,还是在js目录执行npm install,安装完毕之后,js目录会多出一个名为node_modules文件夹,RN所必须依赖的库都在这里面,然后我们创建一个index.ios.js作为RN项目的入口文件(名字可以任意起),然后我们就可以在入口文件中愉快的写RN代码了。

上面的步骤顺利执行完之后,RN项目已经完成了,现在我们要把RN集成到iOS原生项目中。

在项目根目录创建一个Podfile文件,如下所示,在项目的根目录执行pod install 来安装Podfile中指定的依赖库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# The target name is most likely the name of your project.
target 'MixRNAndIOS' do
# Your 'node_modules' directory is probably in the root of your project,
# but if not, adjust the `:path` accordingly
pod 'React', :path => ‘./MixRNAndIOS/js/node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTNetwork',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
'RCTImage',
]
end

注意:Podfile文件中的path路径
用pod安装完iOS所依赖的RN库之后我们就可以着手集成RN了。

RN为我们在iOS平台上提供了一个RCTRootView,RCTRootView是继承自iOS中UIView类,所以你可以像使用UIView一样使用RCTRootView,RN与iOS的交互都要在RCTRootView中进行,本篇文章先不讲交互的事,只讲集成,先把代码贴上,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import "ViewController.h"
import "RCTRootView.h"
@interface ViewController ()
@property (nonatomic, strong) NSDictionary *props;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.props = @{ @"param" : @[
@{
@"name" : @"Alex",
@"des": @"hello,我是从原生传递给RN界面的参数"
}
]
};
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake((self.view.bounds.size.width - 300)/2, 200, 300, 40)];
[btn setTitle:@"点我进入react native界面" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(highScoreButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)highScoreButtonPressed{
NSURL *jsCodeLocation;
#ifdef DEBUG
//开发的时候用,需要打开本地服务器
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
#else
//发布APP的时候用
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"jsbundle"];
#endif
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL : jsCodeLocation
moduleName : @"RNHighScores"
initialProperties : self.props //将native数据传送到RN中
launchOptions : nil];
rootView.frame = [UIScreen mainScreen].bounds;
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
[vc.view addSubview:rootView];
[self presentViewController:vc animated:YES completion:nil];
}

创建RCTRootView,将RCTRootView添加到VC中的view上就OK了
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"jsbundle"];
这一行你先忽略,后面会说。
然后cd到js目录,执行react-native start或者执行npm start,来启动本地node服务器,如果没有错误的话我们就只需最后一步了,用Xcode打开项目,运行项目,大功告成。

打RN离线包

此时我们的项目是依赖于刚刚启动的本地服务器的,要是上线怎么办,所以我们需要打个RN离线包,这样就可以摆脱本地服务器了。
进入js目录,创建一个bundle目录,这里面存放打包后的RN资源,包括RN代码和图片等静态资源,在js目录里执行下面的打包命令,

1
react-native bundle --entry-file ./index.ios.js --bundle-output ./bundle/index.ios.jsbundle --platform ios --assets-dest ./bundle --dev false

如果成功的话,在bundle目录下会生成存放RN静态资源的assert目录和RN的index.ios.jsbundle代码文件,将这俩家伙拖进Xcode中

拖.png
注意:要以引用的方式拖进Xcode中。

在文章的集成部分我粘贴了一大段代码,源代码中有两句代码用来生成RN资源的URL,第一句是依赖本地服务器的,一般调试RN代码时用,第二句是引入打包后的RN资源的URL,发布APP的时候用的,我用宏来进行控制。

1
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"jsbundle"];

注意:假如我们把第一种获取URL的方式注释掉,宏也注释掉,如果iOS项目是DEBUG模式,而我们加载的明明是RN的离线包,你会发现从原生页面跳转到RN页面的时候,顶部的statusBar会有加载资源的进度显示,不要纠结,运行项目的时候改成release模式就好了,来张效果图。

友情提示,在RN中想引入iOS中Assets.xcassets里面的图片的话可以直接写图片的文件名,如下面这样。

1
<Image source={{uri:'happiness.jpg'}} style={styles.happy}/>

ReactNativeDemo.gif

Block深入理解

发表于 2017-03-14 | 分类于 iOS |

block 你应该了解的知识

为什么不把本部分放到本质部分的下面呢,我以为实用为大,还是先把block的使用及其注意点写在前面吧。

1、为了方便声明block类型的变量,我们一般用typedef typedef void (^Block)(void)给block类型起个别名,这样我们就可以直接按如下方式声明block变量了。

1
2
3
4
5
6
7
8
9
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
//这样声明
Block block = ^{};
//而不是这样
//void(^block)(void)=^{};
return 0;
}

2、在非ARC情况下,定义块的时候(无论是全局块还是局部块),其所占的内存区域是分配在栈中的。如下声明了一个block,如下面的代码就有危险,在条件语句实现的两个block都分配在栈内存中,于是这两个块只在对应的条件语句范围内有效,这样写的代码可以编译,但是运行起来却是时而对时儿错,若编译器未复写待执行的块,则程序正常运行,若复写则程序奔溃。

1
2
3
4
5
6
7
8
9
10
11
void (^block)();
if(/*some condition*/){
block = ^{
NSLog(@"Block A");
};
}else{
block = ^{
NSLog(@"Block B");
};
}
block();

应该按这样的姿势写

1
2
3
4
5
6
7
8
9
10
11
void (^block)();
if(/*some condition*/){
block = [^{
NSLog(@"Block A");
} copy];
}else{
block = [^{
NSLog(@"Block B");
} copy];
}
block();

3、同理2,将block声明为属性的时候,要用copy,还要注意如果你不确定你生命的这个block属性会不会被其他线程修改,你就用atomic加个原子锁,这样就线程安全了

1
@property (copy) Block block; //属性默认就是atomic

4、调用block的时候,有些童鞋的姿势不太对,假如我声明了一个block属性,正确调用姿势如下

1
2
3
4
Block block = self.block;
if(block){
block();
}

大部分童鞋会按下面这样写,那些连判断都不做的童鞋我就不批评你了,回去面壁去

1
2
3
4
5
6
if(self.block){
//我是其他线程,我要这里要捣乱
block();
}

上面的写法为什么不妥呢,因为即使self.block当时存在,如果另一个线程在该线程执行到我注释的那一行的时候把block置空了咋办,你再调用是不是就得到了一个完美的闪退,但是我如果把self.block 赋值给了一个局部变量的话,其他线程修改的是self.block而修改不了这个零时变量,所以上面的那种姿势不太稳妥。如果你看过AF的源码你就会发现,歪果仁就是按着我说的上面的正确姿势写的。

5、为什么用了__block就可以修改所截获的变量了?

因为block的特性,编译器不允许在block内直接修改所捕获的变量,但是我们可以修改__block修饰的自动变量,因为用__block修饰过之后,原先存储在栈中的变量就变成了存在堆中了,查看用clang过后的cpp文件你会发现在block中多了一个与该变量同名的__Block_byref_i_0结构体的指针变量,其中包含了存储在堆中的那个变量,可以通过结构体指针变量来直接更改变量的值,而没有用__block修饰的变量,block会把截获的变量copy为自己的一个变量。

6、避免循环引用,如果你把一个block声明成了对象的一个属性,那么该对象就会持有这个block,如果在该对象中要实现block属性的话,用到self的时候要用__weak修饰过的,不然会循环引用。

7、block的存储位置,栈、堆、全局数据区(强调一下如非特殊说明,block都是函数中实现)
block是否截获外围变量会影响他的存储区域的。

7.1 下图是ARC模式下执行的代码
ARC下的block.png
7.2 下图是非ARC模式下执行的代码
MRC下的block.png

解释一下上面的结果,学过C的都知道,malloc是分配到堆中了,global是分配到全局数据区了。

7.3.1 MRC下此种写法Xcode会报错,但是如果不引用外围变量的话就没事,如果你仔细看7.1与7.2的介绍,你就知道原因了,不过我还是想说一下。因为在MRC情况下引入外围变量时,此种写法的block存在栈里面,而该函数的却返回了block,return标志着一个函数的结束,所以在return的时候block会被释放而报错,在MRC情况下不引入外围变量的话,此种写法的block存在全局数据区里,所以没问题。

MRC下此种写法报错.png
7.3.2 ARC下,无论引不引入外围变量,都没事,不引入返回的block存在全局数据区,引入的话存在堆中。就不截图了。

7.4 下面这种情况,ARC与MRC下block都存储在全局数据区,这种情况不常出现,一般我们都是在函数中来是实现block的。

ARC与MRC下都存储在全局数据区.png

7.5 总结(强调一下如非特殊说明,block都是函数中实现):

ARC模式下:不论你声明的是局部block还是全局block,它们只要不截获外围变量,它们都会存储在全局数据区的,如果截获外围变量,block就会存储在堆(heap)中。

非ARC模式下:不论你声明的是局部block还是全局block,它们只要不截获外围变量,它们都会存储在全局数据区的,如果截获外围变量,block就会存储在栈(stack)中。

两种模式下的差别:只要不截获外围变量block一律都存在全局数据区,只有截获了外围变量ARC和MRC才有所区别,而开发中往往我们的block都是后面这么一个情况,现在很少有人使用非ARC了吧,所以还是关注ARC的情况吧,即你只需要记住结论的第一条就好了。

block 的本质

block其本质是一个struct,也可以说是一个含有自动变量的匿名函数,通过clang编译器转换成C++代码可以看出,执行clang -rewrite-objc 要转换的OC文件命令,可以在同级目录下获得一个.cpp文件,里面就是转换后的OC代码,下面我会分三种情况给出OC代码及其对应的cpp代码。

1、只是纯粹的在入口函数中定义了一个block,block中也没有引入外围变量

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
void(^block)(void)=^{
NSLog(@"Block!!");
};
block();
return 0;
}

下面是转换后的C++代码,为了方便观察,我把文件最下方的有关block的代码摘录如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//block的结构体
struct __main_block_impl_0 {
//block的实现
struct __block_impl impl;
//block的描述(包含block的大小以及copy,dispose等)
struct __main_block_desc_0* Desc;
//block的构造函数,对block结构体成员变量的初始化
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block内的代码实现部分
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1q_hr0kg_v15rj7ry_618ljfldr0000gn_T_hellow_a5b27a_mi_0);
}
//block的描述,包含block的大小以及copy,dispose
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//OC中的main函数
int main(int argc, char * argv[]) {
void(*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

2、在入口函数中定义了一个block,并在block中引入外围整型变量i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
//自动变量i
int i = 10;
void(^block)(void)=^{
NSLog(@"Block!!---%d",i);
};
block();
return 0;
}

转换后的cpp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//这是block捕获的变量
int i;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1q_hr0kg_v15rj7ry_618ljfldr0000gn_T_main_1b12e5_mi_0,i);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
int i = 10;
void(*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

3、在入口函数中定义了一个block,并在block中引入外围整型变量i,并且i用__block修饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int i = 10;
void(^block)(void)=^{
i += 1;
NSLog(@"Block!!---%d",i);
};
block();
return 0;
}

转换后的cpp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//存储block截获的外围变量的一个结构体
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//这是block捕获的变量
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) += 1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1q_hr0kg_v15rj7ry_618ljfldr0000gn_T_main_10e8d1_mi_0,(i->__forwarding->i));
}
//下面两个指针函数是__main_block_desc_0结构体中的函数指针的实现,前者是要保留block截获的对象,后者则将之释放
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
//block的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
//主函数
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};
void(*block)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}

第一种和第二种比较可知,当block截获外围变量时,block会把截获的变量注册成为自己的成员变量,这也是为什么block不能直接修改截获的变量的原因,因为在block内操作的外围变量其实是block的同名的成员变量。

第一种和第三种比较可知,当block截获外围变量时,block会把截获的变量封装成__Block_byref_i_0结构体,并把结构体指针变量注册为自己的成员变量。

被__block修饰的外围变量会变成堆变量,这样这个外围变量就不会随函数的结束而被释放了,__Block_byref_i_0结构体i指针变量中有一个指向自己的__forwarding指针,通过i->__forwarding->i来修改存在堆中的外围变量。

iOS音频的播放以及录制

发表于 2017-03-10 | 分类于 iOS |

iOS中的音频包括两种类型,一种是时间比较短无需监听播放时间等属性的音效,一种是时间比较长的音乐

iOS 深拷贝和浅拷贝

发表于 2017-02-11 | 分类于 iOS |

浅拷贝只是增加了一个对被引用对象的一个指向,拷贝出的对象与原对象共用一块内存区域。
retain:只是retainCount加一,实际上还是共用一块内存
copy:不论拷贝的是不是集合对象,只要被拷贝的对象是不可变的,那就是浅拷贝,仅仅retainCount加一

深拷贝会分配出一块内存,然后将引用的内容拷贝进去,也就是说拷贝出来的对象与原对象是独立的。
copy:拷贝可变对象则是深拷贝
mutableCopy:不论被拷贝的是不是集合对象,是可变的还是不可变的,都是深拷贝

注意:如果mutableCopy的是集合对象,深拷贝的是该集合对象,不是指该集合对象里面的元素,称之为单层深拷贝,对于集合对象内的元素依然是指针拷贝

总结
copy:只要copy的是不可变对象,都是浅拷贝,copy可变集合象则是单层深拷贝,拷贝可变非集合对象则是深拷贝
mutableCopy:拷贝集合象则是单层深拷贝,拷贝非集合对象则是深拷贝

iOS中assign与weak,retain与strong的区别

发表于 2017-02-11 | 分类于 iOS |

以前在没有ARC的时候我们使用assign与retain来修饰属性,后来引入了更安全的weak和strong来修饰属性

assign与weak

两者都是弱引用,assign通常用于普通类型属性(如int,NSInteger),还有代理属性的修饰,基本上来说两者是可以通用的。
只是后者比前者多了一个功能,后者会在引用的对象被释放的时候将该属性置为nil,而前者依然会指向原来的位置,这样就会变成野指针。在oc中你给你一个nil对象发送消息不会crash,但是给一个对象(野指针)发送他不能解析的消息是会crash的,所以总的来说weak要比assign安全一些。
像delegate属性建议用weak修饰而不是assign。

retain和strong

他俩都是强引用,除了某些情况下不一样,其他的时候也是可以通用的。

在修饰block属性的时候,相信大家都知道要用copy,如果不copy的话,他的生命周期会随着函数的结束而结束,copy之后会放在堆里面,延长block的生命周期。
strong在修饰block的时候就相当于copy,而retain修饰栈block的时候就相当于assign,这样block会出现提前被释放掉的危险。

12
枫叶

枫叶

坐看云起时

16 日志
3 分类
20 标签
CSDN GitHub 简书 Email
© 2019 枫叶
由 Hexo 强力驱动
主题 - NexT.Muse