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)

iOS集成Braintree, 支持credit card, PayPal支付(sandbox环境)

如果客户端只支持Paypal,那么需要import PayPal Mobile SDKs,但在v.zero以后(https://developer.paypal.com/docs/bt-vzero-overview/),可以集成多种支付,不需要分别import各自的SDK,就像是统一了行业标准,对开发者来说是件好事情,而且如果使用drop-in SDK自带的UI,开发起来会很省心,不需要自己写界面,一个底部弹出框包含所有支付内容。所以,趁我还没酒精过量,赶快写下v.zero的工作原理,还有Server和Client联调的demo。

看到官方文档(https://developers.braintreepayments.com/start/overview),首先先讲两个重要的点:1.Client token,2.Payment method nonce

Client token

是服务端通过Braintree的SDK生成的,比如我要用Rails5来开发,服务端,那么我需要的SDK是(gem “braintree”, “~> 2.72.0”),用它来生成token
这三个参数是生成token的依据,当你注册一个新的账户(https://www.braintreepayments.com/en-ca/sandbox)
给下截图,Account->My User->View Authorizations->API Keys->view,注意那三个参数,mark等下server会用到。


Rails配置代码:

记下以后继续,然后客户端会依据这个client token直接和Braintree的服务器通信,所以这是一个认证令牌(These should not be reused; a new client token should be generated for each request that’s sent to Braintree.)而且为了安全,每次客户端和Braintree服务器的直接通信都需要新的token,对应于每一个支付request。就像APNs当中,注册token,然后拿着token直接和apple服务器通信,一个道理,只不过一个是OS级别的,一个是第三方服务,那OS级别的无非是依赖于Apple ID这个统一的账户系统,本质是一样的对吧。

Payment method nonce
直接翻译叫做:支付方法随机数。其实就是一个字符串,代表一次支付方法的调用,然后服务端,获取这个字符串,通过Server的SDK,对Braintree服务器发起新的事务请求
这个东西其实在客户端的callback方法中有,是Braintree服务器直接下发的,我们的服务器不管这个类型。附上解释原理的那张经典图片:

看下overview的文档,图片下的5个steps,讲得很清楚
以上原理,现在到实践 继续阅读iOS集成Braintree, 支持credit card, PayPal支付(sandbox环境)

在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,打开终端运行

 

多用派发队列,少用同步锁

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

在OC中,如果有多个线程要执行同一份代码,那么有时可能会出现问题。这种情况下,通常要使用锁来实现某种同步机制。在GCD出现之前,有两种方法,第一种是采用同步块(synchronization block):

这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。执行到这段代码结尾处,锁就释放了。在本例中,同步行为所针对的对象是self。这样写通常没错,因为它可以保证每个对象实例都能不受干扰地运行其synchronizedMethod。然而,滥用@synchronized(self)则会降低代码效率,因为公用同一个锁的那些同步块,都必须按顺序执行。若是在self对象上频繁加锁,那么程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码,这样做其实并没有必要。

另一个方法是直接使用NSLock对象:

也可以使用NSRecursiveLock这种”递归锁”(recursive lock),线程能够多次持有该锁,而不会出现死锁(deadlock)现象。
这两种方法都很好,不过也有其缺陷。比方说,在极端情况下,同步锁会导致死锁,另外,其效率也不见得很高,而如果直接使用锁对象的话,一旦遇到死锁,就会变得非常麻烦。 继续阅读多用派发队列,少用同步锁

NSTimer会保留其目标对象

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

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

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

iOS如何查看Apple的私有API

把UIView替换成想要查看的类,切记不能在要提交到apple store的应用内对私有API进行调用,会被拒

精简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的作用