lihei12345


  • 首页

Swift实践初探

发表于 2016-08-16   |   分类于 iOS   |  

常用框架

CocoaPods

Swift + Pods

Cocoapods从0.36开始通过动态库的形式提供对Swift项目的支持。我们只需要在Podfile中注明,use_frameworks!即可,由于需要支持动态库,所以要求项目的最低版本是iOS 8。Pods采用动态库的主要原因是因为目前iOS操作系统中并没有内置Swift的运行时支持库,这种情况下所有包含Swift的代码无法被编译为静态库,Swift的运行时支持都是通过dylib的形式嵌入的。我们可以解压IAP安装包,在 frameworks 目录下,可以看到Swift相关的dylib:

参考:

  • https://blog.cocoapods.org/CocoaPods-0.36/
  • https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html

问题

大多数第三方库,比如腾讯/微信/微博的SDK都是静态库,虽然这些库提供了Pod的支持,但是在Swift中并没有办法使用。只能把这些第三方库拖入到项目中,然后通过bridge header在项目中使用。比较隐蔽的是腾讯的SDK,虽然也是framework,但是实际上是静态库。

但是不包含静态库的第三方库,比如SDWebImage,却是可以通过Pods直接引用的,Pods会将代码编译为一个动态库,然后集成到App中。在项目中使用的时候,也只需要简简单单通过 import SDWebImage 即可,这里面的编译关系都会通过module map进行映射管理。

目前国外的一些比如Fabric SDK,是提供了动态framework的,可以直接在Pods中使用,国内目前我还没有看到。。。leancloud说是提供了,但是打包的时候会坑死你,老老实实用静态库。

参考:

  • https://forum.leancloud.cn/t/sdk-app-store/4516
  • https://forum.leancloud.cn/t/invalid-provisioning-profile/3947
  • https://fabric.io/kits/ios/crashlytics/install

关于modules

Swift’s access control model is based on the concept of modules and source files.

代码的访问权限控制在开发中是一个非常重要的手段,我们需要隐藏实现细节,只暴露一些特定的interface给特定的调用者,比如Java中的 public/private/protected。在Swift中,访问权限的控制是基于modules和源文件的,public/internal/private。源文件较为容易理解,module则是比较含糊的概念,官方文档中的解释如下:

A module is a single unit of code distribution—a framework or application that is built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.

具体来说,对用到Xcode中就是编译target:Each build target (such as an app bundle or framework) in Xcode is treated as a separate module in Swift.

而这个module的概念实际上来源于clang module,在2012年由Apple工程师提出的概念,用于把传统的C/C++中的include进行简化:Modules provide an alternative, simpler way to use software libraries that provides better compile-time scalability and eliminates many of the problems inherent to using the C preprocessor to access the API of a library.

通过这个方式,Swift中引用其他类库的时候,只需要使用import即可,不需要再包含一大堆头文件。如果在一个module内部,编译器会自动处理所有的引用关系,不需要再像OC中引用一大堆繁琐的头文件。在Swift 3.0中,又引入了package manager,跟node/golang中的依赖管理非常类似,但是基本原理应该也是基于module。

参考:

  • http://clang.llvm.org/docs/Modules.html
  • http://andelf.github.io/blog/2014/06/19/modules-for-swift/
  • https://spin.atomicobject.com/2015/02/23/c-libraries-swift/
  • http://nsomar.com/modular-framework-creating-and-using-them/
  • http://adriansampson.net/blog/llvm.html
  • http://blog.csdn.net/column/details/xf-llvm.html
  • http://swift.gg/2016/01/13/swift-ubuntu-x11-window-app/

图片

目前开源的较为流行的图片框架包括SDWebImage和Kingfisher,大致对比了一下SDWebImage与Kingfisher的源代码,发现二者的实现思路基本一致,Kingfisher某种程度上来说是SDWebImage的Swift版本。二者共有的特性:

  • 通过category机制对业务提供API
  • 二级缓存机制,memory缓存使用NSCache;disk缓存使用url MD5之后作为key缓存
  • 网络请求使用NSURLSession
  • 后台线程进行图片解码
  • 所有操作异步化,包括下载,缓存管理,解码
  • 支持GIF
  • 缓存使用serial queue实现

不同点:

  • 下载队列,SDWebImage通过NSOperation进行管理,最大并发请求数为6;Kingfisher没有相关控制
  • 图片下载cancel机制,需要手工调用。这个在快速滑动的列表中,是个非常有用的机制。同时对于偶尔某些超大的图片,这个机制也非常有用;请求数量过多会占用过多的端口和系统资源,尽量减少并发的请求数是非常有意义的。
  • Kingfisher不支持WebP
  • Kingfisher业务开发调用和交互更加友好,配置灵活,例如支持加载完毕的动画效果,延迟加载

网络

OC环境下一般都是用AFNetworking,针对Swift,AF的开发者又开发了Alamofire用来取代AF。AF 3.0与Alamofire本身都是基于NSURLSession开发,虽然相对于AF2.0而言代码量减少了很多,但是读起来我自己感觉并不AF2.0结构清晰,AF2.0对runloop和NSOperation的使用,让整个框架变得非常易读。但Alamofire相对AF3.0而言,无论是API还是内部代码,都要更加清晰,这要感谢Swift的语法。比如枚举,extension+protocol都让代码更加容易阅读和维护。

还有一个更重要的地方,Alamofire跟RxSwift的结合非常容易实现,并且会让请求更加容易控制,几乎不需要额外的封装,就可以作为整个项目网络架构存在。

参考:

  • https://github.com/Alamofire/Alamofire
  • https://github.com/AFNetworking/AFNetworking

FRP

RxSwift or RAC?

最开始接触FRP是通过RAC来接触的,但是这个过程相当痛苦,我个人的理解主要原因是RAC并没有完全遵循Rx的理念,发明了Signal这个概念,导致整个学习曲线非常陡。再加上OC的语法限制,当使用非常多高阶函数处理signal时,代码的书写和阅读都非常困难,Swift回归正常的调用语法之后,链式调用让代码读起来感觉舒服很多。

关于ReactiveX,官方网站上的解释是,ReactiveX is a combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming。ReactiveX是观察者设计模式,迭代器设计模式,和函数式编程的结合,理解这个更加有助于我们更好使用整个框架。如果更多的学习资料和高阶函数的数据流示意可以参考Rx的官方网站:http://reactivex.io/。

关于RX的学习资料,我目前看到最好资料是官方网站的几篇文章和RxJava Essentials,这本书上给出的关于FRP的解释我认为是我见过最清晰和容易理解的:Reactive programming is a programming paradigm based on the concept of an asynchronous data flow. A data flow is like a river: it can be observed, filtered, manipulated, or merged with a second flow to create a new flow for a new consumer.。Rx为什么好用,看了这本书和官方网站上的文章之后,我觉得有了一个比较直观的解释。简单来说,通过Rx,我们可以使用类似Iterator(例如数组,集合)的操作方式,来处理类似网络请求和UI事件这样的状态,将事件(例如网络请求)的处理简化到跟常量一样容易处理。

相对于RAC,Rx概念更加清晰。data flow类似:Observables -> Operator -> Observers,Observables生产实体,Operator进行变换,Observers消费实体。数据流的上游是Observables,中间经过进过Operator变换产生新的Observables,直到到达下游Observers。还有一个比较特殊的存在,Subject,既是Observables又是Observers。

具体实现来说,在Swift中,我们使用的时候一般都是通过closure获取数据流,比如onNext(),Observers则被RxSwift实现进行了隐藏,具体实现时这些传入的closure最终会被转换为AnonymousObserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private func reloadData() {
if disposable != nil {
disposable?.dispose()
disposable = nil
}
disposable = viewModel
.updateData()
.doOnError { [weak self] error in
JLToast.makeText("网络数据异常,请下拉重试!").show()
self?.refresher.stopLoad()
}
.doOnCompleted { [weak self] in
self?.refresher.stopLoad()
}
.subscribe()
}

总结一下,在Swift中,FRP我认为RxSwift是比较占优势的:

  • ReactiveX的文档相当全面,并且有各种Operator示意图用于帮助理解
  • RxSwift在整体概念上要比RAC清晰不少
  • ReactiveX的思想是跨平台的,学习其他语言的基本语法之后,可以做到 “learn once, write everywhere”,这个非常有诱惑力。比如RxJS/RxJava/Rx.NET/Rx.Scala/RxCpp。

Hot vs Cold & Side Effect

从概念上来说,hot observable与cold observable挺好理解的。我一直比较困惑的是在RxSwift使用的过程中,到底哪些是hot的哪些是cold的。一般来说,我们使用Observable.create创建的Observable,即AnonymousObservable,都是cold observable,只有被subscribe的时候才开始emitting items;hot observable,一个比较典型的例子是Variable,无论是否被subscribe,在value发生变化的时候,都会emitting items。

所以一般来说,我们通过自己创建的Observable来处理状态的时候,面对的都是cold observable。RxSwift中这个概念在实现上没有跟RAC 4.0一样做区分,在使用的过程中,需要根据实际情况注意。

关于Side Effect,简单来说,我们使用Observable.create创建Observable时会传入一个block,这个block在observable被subscribe的时候会被执行,这个就叫做side effect,所以一般来说只有cold observable才会有side effect。这个地方如果不注意,会带来一些意想不到的状况。比如说我们使用Observable封装HTTP请求时,每次这个Observable被subscribe,都会触发一次HTTP请求,所以如果被多次subscribe的话,会导致多次网络请求。无论RAC或者RxSwift都会有这个问题,解决方式也都基本一样,使用subject进行multicast。

参考:

  • https://github.com/ReactiveX/RxSwift/blob/master/Documentation/HotAndColdObservables.md
  • http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-3.html
  • http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html
  • https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift

其他框架

Mantle

OC中常用的框架是Mantle,Mantle的核心代码我之前越过一遍,从工程的角度讲非常不错,可以参考我之前的博客:http://blog.csdn.net/colorapp/article/details/50277317。Mantle的实现核心是通过runtime获取属性列表,然后通过 KVC + try/catch 设置value,但是由于Swift是静态语言,这些hack技术都讲无法使用。在Swift中的ORM框架一般都是通过操作符重载来实现ORM过程的简化,我并没有阅读过这种类型框架的源代码,但是就使用感觉而言,并不比Mantle复杂,甚至重载之后的操作符用起来比Mantle中的字典更加安全和可读性更高。

Masonry

还有另外一个经常使用的框架就是Masonry,之前也阅读过它的源代码,参考之前的博客,http://blog.csdn.net/colorapp/article/details/45030163。我认为它的核心是利用Dot Notation实现的链式调用以及巧妙的DSL,极大简化了Auto Layout的开发。Swift中,这个库的作者也开发对应的实现:SnapKit。SnapKit的使用与Masonry的差别并不大,使用起来并不会有任何陌生。源代码暂时还没有阅读,不好对内部的实现进行对比。

无论是Masonry还是SnapKit,都有一个非常需要注意的地方 update constraints和remake constraints的区别,以及update到底update了什么,什么时候可以用update什么时候不可以。

update constraints首先会比对两个constraints是否相等,如果相等,只更新NSLayoutConstraint的constant字段。不相等会重新添加新的constraints。所以当constraints可能根据交互或者内容的时候,使用constraints需要谨慎,很可能就造成约束冲突。这部分查看Masonry的MASViewConstraint的- (void)install和- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint方法。

业务开发

源文件

  • 在Objective-C开发中,我个人比较推荐单文件单类,因为类的声明是两部分,这样可以让代码的可读性更高,并且可能会造成比如说命名冲突;
  • 而在Swift中,代码访问控制更加高级,将多个类组织在一个文件中,代码阅读起来会更加方便一些,并且类名与文件名可以不需要相同
  • 同时,可以利用Swift的Module和Pods的Dynamic framework的实现,将项目更合理进行架构划分,让模块关系变得清晰

Controller

庞大的业务Controller一直是业务开发过程中难以解决的痛点问题。有几种通用的手段:

  • MVCS或者MVVM,说一下我自己的理解,两者的共同点都是将数据逻辑从VC中抽取到单独的类中,比如Service或者MVVM,区别只是数据流的方向不一样
  • 抽象较为通用的业务逻辑服务化,具体来说就是抽象为较为独立的逻辑为Service,VC直接调用这些Service暴露的接口即可实现某种逻辑,比如账户/分享/定位/数据持久化等
  • 基于网络框架提供更高层次的封装,为业务层提供更加简洁的API,例如ORM框架/网络框架的二次封装/Masonry这种UI框架
  • 抽取较为通用的View作为项目通用的UI库,例如上下拉组件/Alert组件/ViewPager/下拉框等基本组件,甚至可以解耦发布为pod私有库
  • 维护项目中较为通用的工具类,这个在iOS中有个非常有效的技术手段,Objective-C中的category,Swift中的extension。例如:UIImage的基本处理(缩放/截图)/UIView相关/JSON的处理等。这个积累到一定量之后,能大大提高开发效率以及代码的可读性

在Swift中,可以合理利用extension将VC的代码模块化分割,将代码更加合理分布。例如,Eureka的FormViewController的分割:

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
extension FormViewController : UITableViewDataSource {
//MARK: UITableViewDataSource
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return form.count
}
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return form[section].count
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
form[indexPath].updateCell()
return form[indexPath].baseCell
}
public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return form[section].header?.title
}
public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return form[section].footer?.title
}
}

关于更多的代码实践,由于我目前只是业余时间写了两周Swift,并没有更好的实践可以分享,这部分刚开始,应该来说,best practice还都处于总结中。

MVVM/MVP/Redux/Protocol-Oriented Programming

在Objective-C开发中,外卖通过广泛使用 MVVM + RAC 来对膨胀的业务逻辑进行控制,效果相当不错,开发效率和代码质量都有不错的提升。得益于Swift的现代语法,我们有了更多的选择,通过这些编程范式,可以让复杂的业务逻辑开发得以简化。MVVM/MVP这里不多说,可以参考的博客有很多,大家应该都有不少接触,下面介绍一下Redux和面向协议编程。

Redux

FB创造React时提出了Flux数据流概念,意在通过单向数据流和不可变状态简化前端状态的管理,但是FB本身对Flux的实现过于复杂,反而是第三方基于Flux提出的Redux,工程上来说更加简单实用。Redux既可以认为是Flux的具体实现也可以认为是Flux的简化,在实际的 React.js/ReactNative 项目中,大多数都是采用Redux进行日常开发。此外,Redux的文档质量相当高,对前端开发的各种理念都有一些挺不错的总结,可以参考我之前的学习笔记:Redux学习笔记。国外的工程师基于Redux实现了Swift版本ReSwift。我目前还没有实际中研究过ReSwift,但是就我之前对Redux的研究而言,发现Redux对于客户端开发并不适用,客户端大量的交互对Redux来说是个噩梦,大量的非模态交互,可能导致很多复杂的数据问题,比如要求请求随时可cancel。

Protocol-Oriented Programming

Protocol-Oriented Programming 是Apple在WWDC 2015发布Swift 2.0的时候推荐的编程范式,可以充分发挥Swift的语法优势,Apple认为这个是Swift的核心。详细大家可以看WWDC上相关的视频,Protocol-Oriented Programming in Swift。这里给大家简单介绍一下,我个人认为当前前端的开发效率的不足,主要的问题是相对后端来说,前端领域的状态比较复杂,很难抽象提取,而Apple推荐面向协议编程也是为了解决iOS开发效率的问题。传统OOP是有缺陷和不足的,下面这些来自 https://gist.github.com/rbobbins/de5c75cf709f0109ee95 :

  • Automatic sharing. Two classes can have a reference to the same data, which causes bugs. You try to copy things to stop these bugs, which slows the app down, which causes race conditions, so you add locks, which slows things down more, and then you have deadlock and more complexity. BUGS! This is all due to implicit sharing of mutable state. Swift collections are all value types, so these issues don’t happen
  • Inheritance is too intrusive. You can only have 1 super class. You end up bloating your super class. Super classes might have stored properties, which bloat it. Initialization is a pain. You don’t want to break super class invariants. You have to know what to over ride, and how. This is why we use delegation in Cocoa
  • Lost type relationships. You can’t count on subclasses to implement some method, e.g:
1
2
3
4
5
6
7
8
9
10
class Ordered {
func precedes(other: Ordered) -> Bool { fatalError("implement me") }
}
class Number: Ordered{
var value: Int
override func precedes(other: Ordered) -> Bool {
return value < other.value //THIS IS THE PROBLEM! WHAT TYPE IS ORDRED?! Does it have a value? Let's force type cast it
}
}

我这里只做一个大概的介绍,详细的资料大家可以参考下面的参考资料。举个让我印象比较深刻的用法,Swift中的 protocol + extension 结合在一起,甚至可以实现类似其他语言中的traits特性 (比如最好的编程语言PHP),将代码的复用颗粒度缩小到某一部分逻辑抽象,不再受限于类继承。这里举个不一定特别合适的例子,比如,飞行这个特性,鸟可以飞行,飞机也能飞行,他们这部分逻辑是能够进行抽象复用的,而鸟和飞机之间却很难抽象到一个基类:

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
// 定义protocol
protocol Flyable {
var speed: Double { get }
}
// 为protocol通过extension添加方法实现
extension Flyable {
func fly() {
print("\(self) fly: \(speed) km/h")
}
}
// 复用飞行逻辑
class Bird: Flyable {
var speed: Double = 5
}
class Plane: Flyable {
var speed: Double = 500
}
func play() {
let bird = Bird()
bird.fly()
let plane = Plane()
plane.fly()
}

输出:

1
2
playswift.Bird fly: 5.0 km/h
playswift.Plane fly: 500.0 km/h

举个更加实际的例子,错误页面的统一处理,来自 http://krakendev.io/blog/subclassing-can-suck-and-heres-why :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protocol ErrorPopoverRenderer {
func presentError(message message: String)
}
extension ErrorPopoverRenderer where Self: UIViewController {
func presentError(message message: String) {
//Add default implementation here and provide default values to your Error View.
}
}
class CustomViewController: UIViewController, ErrorPopoverRenderer {
func failedToEatHuman() {
//…
//Throw error because the Kraken sucks at eating Humans today.
presentError(message: "Oh noes! I didn't get to eat the Human!") //Woohoo! We can provide whatever parameters we want, or no parameters at all!
}
}

参考:

  • https://developer.apple.com/videos/play/wwdc2015/408/
  • https://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2
  • http://krakendev.io/blog/subclassing-can-suck-and-heres-why
  • https://gist.github.com/rbobbins/de5c75cf709f0109ee95
  • http://matthewpalmer.net/blog/2015/08/30/protocol-oriented-programming-in-the-real-world/

转变思维

  • Protocol
  • 泛型
  • Extension
  • Enum
  • Optional
  • 错误处理
  • Swift的class与NSObject
  • 避免OC的黑魔法

参考:

  • http://iosre.com/t/rxswift-runtime-oc-ios/3727

坑

  • 编译 & 打包 速度
  • Xcode莫名其妙crash
  • 安装包大小
  • 语法变化太快,不能做到向下兼容

Hexo博客搭建

发表于 2016-07-15   |  

WordPress搭建博客的方式不太适应技术记录与分享,搭建复杂,需要自己的主机进行Host。而一般的技术博客,只需要静态网页就能满足需求。目前比较流行的是Hexo与Jekyll,后者的搭建过于复杂,所以目前Hexo较为流行。目前几家做的比较好的团队技术博客:

  • 美团点评技术团队
  • 微信移动客户端开发团队: WeMobileDev
  • 微信阅读团队技术博客
  • 腾讯Bugly,微信公众号: 腾讯Bugly
  • 百度Hi技术周报
  • 饿了么用户端技术博客
  • 移动开发前线
  • 微信公众号:QQ空间终端开发团队

github.io

首先,创建github.io,用来host我们要生成的静态博客。创建的方式非常简单,参考:https://pages.github.com/

Hexo安装

Hexo是基于Node.js构建的,使用npm进行安装管理非常便捷:https://pages.github.com/。

整体的安装与使用官方文档讲解的非常清晰,参考文档能快速完成搭建工作。在搭建的过程中,有几点需要注意一下:

1. 分类与标签

这点与WP这种动态网站不同,不需要额外创建分类与标签,只需要在post的文档顶部配置 categories 与 tags 即可,hexo在构建静态网页的时候,会自动进行提取归类。例如:

1
2
3
4
5
6
7
8
---
title: Modern PHP -- 读书笔记
date: 2016-07-14 18:40:28
tags:
- PHP
categories:
- Jason
---

对于团队技术博客而言,categories可以设置为团队成员的名称,这样就能比较好归纳。

2. 主题

设置

我采用的主题与微信阅读团队的一致,Next Mist主题。具体的搭建过程可以参考:

  • https://github.com/iissnan/hexo-theme-next
  • http://theme-next.iissnan.com/getting-started.html

评论设置

采用disqus评论系统,设置过程也非常简单,可以参考:https://github.com/iissnan/hexo-theme-next/wiki/%E8%AE%BE%E7%BD%AE%E5%A4%9A%E8%AF%B4-DISQUS

3.部署

我才用的部署方式git,配置与使用都非常简单,一行代码就能编译然后部署博客静态网页:
https://hexo.io/zh-cn/docs/deployment.html

这里有个比较奇怪的问题,在修改post之后,deploy始终无法更新,总是提示 “nothing to commit…”,解决方式:https://github.com/hexojs/hexo/issues/67。调用下面三行命令清除缓存再部署:

1
2
3
1.rm -rf .deploy
2.hexo generater
3.hexo deploy

Modern PHP -- 读书笔记

发表于 2016-07-14   |   分类于 Server   |  

关于PHP,大家的误解比较多,但其实现代PHP是一门无论开发效率还是执行效率都相当高的编程语言。关于现代PHP的各方面特性,大家可以参考<Modern PHP>作者之前写的 PHP the right way,中文翻译:PHP之道。同时,作者也是比较流行的PHP框架 – Slim 的开发者。所以这本书非常值得已读,甚至你只需要懂一些OOP的概念即可,并不需要你懂PHP开发。

Part 1. Language Feature

Features

Namespaces

PHP命名空间使用 “\” 字符来分割sumnamespaces。与操作系统的物理文件系统不同,PHP命名空间是一个抽象概念,不必跟文件目录一一对应。大多数PHP Components都是根据PSR-4 autoloader standard来组织subnamespaces与文件目录的映射关系的。

从技术上来说,namespaces仅仅是一个PHP语言的符号,PHP解释器使用这个符号来作为一组classes/interfaces/functions/constants集合的前缀,仅此而已。

Namespaces are important because they let us create sandboxed code that works alongside other developer's code. This is the cornerstone concept of the modern PHP component ecosystem.

Helpful Tips

1. Multiple imports
bad:

1
2
3
4
<?php
use Symfony\Component\HttpFoundation\Request,
Symfony\Component\HttpFoundation\Response,
Symfony\Component\HttpFoundation\Cookie;

good:

1
2
3
4
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;

2. One class per file
这点不用多说,每个文件应该只有一个类!

3.Global namespace
如果我们引用一个没有命名空间的class/interface/function/constant,PHP会首先假设这个class/interface/function/constant在当前的命名空间中。如果在当前命名空间中没有找到,PHP才会开始resolve。而对于那些没有没有命名空间的代码,PHP认为他们存在于global namespace。

PSR-4

Code to an interface

An interface is a contract between tow PHP objects that lets one object depend not on what another object is but, instead, on what another can do.

Trait

A trait is a partial class implementation(i.e., constants, properties, and methods) that can be mixed into one or more existing PHP classes. Traits work double duty: they say what a class can do (like an interface), and they provide a modular implementation (like class).

相对Android开发,我最喜欢的iOS中一个特性就是category,PHP的trait就是有点类似于category,不过还是不太一样的:

  1. OC只能针对特定的类进行扩展,而PHP的trait可以将代码单元注入到任意的不相关的类中;
  2. 同时OC中的category并不能直接实现属性的扩展,而PHP的trait则能实现常量,属性,以及方法;
  3. PHP的trait跟OC的category根本上来说用途是不一样的,OC是对现存类直接扩展,不需要继承实现类。而PHP的trait需要在类的定义中使用 use 来明确。

跟class和interface的定义一样,on trait per file。

Generators

Generators are easy to create because they are just PHP functions that use the yield keyword one or more times. Unlike regular PHP functions, generators never return a value. They only yield values.

这个概念并不陌生,Python包括Swift都有这个特性,可以用在对大量数据的迭代中,动态去获取数据,而不是一次性生成,避免内存的浪费。

在每次迭代的过程中,PHP都会让Generator实例计算和提供下一个迭代值。在这个过程中,当generator执行到yield value的时候,generator会暂停它的内部状态的执行。只有generator被要求提供下一个迭代值的时候,它才会继续它的内部状态的执行。generator就这样反复pasuing 和 resuming,直到到达generator的函数定义的尾部或empty的时候,generator才会结束执行。

Generators are a tradeoff between versatility and simplicity. Generators are forward-only iterators.

Closures

A closure is a function that encapsulates its surrounding state at the time it is created. The encapsulated state exists inside the closure even when the closure lives after it original environment ceases to exist.

这里的闭包是指Closure和Anonymous functions。上面是作者对于闭包的解释,感觉非常准确,比我看到的大多数解释都要简单清晰。闭包在日常业务开发中非常有用,可以非常方便替换我们经常需要用到的delegate设计模式,不需要再去定义一个interface,然后再实现这个interface,再把对应的对象指针传递过去。而是通过Closure,只需要简简单单传递一段代码即可,这个极大简化了日常业务开发。所以目前iOS开发中,大家通常都会使用block来代替delegate设计模式。

PHP Closure or Anonymous function 跟PHP function的定义语法是一样的,但是实际上 Closure 的背后是Closure class的实例,所以Closure被认为是first-class value types。

Attach State : PHP的Closure不会automatically enclose application state,不像JavaScript/OC那样会capture作用域之外的变量。而是,you must manually attach state to a PHP closure with the closure object's bindTo() method or the use keyword.

需要注意的是,PHP closures是objects。而我们之所以能让一个closure 实例变量进行调用,是因为这个对象实现 __invoke() magic method,当我们在closure实例变量后面跟着一个 () 的时候,closure实例变量就会寻找并且调用__invoke() 方法,例如 $closure("Jason")。

同样,由于PHP closure是objects。所以,在closure内部我们也可以通过 $this 访问closure的各种内部状态,但是这个状态是非常boring。同时,closure的bindTo()方法可以有一些非常有趣的特殊用法,This method lets us bind a Closure object's internal state to a different object. The bindTo() method accepts an important second argument that specifies the PHP class of the object to which the closure is bound.This lets the closure access protected and private member variables of the object to which it is bound.。这个用法有点类似JavaScript的bind方法,可以改变Closure object的 $this 指针指向。

bindTo()这个有趣的用法,经常各种PHP框架的路由所采用,例如:

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
<? php
class App
{
protected $routes = array();
protected $responseStatus = '200 OK';
protected $responseContentType ='text/html';
protected $responseBody = 'Hello world';
public function addRoute($routePath, $routeCallback)
{
// 将Closure bind到App类上
$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
}
public function dispatch($currentPath)
{
foreach ($this->routes as $routePath => $callback) {
if ($routePath === $currentPath) {
$callback();
}
}
// 这里返回的state是在callback内修改过的
header('HTTP/1.1 '.$this.responseStatus);
header('Content-type: '.$this.responseContentType);
header('Content-length: '.mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
// 添加注册一个路由
<?php
$app = new App();
$app->addRoute('/users/josh', function() {
// 因为这个route是bindTo到App class上的,所以这里直接访问$this修改 App 的内部state
$this->responseContentType = 'application/json;charset=utf8';
$this->responseBody = '{"name": "Josh"}';
});
$app->dispatch('/users/josh');

Zend Opcache

从PHP 5.5.0开始,PHP引入了内置的bytecode cache支持,叫做 Zend OPcache。PHP解释器在执行PHP脚本的时候,会首先把PHP代码编译为Zend Opcodes (machine-code instructions),然后才会执行bytecode。在所有请求中,PHP解释器都需要这样处理所有的PHP文件,read/parse/compiles,然而我们可以通过把PHP文件预编为PHP bytecode来省略这个开销,这就是Zend OPcache。

Zend OPcache的使用非常简单,在我们配置之后,它就会在内存中自动缓存precompiled PHP bytecode,在可用的情况就会直接执行这个PHP bytecode,而不需要再去编译PHP代码。

具体配置去google吧,有一点需要注意的是,如果同时配置了 Xdebug的话,在php.ini文件中,需要在Xdebug之前加载Zend OPcache extension扩展。

Built-in HTTP server

PHP从5.4.0引入了内置的HTTP server,所以我们在不配置Apache或者nginx的情况下就直接预览PHP程序。

Remember, the PHP built-in server is a web server. It speaks HTTP, and it can serve static assets in addition to PHP files. It's a great way to write and preview HTML locally without installing MAMP, WAMP, or a heavyweight web server.

要使用内置的HTTP server非常简单,在工程的根目录下,执行下面的命令即可:

1
php -S localhost:4000

如果要让本地网络的其他设备访问PHP web server,我们可以这么启动:

1
php -S 0.0.0.0:4000

如果我们希望通过 PHP INI 配置文件去做一些特殊配置,可以通过下面命令来启动:

1
php -S localhost:8000 -c app/config/php.ini

我们也可以通过Router Scripts来实现一些特殊的路由需求,可以通过下面的命令启动:

1
php -S localhost:8000 router.php

在PHP代码中,我们可以通过php_sapi_name()来判断:

1
2
3
4
5
6
<?php
if (php_sapi_name() === 'cli-server') {
// PHP web server
} else {
// Other web server
}

Part 2. Good Pratices

Standards

PHP-FIG

PHP-FIG (PHP Framework Interop Group): The PHP-FIG is a group of PHP framework representatives who, according to the PHP-FIG website, "talk about the commonalities between our projects and find ways we can work together."

PHP-FIG是由很多不同PHP framework开发者组成的一个开放组织,他们提出的recommendations,不是标准或也不是要求,更像是best pratices的建议集合。不过,目前比较流行大多是PHP框架,比如Laravel或Symfony,都遵守了这些recommendations,所以这个感觉更像是Modern PHP事实上的标准,如果要使用PHP的很多工具和庞大的各种开源库,最好采用这个标准。

The PHP-FIG's mission is framework interoperability. And framework interoperability means working together via interfaces, autoloading, and style.
正如下面所说,PHP-FIG的使命就是不同framework之间的互通,让不同框架可以很容易结合在一起使用。而实现互通目前主要通过三个方面来入手:interfaces, autoloading, style:

  • Interfaces: Interfaces enable PHP developers to build, share, and use specialized components instead of monolithic frameworks,基于interfaces,我们可以做到直接使用某个框架的某个组件,比如Laravel的HTTP的处理部分就是直接使用 Symfony Frameworks的 symfony/httpfoundation 组件,而不用把整个Symfony都集成到Laravel之内。
  • Autoloading: PHP frameworks work together via autoloading. Autoloading is the process by which a PHP class is automatically located and loaded on-demand by the PHP interpreter during runtime,在autoloading标准出来之前,PHP组件和框架都是基于 \__autoload()或spl_autoload_register() 方法来实现自己独特的autoloaders,所以我们要使用一个第三方组件的时候,需要首先去研究一下它的autoloaders的实现。
  • Style: PHP frameworks work together via code style.

PSR

PSR是 PHP standards recommendation的缩写,是PHP-FIG提出的recommendations文档,例如PSR-1,PSR-2等。每个PHP-FIG recommendation都是为了解决某个大多数PHP框架开发中常遇到的问题而提出的。

目前在PHP-FIG的官方网站上,http://www.php-fig.org/psr/ ,可以看到所有的recommendations,目前被采用的有下面几个:

具体的PSR文档内容,可以参考官方网站,PSR-1/2/3/4 几个文档有中文翻译:

文档 原文 中文翻译
PSR-1 Basic Coding Standard https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md https://segmentfault.com/a/1190000002521577
PSR-2 Coding Style Guide https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md https://segmentfault.com/a/1190000002521620
PSR-3 Logger Interface https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md https://segmentfault.com/a/1190000002521644
PSR-4 AutoLoading Standard https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md https://segmentfault.com/a/1190000002521658

PSR-1 Basic Coding standard

PHP tags : 使用 <?PHP ?> or <?= ?>
Encoding: 编码必须使用UTF-8
Objective: 单个PHP文件只能定义symbols (a class, trait, function, constant, etc.) 或 perform某种有side effects的action (e.g., create output or manipulate data)。单个文件不能同时包含两种代码
Autoloading: PHP namespaces和classes必须支持PSR-4 autoloader standard
Class names: PHP类名必须是驼峰命名
Constant names: 常量必须使用大写,然后以下划线分割,例如 GREAT_SCOTT
Method names: PHP方法命名也必须采用驼峰命名,首字母小写

PSR-2 Strict Code Style

Implement PSR-1 : 要求必须采用PSR-1
Indentation: 采用4个空格字符作为缩进
Files and lines : 必须使用Unix linefeed(LF)作为结尾;文件最后必须以空行作为结束;不能使用尾部 ?> PHP tag;每行尽量不要超过80个字符,最多不能超过120个字符;每行结尾不能包含空格;
Keywords: 所有的PHP关键字都必须小写
Namespaces: 每个namespace声明后面都必须跟着一个空行;使用use来import or alias namespaces的时候,必须在use声明后面跟一个空行;
Classes: 定义类时,开始的大括号(opening bracket)必须新起一行,结束的大括号(closing bracket)必须在类体定义后面新起一行;extents/implements关键字必须跟在类名定义的后面;例如:

1
2
3
4
5
<?php
namespace My\App;
class Administrator extends User {
// Class definition body
}

Methods: 方法定义的大括号规则与类定义类似,opening brackets和closing brackets都必须新起一行。
Visibility: 对类中定义的全部property和method都必须声明可见性(visibility),可见性是public, protected, private其中的一个;abstract / final 必须写在visibility之前;static必须写在visibility之后;
Control structures : 所有的control structure keyword (if/elseif/else/switch/case/while/do while/for/foreach/try/catch)的后面都必须一个空字符;大括号的规则与class定义不同,opening brackets跟control structure keyword必须在同一行,而closing bracket必须另新一行;

我们可以通过IDE的格式化工具来让代码格式化,实现PSR-1和PSR-2的code style。我经常使用的工具PHPStorm就可以设置。还有一些其他工具,比如 PHP-CS-Fixer 或 PHP Code Sniffer

PSR-3 Logger Interface

PSR-3 is an interface, and it prescribes methods that can be implemented by PHP logger components.

PSR-4 Autoloaders

An autoloader is a strategy for finding a PHP class, interface, or trait and loading it into the PHP interpreter on-demand at runtime. PHP components and frameworks that support the PSR-4 autoloader standard can be located by and loaded into the PHP interpreter with only one autoloader.

关于PSR-4,看官方文档之后感觉理解很困惑,本书的作者的解释就非常简洁:
The essence of PSR-4 is mapping a top-level namespaces prefix to a specific filesystem directory.,简单来说,就是设定了一个namespaces前缀和某个特定的文件目录之间的映射关系,然后在这个namespace前缀之下如果还有更多的sub namespace,这些sub namespaces就会跟这个特定的目录下面的子目录一一映射起来。例如,\Oreilly\ModernPHP namespace与 src/ 物理路径一一映射,那么\Oreilly\ModernPHP\Chapter1对应的文件夹就是src/Chapter1,而\Oreilly\ModernPHP\Chapter1\Example类对应的文件路径就是src/Chapter1/Example.php文件。

PSR-4 lets you map a namespace prefix to a filesystem directory. The namespace prefix can be one top-level namespace. The namespace prefix can also be a top-level namespace and any number of subnamespaces. It's quite flexible.

Components

Components

Modern PHP is less about monolithic framework and more about composing solutions from specialized and interoperable components.

What Are Components?: A component is a bundle of code that helps solve a specific problem in your PHP application.

框架与Components:如果我们正在创建一个小项目,可以直接使用一些PHP Components集合来解决问题;如果我们正在进行一个多人合作开发的大项目,我们可以通过使用一个Framework;但这都不是绝对的,应该根据具体问题来解决。

Packagist:跟其他语言的包管理机制一样,例如Maven,也有一个网站 https://packagist.org/ 让我们搜索我们需要的PHP Components的相关信息。总所周知的原因,Packagist在国内很不稳定,可以使用国内的全量镜像来代替,http://www.phpcomposer.com/ 。

Composer

Composer is a dependency manager for PHP components taht runs on the command line,跟其他现代语言一样,PHP使用Composer来做依赖管理,类似的有iOS中的Cocoapods,Android中的Maven/gradle,前端的npm,ruby的gem,这些工具可以大大简化我们管理第三方库的成本。于是,当我们在Packagist上面找到我们需要的Components之后,就可以通过Composer来使用这个库。

当我们使用Composer来添加第三方Component的时候,Composer除了会自动帮我们下载需要的PHP Components之外,还会自动帮我们创建一个符合PSR-4的Autoloader。

跟Cocoapods类似,Cocoapods使用Podfile来指定需要依赖的第三方库,以及保存有当前使用的具体的第三方库的版本号。所以我们需要把这两个文件都加入到版本控制中进行管理,确保不同成员/CI/开发环境等不同地方大家使用第三方库版本的一致性。对应Composer中的文件就是 composer.json以及composer.lock。这里需要注意的是composer install 和 composer update 命令的差别:

  • composer install,不会安装比composer.lock中列出的更高版本的Components;
  • composer update,会更新你的components到最新的稳定版,同时也会更新composer.lock文件为最新的PHP components版本号。

Semantic Versioning

Modern PHP Components 使用 Semantic Versioning scheme,同时包含了用小数点(.)分隔的三个数字,比如 1.13.2。同时,这个也是很多其他语言开源库的版本规则,对这个一直比较好奇,终于在Modern PHP中看到了相应的解释。

  • major release number:第一个数字是major release number,只有当PHP Component发生不再向前兼容的更新时,才需要增加这个版本号。
  • minor release number:第二个数字是minor release number,当PHP Component发生一些小的功能更新,并且没有破坏版本兼容时,增加这个版本号。
  • patch release number:最后一个数字是patch release number,当发生版本兼容的bug修复的时候,增加这个版本号。

Create PHP Components

这部分跟iOS创建自己的spec非常相似,并不是非常复杂的问题,参考书或者官方文档很容易就能发布

lihei12345

lihei12345

3 日志
2 分类
2 标签
© 2016 lihei12345
由 Hexo 强力驱动
主题 - NexT.Mist