QQ邮箱的技术博客


  • 首页

  • 博文

  • 公益404

用 ReactiveCocoa 事半功倍的写代码(四)

jasonjfeng(冯牮) 发表于 2016-05-03   |  

监听系统截屏操作的复杂管道

这是一个很复杂的 Pipeline,因为要做的业务比较繁琐,如下图:

monitor screenshot

需求大致可以描述为:

  1. 当 app 停留在读信页面的时候,要实时的监听用户是否有截屏操作。
  2. 在 1 的基础上,只有 app 前台运行的时候,才实时监听用户是否有截屏操作,如果是后台状态,则不监听。
  3. 如果用户有截图动作,则将截图内容显示在一个预览视图内(如上图中红框区域)。
  4. 如果用户点击了预览视图,则进入后续的业务流程,对截图进行涂鸦编辑等等。
  5. 如果点击了预览视图的外部区域,则隐藏预览视图。
  6. 如果 10 秒钟之内没有任何操作,也自动隐藏预览视图。
    阅读全文 »

用 ReactiveCocoa 事半功倍的写代码(三)

jasonjfeng(冯牮) 发表于 2016-04-28   |  

collect + combineLatest 或者 zip

RAC 里面的 collect 是一个比较容易理解的操作,它的强大之处,在于和其他的操作进行组合之后,可以完成很复杂的业务逻辑。在看真实业务代码之前,先通过下面的代码初步了解一下这种 Pipeline 的行为模式。collect 相当于 Rx 中的 ToArray 操作

版本 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)testCollectSignalsAndCombineLatestOrZip {
//1
RACSignal *numbers = @[@(0), @(1), @(2)].rac_sequence.signal;

RACSignal *letters1 = @[@"A", @"B", @"C"].rac_sequence.signal;
RACSignal *letters2 = @[@"X", @"Y", @"Z"].rac_sequence.signal;
RACSignal *letters3 = @[@"M", @"N"].rac_sequence.signal;
NSArray *arrayOfSignal = @[letters1, letters2, letters3]; //2


[[[numbers
map:^id(NSNumber *n) {
//3
return arrayOfSignal[n.integerValue];
}]
collect] //4
subscribeNext:^(NSArray *array) {
DDLogVerbose(@"%@, %@", [array class], array);
} completed:^{
DDLogVerbose(@"completed");
}];
}
阅读全文 »

用 ReactiveCocoa 事半功倍的写代码(二)

jasonjfeng(冯牮) 发表于 2016-04-26   |  

利用 map 组装顺序执行的业务

这其实应该是最常见的使用场景,有一类业务,是可以抽象成一组按顺序执行的串行任务的,比如下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
ColdSignal<NSString, NSError>
Completion: decode success;
Error: FMBarCodeServiceErrorDomain || NSURLErrorDomain || RACSignalErrorDomain with RACSignalErrorTimedOut //4
Scheduler: specified;
Multicast: NO;
*/

- (RACSignal *)decodeBarWithURLString: (NSString *)urlString {
NSParameterAssert(urlString != nil);

@weakify(self);
return [[[self getUIImageWithURLString:urlString] //1
flattenMap:^(UIImage *image) {
@strongify(self);
return [self decodeBarWithUIImage:image]; //2
}]
timeout:1.5 onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityDefault]]; //3

}
阅读全文 »

用 ReactiveCocoa 事半功倍的写代码(一)

jasonjfeng(冯牮) 发表于 2016-04-17   |  

前言

FRP 是一门学习曲线比较陡峭的技术,回想自己以前的学习过程,也是反反复复好几次,而且总是挫败感很强。不过还好坚持了下来,现在也算是用着比较顺手了。

关于 FRP, 最容易被吐槽的地方就是没有好的学习资料和文档。一开始我也是这种感觉,后来在反复尝试的过程中,发现其实真的不是文档的问题。先说我的结论 —- 不要指望脱离代码能够把 FRP 的原理讲清楚,这是 FRP 和其他编程技术的一个明显差异,这就类似于很难用一段文字把一个数学公式描述清楚一样。而且,即便是开始看用 FRP 编写的各种代码了,还是会觉得太抽象了,仍然需要大量的时间体会代码,或者说,『悟』出其中的一些基本门道。

关于入门学习,没有捷径,最好的办法就是通过代码来学习,下面是我觉得比较好的一些入门学习资料

  • The introduction to Reactive Programming you’ve been missing
  • ReactiveCocoa Documentation 我本人主要是做 iOS 开发,目前使用的是 RAC 这个库,所以它的官方文档也是一个学习途径。另外,本文中的代码也是使用 RAC 进行编写
  • ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
  • ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
  • Interactive diagrams of Rx Observables 这个是一组动态效果图,用可视化的效果演示了一些 FRP 里常用操作(当然,其实还是很抽象的)

之所以说 FRP 的学习曲线很陡峭,不仅仅是指它的入门学习比较耗时费脑,当入了门或者稍微找到一些感觉之后,紧接着就会面对第二个问题:FRP 里面提供的都是一些比较抽象的函数操作,怎样才能用这些基本函数来解决各种各样的业务问题?尤其是那些很抽象的操作,怎样才能用起来?

这个系列的文章,主要就是针对后面这第二个问题,做的一些 demo 演示。

可以把 FRP 看成是一种更高级的 Pipeline 编程范式,Pipeline 的一个精髓,就是可以灵活的组合,虽然 FRP 里常用的操作也就那么几十个,但是一旦像搭积木那样对它们进行了组装之后,FRP 的强大之处一下子就展现了出来。

FRP 通常是以库或框架的形式提供给使用者,目前已经有很多常见编程语言的具体实现。在这个系列文章中,将使用 RAC 2 (ReactiveCocoa 的 Objective-C 版本) 进行编写。但是 FRP 本质上是一种编程范式,从 Pipeline 的角度来看,它的侧重点在于如何组装出不同形状的 Pipeline,而不太在乎 Pipeline 的具体构成材料(编程语言),从框架的角度来看,虽然有不同语言版本的实现,但是每个版本里,提供的诸如 map、flattenMap、reduce 等基础操作,在概念上和行为模式上,又都是一样的。所以,FRP 也是一门 “Learn once, write anywhere” 的技术。

FRP 有几个明显的好处,比如可以减少中间状态变量的使用,可以编写紧凑的代码,可以用同步风格编写异步运行的代码,在本系列文章中,也会尽量体现出这些特点。

阅读全文 »

CSP Concurrency Patterns In Swift

jasonjfeng(冯牮) 发表于 2016-04-10   |  

前言

这篇文章的主要内容,是从 Go Concurrency Patterns 翻译过来的。

原文是介绍 Golang 里面的 CSP 并发模型(Communicating Sequential Processes),这里则是使用一个基于 Swift3.0 的库 Venice 编写的代码。

这篇文章的主要目的,并不是鼓励大家立刻就用 Swift 进行后端开发(至少不是目前这个阶段),但是,对于想尝试全栈开发的 iOS 工程师来说,则可以通过这篇文章入门学习 CSP 这种并发编程模型。

2016/04/08 update: 为了成功运行本文中的代码,需要安装 https://swift.org/builds/development/xcode/swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a/swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a-osx.pkg 这个版本的 swift。

2016/04/13 update: Venice 里面的 Channel 不再支持基于自定义运算符的读写操作,只能使用 func api。

为什么要讨论并发 (concurrency)

观察一下我们周围,能发现什么?

我们的世界里发生的事情,总是一步一步按顺序执行的吗?

或者说,发生在我们身边的所有的事件,是一个很复杂的组合体,里面充满了更独立、更小型的事件单元,这些单元之间,则是有各种各样的交互和组织关系。

其实就像后者描述的这样,顺序处理 (Sequential processing) 并不是完美的建模思路。

什么是并发?

并发是独立的计算任务的组合。

并发是一种软件的设计模式,用并发的思维模式,可以编写出更清晰的代码。

并发 (concurrency) 不是并行 (parallelism)

并发不是并行,但是可以在并行的基础上形成并发。

如果只有一个单核处理器(单线程模式),则谈不上并行,但是仍然可以写出并发的代码。

另一方面,如果一段代码已经按照并发的思路进行了设计,那它也是可以很容易的在多核处理器(多线程模式)中并行执行。

关于这个话题,更详细的讨论可以参看 Concurrency is not Parallelism

阅读全文 »

基于libcurl的单线程I/O多路复用HTTP框架

Jasen Huang 发表于 2016-03-01   |  

1. 前言

开发iOS已经有一段时间,在每一个成熟的app开发中,一定少不了要和后台数据进行数据交互,这个时候一个好的网络框架就显得很重要。

1.1 HTTP和跨平台

HTTP请求在app开发中应用广泛。iOS本身就带有很成熟的http api,NSURLRequest,NSURLConnection等,而业界优秀的开源库AFNetworking在系统的api上进行封装,使得HTTP的开发更加便捷。Android得益于java语言,也带有很强大的HttpClient。 但这些都只能在各自平台上运行,在跨平台的应用场景下,就需要有一个套统一的解决方案。在移动客户端的开发过程中,我们实现了跨平台的http网络框架,实现了iOS和Android两个平台共用一套底层网络代码。

1.2 libcurl

libcurl是一个跨平台的多协议数据传输开源库,当然也包括http协议。在移动客户端的开发过程中,我们就尝试着基于libcurl进行了二次开发,抽取了一套通用的http client框架。

1.3 单线程I/O多路复用

网络I/O模型在后台开发中应该是比较常见的,在后台开发中,为了达到负载与性能的最优,一般要采用select/epoll/kqueue等方式对网络I/O进行复用来提高线程的负载。其实这种思想在移动客户的开发中也同样的适用,AFNetworking框架中的网络实现就是采用了这种I/O模型。网络I/O模型包括 1:阻塞I/O模型 ,2:非阻塞I/O模型 ,3:多路复用I/O模型 ,4:异步I/O模型 。这里就不展开讲解,有兴趣的同学可以查阅《UNIX网络编程》。对于iOS和Android系统而言,异步I/O模型需要依赖系统的特殊支持,所以不同于后台开发,在移动客户端开发中,一般选用第三种模型(多路复用I/O模型)。

阅读全文 »

线程与消息循环-MessageLoop

Jasen Huang 发表于 2016-03-01   |  

1. 前言

每一个需要与用户进行交互的app都需要至少一个消息循环,来处理用户事件的输入和各种I/O事件。iOS和Andriod都有各自系统的MessageLoop,iOS的叫NSRunLoop,Android的叫Looper。消息循环有利于提交系统的影响速度,在现代操作系统中得到了广泛的应用。

2. 消息循环

消息循环的工作原理大同小异,原理上就是一个while循环+消息队列不断接收任务。具体可以分为下面几个部分的内容:

  1. 事件源接收与分发
  2. 延时处理
  3. 线程等待唤醒处理

2.1 事件源接收与分发

MessageLoop内部一般会有一个消息队列,等待要执行的任务push进来。下面通过两张图简单说明下:

  • iOS的NSRunLoop
    NSRunLoop

  • Android的Looper
    Looper

    阅读全文 »

Swizzle Foundation容器的正确姿势

Jasen Huang 发表于 2016-01-04   |  

Nil Crash

这事还得从一个crash 讲起。。。
在开发iOS App的时候,很多时候会遇到下面这种场景:

1
2
3
4
5
6
7
8
//从数据库读取
NSString* item = [NSString stringWithUTF8String:sqlite3_column_text(statement, idx)];

//长长的逻辑
...

NSMutableArray* items = [NSMutableArray array];
[items addObject:item]

上面的代码存在两个地方可能引发crash,没办法,项目进度紧张,先保护一下再说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//从数据库读取
NSString* item;
const char* name = sqlite3_column_text(statement, idx);
if (name != NULL){
item = [NSString stringWithUTF8String:name];
}

//长长的逻辑
...

NSMutableArray* items = [NSMutableArray array];
if (item.length){
[items addObject:item]
}

但很快你就会发现,如果项目中要加保护的地方数不胜数,而且很容易在开发阶段把问题隐藏起来。
但不加的话,如果修复不及时,很容易把crash带到线上版本,严重影响产品的质量。
这个时候最好的办法就是Hook掉 addObject:和 stringWithUTF8String:这两个函数,
然后在Debug模式下Assert,Release模式就打Log 帮助定位。

阅读全文 »
QQ邮箱iOS开发组

QQ邮箱iOS开发组

8 博客
19 标签
© 2016 - 2017 QQ邮箱iOS开发组
由 Hexo 强力驱动
主题 - NexT.Muse