Reactive-Native框架的简单尝试(Part I)

从Reactive-Native的名字说起
字面意思,Reactive响应式,Native原生。就如JSPatch下发热更新修复线上问题,本质上还是利用OC的runtime特性,把JS解析并注入运行时,比如在class中加属性就是改变对象模型的offset,加方法就是在method list中增加对应的selector并映射到相关的method,改变方法Method Swizzling等等,总之runtime能做的事情,解析这段JS文件以后也可以执行,和原生的代码一样的性能,而不是在webview中去渲染,体验更佳流畅,这就是Native。经过FB大神们的封装,JS的写法不再是原生的JS,而是和React如出一辙(http://reactjs.cn/react/index.html),这就是Reactive。

接下来我们来试一下官方的几个basic demo
安装,自己看,要哪些东西(https://facebook.github.io/react-native/docs/getting-started.html)注意执行,react-native run-ios的时候,如果闪退,检查下网络配置,因为本地会有node.js的服务,全局代理冲突了会无法访问。
写一个hello world,文档(https://facebook.github.io/react-native/docs/tutorial.html)import, from, class, extends, and the () => 这些语法都是ES2015标准,<Text>Hello world!</Text>就是JSX,XML和嵌入JS的写法,类似于Rails当中的erb文件,嵌入式,<Text>是RN框架封装好的组件,注意RN框架有一些列已经封装好的组件,熟悉他们的特性也就是掌握这个框架的过程。在RN中所有东西都是组件,就算你要新建一个类也必须是extends Component,另外AppRegistry表明是root组件。完成以后cd到项目文件夹下的ios目录,用Xcode打开,看到AppDelegate.m,定位到application:didFinishLaunchingWithOptions:这个方法,在应用打开的时候加载了.js文件,并生成RCTRootView作为root view,看下node.js的server在干什么,浏览器中打开http://localhost:8081/index.ios.bundle,能看到相关的JS代码,由JavaScriptCore framework加载并执行。后来debug中每次cmd+R都是重新加载并执行应用开启时的动作。

再往下看文档你会发现,每一个RN框架中的class其实都继承于Component,而一个组件Component是由props和state控制的,写下这两个概念

Props:大多数组件可以被自定义创建,当然需要不同的初始化参数,这里叫做Props,看到demo(https://facebook.github.io/react-native/docs/props.html#content)<Greeting name=’Rexxar’ />,组件创建需要参数name, 然后在Greeting这个Class中访问这个参数需要从props进入,this.props.name。只在初始化组建的时候使用,不能改变的。

State:Props在组件的生命周期中是固定的,如果组件的状态发生改变,比如输入框输入新的内容,那么我们就要使用state来追踪这些改变。一般情况下,我们在constructor中初始化state,看到https://facebook.github.io/react-native/docs/state.html#content,this.state = {showText: true};,这里的state是个bool值。你可以选择一个状态容器state container比如Redux,但如果之前没有接触过,就会有学习成本,脑中迅速闪过一个个JS大神的名字,哈哈。

界面布局,总体来说,用JS来写,CSS可以嵌套,内容与样式分离和web app一个套路,还有flexbox流式布局等等,用到了就去查看文档和实例吧。简单过一下,TextInput组件,依赖state的改变,每次输入以后自动刷新页面。ScrollView会一次把数据都显示在页面上,没有复用的效果。ListView有点像UITableView可以复用,但也需要Datasource来初始化。网络层使用Mozilla的Fetch框架,你也可以用Ajax,支持WebSocket。有通用的Navigators,也有iOS定制的Navigators,那个页面的堆栈要自己来维护,推入推出操作要手动写,淡然也有现成的东西(https://github.com/react-community/react-navigation)。在iOS10.2的iPhone6上运行官方的demo,我看到那个导航栏在iOS上是看不到的,只是一行text,这种情况也是我担心的风险,OS升级带来的bug,所以RN要更新及时啊~

说了这么多,我们从头到尾写一个业务相关的demo吧,包括login,拿到token,传递token再次发起请求,解析数据,渲染布局等等。开始 继续阅读Reactive-Native框架的简单尝试(Part I)

在MVVM为基础的并发框架中接入Realm

Realm是为移动端打造的数据库,面向对象的数据库,重写了存储引擎,告别ROM,纯粹原生的对象存储。介绍:https://realm.io/cn/news/realm-object-centric-present-day-database-mobile-applications/ 划下重点:1. 数据层库和 ORMs 2. Realm 不是重复现在的技术。

Realm 移动数据库的一个标志性特性:你可以在主线程里面读写!你不用担心这会阻塞你的 UI。但我原来的结构是并发的,数据的增删改查都是异步的,怎么接入?
我接给你看,这就是我要讲的,看上去很脑残,哈哈。

首先我的架构是怎样的,简单描述下:

正如一千个人眼中就有一千个哈姆雷特,所讲的也是我眼中的MVVM 继续阅读在MVVM为基础的并发框架中接入Realm

iOS命令行符号化crash文件

打开命令行输入:

找到symbolicatecrash工具的路径,可能会花去一些时间,OSX 10.10.5 安装xcode7下的结果是:

遇到Error: “DEVELOPER_DIR” is not defined,处理:

把.ipa改名.zip解压以后得到.app文件,.app.dSYM文件,对应的crash文件方法到一个目录下,运行:

符号化到当前目录的crashlog.log文件中
配置符号化工具到命令行:

以后这么用:

最后方便大家使用,我用gem写了命令行工具,crash_analysis,打开终端运行

 

NSTimer会保留其目标对象

来源 ——《Effective Objective-C 2.0 》Matt Galloway

计时器是一种很方便的对象。Foundation 框架中有个类叫做NSTimer,开发者可以指定绝对的日期和时间,以便到时执行任务,也可以指定执行任务的相对延迟时间。计时器还可以重复运行任务,有个与之相关联的”间隔值”(interval)可用来指定任务的触发平绿。比方说,可以每5秒轮询某个资源。
计时器要和”运行循环”(run loop)相关联,运行循环到时候回触发任务。创建NSTimer时,可以将其预先安排在当前的运行循环中,也可以先创建好,然后由开发者自己来调度。无论采用哪种方式,只有把计时器放在运行循环里,它才能正常触发任务。例如,下面这个方法可以创建计时器,并将其预先安排在当前运行循环中:

用此方法创建出来的计时器,会在指定的间隔时间后执行任务。也可以令其反复执行任务,直到开发者稍后将其手动关闭为止。target与selector参数表示计时器将在哪个对象上调用哪个方法。计时器会保留其目标对象,等到自身失效时再释放此对象。调用invalidate方法可令计时器失效;执行完相关任务之后,一次性的计时器也会失效。开发者若将计时器设置成重复执行模式,那么必须自己调用invalidate方法,才能令其停止。 继续阅读NSTimer会保留其目标对象

精简initialize与load的实现代码

来源 ——《Effective Objective-C 2.0 》Matt Galloway

有时候,类必须先执行某些初始化操作,然后才能正常使用。在OC中,绝大多数类都继承自NSObject这个根类,而该类有两个方法,可用来实现这种初始化操作。

首先要讲的是load方法,其原型如下:

对于加入运行期系统中的每个类(class)及分类(category)来说,必定会调用此方法,而且仅调用一次。当包含类或分类的程序载入系统时,就会执行此方法,而这通常就是指应用程序启动的时候,若程序是为iOS平台设计的,则肯定会在此时执行。Mac OS X应用程序更自由一些,它们可以使用“动态加载”(dynamic loading)之类的特性,等应用程序启动好之后再去加载程序库。如果分类和其所属的类都定义了load方法,则先调用类里的,再调用分类里的。

load方法的问题在于,执行该方法时,运行期系统处于“脆弱状态”(fragile state)。在执行子类的load方法之前,必定会先执行所有超类的load方法,而如果代码还依赖了其他程序库,那么程序库里相关类的load方法也必定会先执行。然而,根据某个给定的程序库,却无法判断出其中各个类的加载顺序。因此,在load方法中使用其他类是不安全的。比方说,有下面这段代码:

此处使用NSLog没问题,而且相关字符串也会照常记录,因为Foundation框架肯定在运行load方法之前已经载入系统了。但是,在EOCClassB的load方法里使用EOCClassA却不太安全,因为无法确定在执行EOCClassB的load方法之前,EOCClassA是不是已经加载好了。可以看到:EOCClassA这个类,也许会在其load方法中执行某些重要操作,只有执行完这些操作之后,该类实例才能正常使用。 继续阅读精简initialize与load的实现代码

多用类型常量,少用#define预处理命令

来源 ——《Effective Objective-C 2.0 》Matt Galloway

编写代码时经常要定义常量。例如,要写一个UI视图类,此视图显示出来以后就播放动画,然后消失。你可能想把播放动画的时间提取为常量。可能会这么写:

上述预处理指令会把源代码中的ANIMATION_DURTION字符串替换为0.3。这可能就是你想要的效果,不过这样定义出来的常量没有类型信息。“持续”(duration)这个词看上去应该与时间相关,但是代码中又未明确指出。此外,预处理过程会把碰到的所有ANIMATION_DURATION一律替换成0.3。这样的话,假设此指令声明在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。

要想解决此问题,应该设法利用编译器的某些特性才对。有个办法比用预处理指令来定义常量更好。比方说,这样写:

请注意,此方式定义的常量包含类型信息,其好处是清楚地描述了常量的含义。由此可知该常量类型为NSTimeInterval,这有助于为其编写开发文档。如果要定义许多常量,那么这种方式能令稍后阅读代码的人更易理解其意图。 继续阅读多用类型常量,少用#define预处理命令

理解objc_msgSend的作用

来源 ——《Effective Objective-C 2.0 》Matt Galloway

在对象上调用方法是OC中经常使用的功能。用OC的术语来说,这叫做“传递消息”(pass a message)。消息有“名称”(name)或“选择子”(selector),可以接受参数,而且可能还有返回值。

由于OC是C的超集,所以最好先理解C语言的函数调用方式。C语言使用“静态绑定”(static binding),也就是说,在编译期就能决定运行时所应该调用的函数。以下列代码为例:

如果不考虑“内联”(inline),那么编译器在编译代码的时候就已经知道程序中有printHello与printGoodbye这两个函数了,于是会直接生成调用这些函数的指令。而函数地址实际上是硬编码在指令中的。若是将刚才那段代码写成下面这样,会如何呢?

这时就得使用“动态绑定”(dynamic binding)了,因为所要调用的函数直到运行期才能确定。编译器在这种情况下生成的指令与刚才那个例子不同,在第一个例子中,if与else语句里都有函数调用指令。而在第二个例子中,只有一个函数调用指令,不过待调用的函数地址无法硬编码早指令之中,而是要在运行期读取出来。 继续阅读理解objc_msgSend的作用

用枚举表示状态、选项、状态码

来源 ——《Effective Objective-C 2.0 》Matt Galloway

由于OC基于C语言,所以C语言的功能它都有。其中之一就是枚举类型:enum。系统框架中频繁用到此类型,然而开发者容易忽视它。在以一系列常量来表示错误状态码或可组合的选项时,使用枚举为其命名是常见的。由于C++11标准扩充了枚举的特性,所以最新版系统框架使用了“强类型”(strong type)的枚举。没错,OC也能得益于C++11标准。

枚举只是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集(enumeration set)。比如说,可以用下列枚举表示“套接字连接”(socket connection)的状态:

由于每种状态都用一个便于理解的值来表示,所以这样写出来的代码更易读懂。编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1。实现枚举所用的数据类型取决于编译器,不过其二进制位(bit)的个数必须能完全表示枚举编号才行。在范例中,由于最大编号是2,所以使用1个字节的char类型就可以。然而定义枚举变量的方式却不太简洁,要依据如下语法编写:

若是每次不用敲入enum而只需写EOCConnectionState就好了。要想这么做,则需要使用typedef关键字重新定义枚举类型:

现在可以用简写的EOCConnectionState来代替完整的enum EOCConnectionState了:

继续阅读用枚举表示状态、选项、状态码

多用字面量语法,少用与之等价的方法

来源 ——《Effective Objective-C 2.0 》Matt Galloway

编写OC程序时,总会用到某几个类,它们属于Foundation框架。虽然从技术上来说,不用Foundation框架也能写出OC代码,但实际上却经常用到此框架。这及各类是NSString、NSNumber、NSArray、NSDictionary。从类名上即可看出各自所表示的数据结构。

OC以语法繁杂而著称,事实上的确实这样。不过,从OC1.0起,有一种非常简单的方式创建NSString对象。这就是“字符串字面量”(string literal),其语法如下:

如果不用这种语法的话,就要以常见的alloc,然后init方法来分配并且初始化NSString对象了。在版本较新的编译器中,也能用这种字面量语法来声明NSNumber、NSArray、NSDictionary类的实例。使用字面量语法(literal syntax)可以缩减源代码长度,使其更为易读。 继续阅读多用字面量语法,少用与之等价的方法

@class与#import

来源 ——《Effective Objective-C 2.0 》Matt Galloway

与C和C++一样,OC也使用“头文件”(header file)与“实现文件”(implementation file)来分离代码。用OC语言编写的“类”(class)的标准方式为:以类名做文件名,分别创建两个文件,头文件后缀用.h,实现文件后缀用.m。创建好一个类以后,其代码看上去是这样的:

用OC语言编写任何类几乎都需要引入Foundation.h。如果不在该类本身引入这个文件的话,那么就要引入其父类所属框架相对应的“基本头文件”(base header file)。例如,在创建iOS应用程序时,通常会继承UIViewController类。而这些子类的头文件需要引入UIkit.h。

现在看来,EOCPerson类还好,虽然其头文件引入了整个Foundation框架。如果此类继承自Foundation框架中的某个类,那么EOCPerson的使用者(consumer)可能会用到其基类中的许过内容。继承UIViewController的那些类也是如此,其使用者可能会用到UIKit中的大部分内容。 继续阅读@class与#import