ReactiveCocoa入门教程——第二部分
译文: http://benbeng.leanote.com/post/ReactiveCocoaTutorial-part2 ReactiveCocoa是一个框架,它能让你在iOS应用中使用函数响应式编程(FRP)技术。在本系列教程的第一部分中,你学到了如何将标准的动作与事件处理逻辑替换为发送事件流的信号。你还学到了如何转换、分割和聚合这些信号。 在本系列教程的第二部分,你将会学到一些ReactiveCocoa的高级功能,包括:
是时候深入研究一下了。 Twitter Instant在本教程中你将要开发的应用叫Twitter Instant(基于Google Instant的概念),这个应用能搜索Twitter上的内容,并根据输入实时更新搜索结果。 这个应用的初始工程包括一些基本的UI和必须的代码。和第一部分一样,你需要使用CocoaPods来获取ReactiveCocoa框架,并集成到项目中。初始工程已经包含必须的Podfile,所以打开终端,执行下面的命令:
如果执行正确的话,你能看到和下面类似的输出: Analyzingdependencies 这会生成一个Xcode workspcae,TwitterInstant.xcworkspace。在Xcode中打开它,确认其中包含两个项目:
构建运行,就能看到下面的界面:
花一些时间来熟悉应用的代码。这个是一个很简单的应用,基于split view controller。左栏是RWSearchFormViewController,它通过storyboard在上面添加了一些UI控件,通过outlet连接了search text field。右栏是RWSearchResultsViewController,目前只是UITableViewController的子类。 打开RWSearchFormViewController.m,能看到在viewDidLoad方法中,首先定位到results view controller,然后把它分配给resultsViewController私有属性。应用的主要逻辑都会集中在RWSearchFormViewController,这个属性能把搜索结果提供给RWSearchResultsViewController。 验证搜索文本的有效性首先要做的就是验证搜索文本,来确保文本长度大于2个字符。如果你完成了本系列教程的第一部分,那这个应该很熟悉。 在RWSearchFormViewController.m中的viewDidLoad下面添加下面的方法: -(BOOLisValidSearchText:(NSString*)text{ 这个方法就只是确保要搜索的字符串长度大于2个字符。这个逻辑很简单,你可能会问“为什么要在工程文件中写这么一个单独的方法呢?”。 目前验证输入有效性的逻辑的确很简单,但如果将来逻辑需要变得更复杂呢?如果是像上面的例子中那样,那你就只需要修改一个地方。而且这样写能让你代码的可读性更高,代码本身就说明了你为什么要检查字符串的长度。 在RWSearchFormViewController.m的最上面,引入ReactiveCocoa: #import<ReactiveCocoa.h>
把下面的代码加到viewDidLoad的最下面 : [[selfsearchTextrac_textSignal 上面的代码做了什么呢?
构建运行,观察在输入文本过短时,text field的背景会变成黄色来标示输入无效。 用图形来表示的话,流程和下面的类似: 当text field中的文字每次发生变化时,rac_textSignal都会发送一个next事件,事件包含当前text field中的文字。map这一步将文本值转换成了颜色值,所以subscribeNext:这一步会拿到这个颜色值,并应用在text field的背景色上。 你应该还记得本系列教程第一部分里这些内容吧?如果忘了,建议你先停在这里,回去看一下第一部分。 在添加Twitter搜索逻辑之前,还有一些有意思的话题要说说。 格式化代码 当你在探索如何格式化ReactiveCocoa的代码时,惯例是每个操作新起一行,垂直对齐每个步骤。 在下图中你能看到比较复杂的代码是如何对齐的,这是第一部分教程中的代码。 这样对齐能让你很容易的看到每一步的操作。同时你还应该减少每个block中的代码量,如果block中的代码超过几行时,就应该新写一个私有方法。 很不幸的是,Xcode不是很喜欢这种风格的格式化,所以你会发现Xcode的自动缩进逻辑总是和你过不去。 内存管理 看一下你添加到TwitterInstant中的代码,你是否好奇创建的这些管道是如何持有的呢?显然,它并没有分配给某个变量或是属性,所以它也不会有引用计数的增加,那它是怎么销毁的呢? ReactiveCocoa设计的一个目标就是支持匿名生成管道这种编程风格。到目前为止,在你所写的所有响应式代码中,这应该是很直观的。 为了支持这种模型,ReactiveCocoa自己持有全局的所有信号。如果一个signal有一个或多个订阅者,那这个signal就是活跃的。如果所有的订阅者都被移除了,那这个信号就能被销毁了。更多关于ReactiveCocoa如何管理这一过程,参见文档Memory Management。 上面说的就引出了最后一个问题:如何取消订阅一个signal?在一个completed或者error事件之后,订阅会自动移除(马上就会讲到)。你还可以通过RACDisposable手动移除订阅。 RACSignal的订阅方法都会返回一个RACDisposable实例,它能让你通过dispose方法手动移除订阅。下面是一个例子: RACSignalbackgroundColorSignal= 你会发现这个方法并不常用到,但是还是有必要知道可以这样做。
避免循环引用 ReactiveCocoa已经在幕后做了很多事情,这也就意味着你并不需要太多关注signal的内存管理。但是还有一个很重要的内存相关问题你需要注意。 看一下你刚才添加的代码: subscribeNext:block中使用了self来获取text field的引用。block会捕获并持有其作用域内的值。因此,如果self和这个信号之间存在一个强引用的话,就会造成循环引用。循环引用是否会造成问题,取决于self对象的生命周期。如果self的生命周期是整个应用运行时,比如说本例,那也就无伤大雅。但是在更复杂一些的应用中,就不是这么回事了。 ReactiveCocoa框架包含了一个语法糖来替换上面的代码。在文件顶部添加下面的代码: #import"RACEXTScope.h"?? 然后把代码替换成下面的: @weakify(@strongify 上面的@weakify和@strongify语句是在Extended Objective-C库中定义的宏,也被包括在ReactiveCocoa中。@weakify宏让你创建一个弱引用的影子对象(如果你需要多个弱引用,你可以传入多个变量),@strongify让你创建一个对之前传入@weakify对象的强引用。 就在引用的下面,添加下面的枚举和常量: typedefNS_ENUMNSInteger,RWTwitterInstantErrorRWTwitterInstantErrorAccessDeniedRWTwitterInstantErrorNoTwitterAccountsRWTwitterInstantErrorInvalidResponse};staticconstRWTwitterInstantDomain@"TwitterInstant";?? 一会儿你就要用到它们来标示错误。 还是在这个文件中,在已有属性声明的下面,添加下面的代码: @propertystrongnonatomicACAccountStoreaccountStoreACAccountTypetwitterAccountTypeACAccountsStore类能让你访问你的设备能连接到的多个社交媒体账号,ACAccountType类则代表账户的类型。 上面的代码创建了一个account store和Twitter账户标识符。 当应用获取访问社交媒体账号的权限时,用户会看见一个弹框。这是一个异步操作,因此把这封装进一个signal是很好的选择。 还是在这个文件中,添加下面的代码: requestAccessToTwitterSignal//1-defineanerror
这个方法做了下面几件事:
回忆一下教程的第一部分,signal能发送3种不同类型的事件:
在signal的生命周期中,它可能不发送事件,发送一个或多个next事件,在这之后还能发送一个completed事件或一个error事件。 最后,为了使用这个signal,把下面的代码添加到viewDidLoad的最下面: idxNSLog(@"Accessgranted");"Anerroroccurred:%@" 构建运行,应该能看到下面这样的提示: then方法会等待completed事件的发送,然后再订阅由thenblock返回的signal。这样就高效地把控制权从一个signal传递给下一个。 注意:你在之前的代码中已经把self转成弱引用了,所以就不用在这个管道之前再写@weakify(self)了。 then方法会跳过error事件,因此最终的subscribeNext:error:block还是会收到获取访问权限那一步发送的error事件。 构建运行,然后允许访问,你应该能看到search text field的输入会在控制台里输出。 2014-010408:1611.444TwitterInstant[39118a0bm 接下来,在管道中添加一个filter操作来过滤掉无效的输入。在本例里就是长度不够3个字符的字符串: [[[[filter 再次构建运行,观察过滤器的工作: 管道从requestAccessToTwitterSignal开始,然后转换为rac_textSignal。同时,next事件通过一个filter,最终到达订阅者的block。你还能看到第一步发送的error事件也是由subscribeNext:error:block来处理的。 现在你已经有了一个发送搜索文本的signal了,是时候来搜索Twitter的内容了。你现在觉得还好吗?我觉得应该还不错哦~ 搜索Twitter的内容 你可以使用Social Framework来获取Twitter搜索API,但的确如你所料,Social Framework不是响应式的。那么下一步就是把所需的API调用封装进signal中。你现在应该熟悉这个过程了。 在RWSearchFormViewController.m中,添加下面的方法: SLRequestrequestforTwitterSearchWithTextNSURLurlNSURLURLWithString:@"https://api.twitter.com/1.1/search/tweets.json"NSDictionary*params@{@"q"requestrequestForServiceTypeSLServiceTypeTwitterrequestMethodSLRequestMethodGETURLurl 下一步是基于这个请求创建signal。在同一个文件中,添加下面的方法: signalForSearchWithText//1-definetheerrors
构建运行,在search text field中输入一些文字。当文本长度超过3个字符时,你应该就能在控制台看到搜索Twitter的结果了。 下面是一段你将会看到的数据: 05074227.697403085403"search_metadata""completed_in""0.019"15"max_id"419735546840117248"max_id_str""next_results""?max_id=419734921599787007&q=asd&include_entities=1"queryasd"refresh_url""?since_id=419735546840117248&q=asd&include_entities=1""since_id""since_id_str"statusescontributors""coordinates"created_at""SunJan0507:42:07+00002014"entitieshashtags...?? signalForSearchText:方法还会发送error事件到subscribeNext:error:block里。你最好自己尝试一下。 在模拟中打开设置应用,选择你的Twitter账户,然后按“Delete Account”删除它。 再重新运行应用,现在还是允许访问用户的Twitter账号,但是没有可用的账号。signalForSearchText:会发送一个error,输出如下: 5211.705413741403AnerroroccurredErrorDomain=TwitterInstantCode=1"Theoperationcouldn’tbecompleted.(TwitterInstanterror1.)"Code=1表示是RWTwitterInstantErrorNoTwitterAccounts错误。在实际的应用中,你可能需要判断错误码来做一些更有用的事情,而不只是打印到控制台。 这是真的吗?一个简单的操作,就把事件流切换到不同的线程了?真的是太棒了! 现在你就能安全地更新UI啦! 注意:如果你看一下RACScheduler类,就能发现还有很多选项,比如不同的线程优先级,或者在管道中添加延迟。 现在要展示那些微博了。 更新UI 如果你打开RWSearchResultsViewController.h就会发现已经有一个displayTweets:方法了,它会让右边的view controller根据提供的微博数组来展示内容。实现非常简单,就是一个标准的UITableView数据源。displayTweets:方法需要的唯一一个参数就是包含RWTweet实例的数组。RWTweet模型已经包含在初始工程里了。 subscibeNext:error:里收到的数据目前是在signalForSearchWithText:里由返回的JSON值转换得到的一个NSDictionary。所以你怎么确定字典里的内容呢? 看一下Twitter的API文档,那里有返回值的样例。NSDictionary和这个结构对应,所以你能找到一个叫“statuses”的键,它对应的值是一个包含微博的NSArray,每个条文也是NSDictionary实例。 RWTweet已经有一个类方法tweetWithStatus:,方法从NSDictionary中取得需要的数据。所以你需要的做的就是写一个for循环,遍历数组,为每条微博创建一个RWTweet实例。 但我们这次不这么做。还有更好的方法。 这篇文章是关于ReactiveCocoa和函数式编程。如果用函数式API来实现把数据从一个格式转换为另一个会优雅很多。你将会用到LinqToObjectiveC来完成这个任务。 关闭TwitterInstant workspace,然后在文本编辑中打开之前创建的Podfile。加入新的依赖: platformios'7.0'pod'ReactiveCocoa''2.1.8''LinqToObjectiveC''2.0.0' 在这个文件中打开终端,输入下面的命令: podupdate 能看到输出和下面的类似: InstallingLinqToObjectiveC2.0project 再次打开workspace,检查新的pod是否和下图一样显示出来: 打开RWSearchFormViewController.m,添加下列引用: #import"RWTweet.h" NSArray+LinqExtensions.h头文件是LinqToObjectiveC里的,它为NSArray添加了许多方法,能让你用流式API来转换、排序、分组和过滤其中的数据。现在就来用一下 viewDidLoad中的代码更新成下面这样的: jsonSearchResult[@"statuses"tweetsstatuseslinq_selectidtweetRWTweettweetWithStatustweetresultsViewControllerdisplayTweets 在上面的代码中,subscribeNext:block首先获取包含微博的数组。然后linq_select方法对数组中的每个元素执行提供的block,来把NSDictionary的数组转换成RWTweet的数组。 |
- c# – 进程是否在远程计算机上运行?
- XML文档定义有几种形式?它们之间有何本质区别?
- ios – 添加Xcode子项目:不应该复制所有源文件吗
- 依赖注入 – 使用DbConnection的正确方法,DbTran
- 修补AJAX应用中Back/Forward Button和Bookmark失
- oracle12c – Oracle 12c中SYS.ALL_TAB_COLUMNS和
- c# – 在使用XmlSerializer反序列化XML时保留仅空
- ncurses多种颜色在屏幕上
- 【cocos2d-x 3.x 学习与应用总结】3: CallFunc系
- applicationContext.xml 配置文件的存放位置