Swift语法熟悉(一)

知识储备

就像从零搭建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
1
2
3
4
/// 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等使用//开头,默认注释使用///开头。

好的规范可以提高代码的可读性与统一性

上边只是一些简单的规范。还有很多好的建议,我们慢慢聊。

知识
  1. 空值合并

a ?? b 中的 ?? 就是是空值合并运算符,会对 a 进行判断,如果不为 nil 则解包,否则就返回 b 。使用的时候有以下两点要求:

  • a 必须是 optional 的
  • b 必须和 a 类型一致

当然如果你觉得这样可读性不高,我们可以自定义运算符来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
infix operator |->| {
}
func |->|<T> (left: T?, right: T) ->T {
if let l = left {
return l
}
return right
}
var a: String?
var b = "Hello world!"
var c = a |->| b
  1. 关于字符串

String传递的时候会进行拷贝,NSString通常为引用,使用countElements计算String数量与同样NSString.length计算的结果可能不同,因为 length 利用的是 UTF-16 类型的值,而不是 Unicode 字符。比如 emoji 表情加进去之后,UTF-16 算的结果是2,而 Unicode 的计算结果是1。

  1. 数组

除了普通的初始化方法,我们可以通过 init(count: Int, repeatedValue: T) 来初始化一个数组并填充上重复的值:

1
2
// [0.0, 0.0, 0.0]
var threeDoubles = [Double](repeating:0.0, count:3)

按某种规则获得新数组,使用map方法

1
2
3
4
5
var oldArray = [10, 20, 30, 40]
var newArray = oldArray.map { (money: Int) in
"¥\(money)"
}
print(newArray)

当然也可以简写

1
var newArray = oldArray.map({ "¥\($0)" })

数组过滤:

1
2
3
4
5
6
7
8
var oldArray = [10, 20, 45, 32]
var filteredArray : Array<Int> = []
for money in oldArray {
if (money > 30) {
filteredArray += [money]
}
}
print(filteredArray)

不过这个方法看起来很笨重,我们使用新方法

1
2
3
var filtered = oldArray.filter {
return $0 > 30
}

计算数组的和

1
2
let array: Array<Int> = [10, 20, 30, 40]
var result = array.reduce(0, +)
  1. 解包技巧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

字符串也可以进行容错

1
2
3
4
5
6
7
8
func madd(_ s: String?) ->String {
return s.map{
sss in "Hello \(sss)!"
} ?? "default value"
}
madd("c")
madd(nil)
  1. Switch中巧用where
1
2
3
4
5
6
7
8
9
10
11
12
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内,只能用在直接的判断中

  1. 控制流语法大全
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 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<String, AnyObject> = [
"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")
}
  1. 函数

可选的参数请提供默认值

1
2
3
4
5
func add(_ a: Int = 1, _ b: Int = 2) ->Int {
return a + b
}
add(3) // 3 + 2

可变参数

1
2
3
4
5
func add(nums: Int ...) ->Int {
return nums.reduce(0, +)
}
add(nums: 1, 2, 3, 4, 5) // 15

如果不止一个参数,需要把可变参数放在最后,否则会报错。

通过函数修改原始值需要 inout

1
2
3
4
5
6
7
func add( v:inout Int) {
v = v + 1
}
var a = 1
add(v: &a)

泛型函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func value<T>( 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)

变量:

1
2
3
4
5
6
7
func functionAdd(_ a: Int) ->Int {
return a + 2
}
let funcalias = functionAdd
funcalias(2)

函数既然是类型的一种,那么显然也是可以作为参数传递的:

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 的作用基本是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
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!"
}
}

函数嵌套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

柯里化(简单提及)

1
2
3
4
5
6
7
8
9
10
11
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
  1. 闭包

闭包的完整形态是这个样子的:

1
{(parameters) -> (returnType) in statements}

闭包有3种形态

  1. 全局的函数都是闭包,它们有自己的名字,但是没有捕获任何值。
  2. 内嵌的函数都是闭包,它们有自己的名字,而且从包含他们的函数里捕获值。
  3. 闭包表达式都是闭包,它们没有自己的名字,通过轻量级的语法定义并且可以从上下文中捕获值。

我们可以直接用 $0 $1 $2 这种来依次定义闭包的参数。比如 sorted 函数:

1
var reversed = sorted(["c","a","d","b"], { $0 > $1 }) // d c b a

如果闭包是函数的最后一个参数,Swift 提供了尾随闭包 (Trailing Closures) 解决这个审美问题:

1
2
3
4
5
6
7
8
9
// 以下是不使用尾随闭包进行函数调用
someFunc(0, {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunc(0) {
// 闭包主体部分
}

使用尾随,之前的函数改写为:

1
var reversed = sorted(["c","a","d","b"]) { $0 > $1 } // d c b a

如果除了闭包没有其他参数了,甚至可以把小括号也去掉。

1
2
3
4
5
var oldArray = [10, 20, 30, 40]
var filteredArray = oldArray.filter{
return $0 > 30
}

closure 的变形大致有以下几种形态:

1
2
3
4
5
6
[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 }

一个活生生的例子:

1
2
3
4
5
6
7
8
9
10
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

常见形态

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
// 作为变量
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(<)
  1. 枚举

Swift 中的相关值有点像是 F# 中的 Discriminated Unions,它允许在枚举中存储额外的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}

Swift代码块

  1. 懒加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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())
}
}
  1. 其他类型
  • Any 可以表示任何类型
  • AnyObject 可以代表任何class类型的实例
  1. 类型设计
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
39
40
41
42
43
44
45
46
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

插曲:实践一下写个动画

大部分知识块儿内容来自这个粗心的开发者,不过我很喜欢

熟悉并回顾了这些杂乱的东西之后,都跃跃欲试了吧?下篇文章开始正式的写代码。

评论