知识储备
就像从零搭建OC项目Service支撑一样,要在一定量知识储备的情况下才可以让框架质量得到保证。 Service:项目支撑服务。每个公司的项目都会依赖一定量的组件,每一个组件作为项目的独立模块提供本身的功能供使用。一般来讲,大公司的项目均为组件化项目,一方面为了方便安排开发,另一方面方面集成管理与扩展。小公司的项目则是依赖某一个私有库作为项目的支撑。建议每一个项目都按照组件化项目的思想来从零构造,便于以后核心服务代码抽出进行组件化拆分。
Swift知识储备与框架需求
知识储备即熟悉Swift编码思想,基础知识,进阶知识与细节知识,这里不做赘述。编码能力不能做衡量,即便知识储备不够,也可以慢慢的学习。给出的建议是:多阅读源码,不是大型开源项目的源码,而是小而精的功能性项目源码。
框架需求:按照开发步骤
- [ ] 环境管理 ModeSwitch
控制当前环境,开发,预发,正式
- [ ] 日志模块-基础日志 Console
完成基础日志模块,日志打印即可
- [ ] 功能扩展 Foundation
Foundation常用的扩展
- [ ] 界面扩展 UIKit
UIKit常用的扩展
- [ ] 权限服务 Permission
获得应用程序权限
- [ ] 组件处理/链接模块 Plug-in
组件化方案,组件加载,组件衔接(Router)
- [ ] 数据模块 DataBase
数据持久层,暂定使用数据库
- [ ] 网络模块 Network
网络层,网络请求,缓存控制,图片缓存等
- [ ] 日志模块-日志上报 Console
完成日志上报功能(一般为奔溃上报)
- [ ] 推送服务 Push
远程推送与本地推送服务
- [ ] 位置服务 Location
位置获取服务
- [ ] 数据收集服务 Monitor
数据打点与用户信息上报等
规范、知识 (仅了解基础的开发者阅读<知识回顾>)
别急着入手写代码,先制定一系列的规范,了解一些知识。
规范
Swift版本:3.0
Xcode版本:8.0+
支撑版本:iOS 8.0+
缩进: 2
单例写法与命名规范:
闭包预定义:
函数参数与规范:
使用 _来忽略参数描述,使用@discardableResult来标记该函数可以忽略返回值,尽量不要使用关键字。尽量避免函数复杂程度(不要写内部函数等)。命名明确,可选参数要写默认值。
注释规范:
1// MARK: Initializing
/// Returns a matched view controller from a specified URL.
///
/// - parameter url: The URL to find view controllers.
/// - returns: A match view controller or `nil` if not matched.
MARK、TODO等使用//开头,默认注释使用///开头。
好的规范可以提高代码的可读性与统一性
上边只是一些简单的规范。还有很多好的建议,我们慢慢聊。
知识
- 空值合并
a ?? b 中的 ?? 就是是空值合并运算符,会对 a 进行判断,如果不为 nil 则解包,否则就返回 b 。使用的时候有以下两点要求:
- a 必须是 optional 的
- b 必须和 a 类型一致
当然如果你觉得这样可读性不高,我们可以自定义运算符来实现
infix operator |->| {
}
func |->| (left: T?, right: T) ->T {
if let l = left {
return l
}
return right
}
var a: String?
var b = "Hello world!"
var c = a |->| b
- 关于字符串
String传递的时候会进行拷贝,NSString通常为引用,使用countElements计算String数量与同样NSString.length计算的结果可能不同,因为 length 利用的是 UTF-16 类型的值,而不是 Unicode 字符。比如 emoji 表情加进去之后,UTF-16 算的结果是2,而 Unicode 的计算结果是1。
- 数组
除了普通的初始化方法,我们可以通过 init(count: Int, repeatedValue: T) 来初始化一个数组并填充上重复的值:
// [0.0, 0.0, 0.0]
var threeDoubles = [Double](repeating:0.0, count:3)
按某种规则获得新数组,使用map方法
var oldArray = [10, 20, 30, 40]
var newArray = oldArray.map { (money: Int) in
"¥\(money)"
}
print(newArray)
当然也可以简写
var newArray = oldArray.map({ "¥\($0)" })
数组过滤:
var oldArray = [10, 20, 45, 32]
var filteredArray : Array = []
for money in oldArray {
if (money > 30) {
filteredArray += [money]
}
}
print(filteredArray)
不过这个方法看起来很笨重,我们使用新方法
var filtered = oldArray.filter {
return $0 > 30
}
计算数组的和
let array: Array = [10, 20, 30, 40]
var result = array.reduce(0, +)
- 解包技巧:
func add(_ num: Int?) ->Int? {
if let n = num {
return n + 1
} else {
return nil
}
}
add(1)
add(nil)
// -> Map
func madd(_ num: Int?) ->Int? {
return num.map {n in n + 1}
}
madd(1)
madd(nil)
字符串也可以进行容错
func madd(_ s: String?) ->String {
return s.map{
sss in "Hello \(sss)!"
} ?? "default value"
}
madd("c")
madd(nil)
- Switch中巧用where
let point = (1, -1)
switch point {
case let (x, y) where x == y:
print("1")
break
case let (x, y) where x == -y:
print("2")
break
default:
break
}
fallthrough - 在 switch 中,将代码引至下一个 case 而不是默认的跳出 switch。不过要注意的是,fallthrough不能用在定义了变量的case内,只能用在直接的判断中
- 控制流语法大全
// for loop (array)
let myArray = [1, 1, 2, 3, 5]
for value in myArray {
if value == 1 {
println("One!")
} else {
println("Not one!")
}
}
// for loop (dictionary)
var dict = [
"name": "Steve Jobs",
"title": "CEO",
"company": "Apple"
]
for (key, value) in dict {
println("\(key): \(value)")
}
// for loop (range)
for i in -1...1 { // [-1, 0, 1]
println(i)
}
// use .. to exclude the last number
// for loop (ignoring the current value of the range on each iteration of the loop)
for _ in 1...3 {
// Do something three times.
}
// while loop
var i = 1
while i < 1000 {
i *= 2
}
// do-while loop
do {
println("hello")
} while 1 == 2
// Switch
let vegetable = "red pepper"
switch vegetable {
case "celery":
let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
let vegetableComment = "Is it a spicy \(x)?"
default: // required (in order to cover all possible input)
let vegetableComment = "Everything tastes good in soup."
}
// Switch to validate plist content
let city:Dictionary = [
"name" : "Qingdao",
"population" : 2_721_000,
"abbr" : "QD"
]
switch (city["name"], city["population"], city["abbr"]) {
case (.Some(let cityName as NSString),
.Some(let pop as NSNumber),
.Some(let abbr as NSString))
where abbr.length == 2:
println("City Name: \(cityName) | Abbr.:\(abbr) Population: \(pop)")
default:
println("Not a valid city")
}
- 函数
可选的参数请提供默认值
func add(_ a: Int = 1, _ b: Int = 2) ->Int {
return a + b
}
add(3) // 3 + 2
可变参数
func add(nums: Int ...) ->Int {
return nums.reduce(0, +)
}
add(nums: 1, 2, 3, 4, 5) // 15
如果不止一个参数,需要把可变参数放在最后,否则会报错。
通过函数修改原始值需要 inout :
func add( v:inout Int) {
v = v + 1
}
var a = 1
add(v: &a)
泛型函数:
func value( value1:inout T, oldValue: inout T) {
let temp = value1
value1 = oldValue
oldValue = temp
}
var n1 = "mr r"
var n2 = "ms s"
value(value1: &n1, oldValue: &n2)
print(n1)
print(n2)
var i1 = 1
var i2 = 2
value(value1: &i1, oldValue: &i2)
print(i1)
print(i2)
变量:
func functionAdd(_ a: Int) ->Int {
return a + 2
}
let funcalias = functionAdd
funcalias(2)
函数既然是类型的一种,那么显然也是可以作为参数传递的:
func addFunc(_ a: Int) ->Int {
return a + 3
}
func withFunc(addingFunc: (Int) ->Int, a: Int) {
print("R: \(addingFunc(a))")
}
withFunc(addingFunc: addFunc, a: 4) // 7
函数也是可以作为结果返回的。比如返回的值是一个参数为 Int 返回值为 Int 的函数,就是这样定义:func foo() -> (Int) -> Int
func add(a: Int) ->Int {
return a + 1
}
func rev(a: Int) ->Int {
return a - 1
}
func choose(a2r: Bool) -> (Int) ->Int {
return a2r ? add: rev
}
let functionForUse = choose(a2r: false)
functionForUse(3) // rev 2
用多了会发现在一大串 ()-> 中又穿插各种 ()-> 是一个非常蛋疼的事情。我们可以用 typealias 定义函数别名,其功能和 OC 中的 typedef 以及 shell 中的 alias 的作用基本是一样的:
typealias handler = (Bool, String) ->String
func requetsWithHandler (_ completeHandler: handler) -> String {
return completeHandler(false, "Hello")
}
requetsWithHandler { (a:Bool, s:String) -> String in
if a {
return s
} else {
return "World!"
}
}
函数嵌套:
func choose(a2r: Bool) -> (Int) ->Int {
func add(a: Int) ->Int {
return a + 1
}
func rev(a: Int) ->Int {
return a - 1
}
return a2r ? add: rev
}
let functionForUse = choose(a2r: true)
functionForUse(3)
柯里化(简单提及)文
func add(b:Int)(a:Int) -> Int{
return a + b ;
}
let addOne = add(1)
let addTwo = add(2)
var a = 0
a = addOne(a: a) // 1
a = addTwo(a: a) // 3
- 闭包
闭包的完整形态是这个样子的:
{(parameters) -> (returnType) in statements}
闭包有3种形态
- 全局的函数都是闭包,它们有自己的名字,但是没有捕获任何值。
- 内嵌的函数都是闭包,它们有自己的名字,而且从包含他们的函数里捕获值。
- 闭包表达式都是闭包,它们没有自己的名字,通过轻量级的语法定义并且可以从上下文中捕获值。
我们可以直接用 $0 $1 $2 这种来依次定义闭包的参数。比如 sorted 函数:
var reversed = sorted(["c","a","d","b"], { $0 > $1 }) // d c b a
如果闭包是函数的最后一个参数,Swift 提供了尾随闭包 (Trailing Closures) 解决这个审美问题:
// 以下是不使用尾随闭包进行函数调用
someFunc(0, {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunc(0) {
// 闭包主体部分
}
使用尾随,之前的函数改写为:
var reversed = sorted(["c","a","d","b"]) { $0 > $1 } // d c b a
如果除了闭包没有其他参数了,甚至可以把小括号也去掉。
var oldArray = [10, 20, 30, 40]
var filteredArray = oldArray.filter{
return $0 > 30
}
closure 的变形大致有以下几种形态:
[1, 2, 3].map( { (i: Int) ->Int in return i * 2 } )
[1, 2, 3].map( { i in return i * 2 } )
[1, 2, 3].map( { i in i * 2 } )
[1, 2, 3].map( { $0 * 2 } )
[1, 2, 3].map() { $0 * 2 }
[1, 2, 3].map { $0 * 2 }
一个活生生的例子:
var canvas = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
canvas.backgroundColor = UIColor.red
UIView.animate(withDuration: 4, animations: {
canvas.backgroundColor = UIColor.blue
}) {
print("Result \($0)")
}
PlaygroundPage.current.liveView = canvas
// 作为变量
var closureName: (parameterTypes) -> (returnType)
// 作为可选类型的变量
var closureName: ((parameterTypes) -> (returnType))?
// 做为一个别名
typealias closureType = (parameterTypes) -> (returnType)
// 作为函数的参数
func({(parameterTypes) -> (returnType) in statements})
// 作为函数的参数
array.sort({ (item1: Int, item2: Int) -> Bool in return item1 < item2 })
// 作为函数的参数 - 隐含参数类型
array.sort({ (item1, item2) -> Bool in return item1 < item2 })
// 作为函数的参数 - 隐含返回类型
array.sort({ (item1, item2) in return item1 < item2 })
// 作为函数的参数 - 尾随闭包
array.sort { (item1, item2) in return item1 < item2 }
// 作为函数的参数 - 通过数字表示参数
array.sort { return $0 < $1 }
// 作为函数的参数 - 尾随闭包且隐含返回类型
array.sort { $0 < $1 }
// 作为函数的参数 - 引用已存在的函数
array.sort(<)
- 枚举
Swift 中的相关值有点像是 F# 中的 Discriminated Unions,它允许在枚举中存储额外的数据。
enum BarCode {
case UPCA(Int, Int)
case QRCode(String)
}
var productBar = BarCode.QRCode("abc")
productBar = .UPCA(1, 2)
switch productBar {
case .UPCA(let a, let b):
print("\(a) \(b)")
break
default:
break
}
- 懒加载
class Person {
var name: String
lazy var greeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
var p = Person(name: "World!")
p.greeting
class ViewController: UIViewController {
lazy var animator: UIDynamicAnimator = {
return UIDynamicAnimator(referenceView: self.view)
}()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
animator.addBehavior(UIGravityBehavior())
}
}
- 其他类型
- Any 可以表示任何类型
- AnyObject 可以代表任何class类型的实例
- 类型设计
import Foundation
enum Github {
case Zen
case UserProfile(String)
}
protocol Path {
var path : String { get }
}
extension Github : Path {
var path: String {
switch self {
case .Zen: return "/zen"
case .UserProfile(let name): return "/users/\(name)"
}
}
}
let sample = Github.UserProfile("ashfurrow")
println(sample.path) // Prints "/users/ashfurrow"
// So far, so good
protocol Moya : Path {
var baseURL: NSURL { get }
var sampleData: String { get } // Probably JSON would be better than AnyObject
}
extension Github : Moya {
var baseURL: NSURL { return NSURL(string: "https://api.github.com")! }
var sampleData: String {
switch self {
case .Zen: return "Half measures are as bad as nothing at all."
case .UserProfile(let name): return "{login: \"\(name)\", id: 100}"
}
}
}
func url(route: Moya) -> NSURL {
return route.baseURL.URLByAppendingPathComponent(route.path)
}
println(url(sample)) // prints https://api.github.com/users/ashfurrow
插曲:实践一下写个动画
大部分知识块儿内容来自这个粗心的开发者,不过我很喜欢
熟悉并回顾了这些杂乱的东西之后,都跃跃欲试了吧?下篇文章开始正式的写代码。