tips
全3篇,如下,这是第一篇。
Swift 开发必备 tips 阅读笔记 第一篇,Swift 语言的新特性。
Swift 开发必备 tips 阅读笔记 第二篇,Objective-C 与 Swift 的一些特性过渡。
Swift 开发必备 tips 阅读笔记 三 最后一篇,Swift与开发环境及一些实践。
Swift新元素
柯里化(Currying)
Swift里可以将方法进行柯里化(Currying),把接受多个参数的方法进行一些变形,使其更加灵活。
之前的博客已经介绍过,不再赘述。
1
2
3
4
func addTo(_ adder: Int) -> (Int) -> Int {
return { num in
return num + adder
}
将protocol的方法声明为mutating
Swift 的 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,所以如果你 没在协议方法里写 mutating 的话,别人如果用 struct 或 enum 来实现这个协议的话,就不能在方法里改变自己的变量了。
1
2
3
protocol AnyProtocol {
mutating func printName()
}
Sequence 序列
你使用某个类型遵守 IteratorProtocol 协议,那么这个类型即可使用 for … in 方法了;同时这个类型也可以使用 map,filter,reduce 这些高阶函数。
究其根本的话 for in 是这样的
1
2
3
4
var iterator = arr.makeIterator( )
while let obj = iterator .next() {
print(obj)
}
tuple 元组
元组的使用,例如交换两个值,例如返回两个值
1
2
3
4
5
//1
(a, b) = (b, a)
//2
func calc() -> (Int, Int)
@autoclosure 和 ??
例如一个函数接受一个闭包,虽然可以使用尾随闭包的特性:
logIfTrue{2 > 1}
但是,要么是书写起来十分麻烦,要么是表达上不太清晰,看起来都让人生气。于 是@autoclosure登场了。我们可以改换方法参数,在参数名前面加上@autoclosure 关键字:
1
2
3
4
5
func logIfTrue(_ predicate: @autoclosure () -> Bool) {
if predicate() {
print("True" )
}
}
这时候我们就可以直接写:
logIfTrue(2 > 1)
@escaping
逃逸闭包的用法。注意适时使用 [weak self]。
Optional Chaining
注意一点,连续可选值调用时候,判断最终调用是否成功容易发生错误:
这是错误代码
let playClosure = {(child: Child) -> () in child.pet?.toy?.play()}
这样的代码是没有意义的! 问题在于对于 play() 的调用上。定义的时候我们没有写play() 的返回,这表示这个方法返回 Void (或者写作一对小括号(), 它们是等价的)。但是,经过 Optional Chaining以后 我们得到的是一个Optional的结果。也就是说,我们最后得到的应该是这样一个 closure :
let playClosure = {(child: Child) -> ()? in child. pet? . toy?. play()}
这样调用的返回将是一-个()? (或者写成Void? 会更清楚- -些), 虽然看起来挺奇怪的,但这就是 事实。使用的时候我们可以通过Optional Binding来判定方法是否调用成功:
1
2
3
4
5
if let result: () = playClosure(xiaoming) {
print("好开心~")
} else {
print("没有玩具可以玩:(")
}
操作符
可以自定义操作符。可能跟 柯里化 擦出不一样的火花。
注意 尽量明了,避免歧义和可能的误解。因为一个不被公认的操作符是存在冲突风险和理解难度的, 所以我们不应该滥用这个特性。
func的参数修饰
1
2
3
func increaseIt(_ number: inout Int) {
number += 1
}
字面量表达
除了参数初始化时候可以省略类型初始化。
另一种方式 通过实现协议即可使用 字面量初始化 自定义类型,例如用字符串初始化 enum 等。
下标
作为一门代表了先进生产力的语言,Swift 是允许我们自定义下标的。这不仅包含了对自己写的类 型进行下标自定义,也包括了对那些已经支持下标访问的类型(没错就是Array 和 Dictionay)进 行扩展。
subscript (index: Int) -> T
subscript ( subRange: Range
) -> Slice
函数嵌套
如果一个函数只是被另一个函数调用,不妨尝试一下。单一功能的代码块会整合到一起。
命名空间
在 Swift 中,由于可以使用命名空间了,即使是名字相同的类型,只要是来自不同的命名空间的 话,都是可以和平共处的。和 C# 这样的显式在文件中指定命名空间的做法不同,Swift 的命名空 间是基于 module 而不是在代码中显式地指明,每个 module 代表了 Swift 中的一个命名空间。也 就是说,同一个 target 里的类型名称还是不能相同的。在我们进行 app 开发时,默认添加到 app 的主 target 的内容都是处于同一个命名空间中的,我们可以通过创建 Cocoa (Touch) Framework 的 target 的方法来新建一个 module,这样我们就可以在两个不同的 target 中添加同样名字的类型 了。
typealias
1
2
3
4
5
6
7
8
9
10
11
12
13
// This is OK
typealias Worker<T> = Person<T>
class Person<T> {}
typealias WorkId = String
typealias Worker = Person<WorkId>
protocol Cat { ... }
protocol Dog { ... }
typealias Pat = Cat & Dog
associatedtype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 相当于一个抽象父协议,不能被直接遵守实现
protocol Animal {
associatedtype F: Food
func eat(_ food: F)
}
struct Tiger: Animal {
func eat(_ food: Meat) {
print("eat (meat)")
}
}
struct Sheep: Animal {
func eat(_ food: Grass) {
print("eat (food)")
}
}
可变参数函数
不必像 OC 一样,只能将可变参数设置在最后一位。
1
2
3
4
5
6
7
8
func myFunc (numbers: Int..., string: String) {
numbers.forEach {
for i in 0..<$0 {
print("'(i + 1): (string)")
}
}
}
myFunc (numbers: 1, 2, 3, string: "hello" )
初始化方法顺序
- 先初始化子类的成员变量常量
- 调用 super.init
- 视情况修改父类的成员变量
Designated, Convenience 和Required
- designated,不加修饰的初始化方法,必须对非可选的属性进行设置,并且会显式或者隐式调用父类的方法。
- convenience,便利的初始化方法,必须调用当前类的 designated 方法。如果在子类重写了当前调用的 designated 方法,那么子类就可以使用父类中相关的 convenience 方法了
- 对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是 可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。
初始化返回nil
convenience init?(string URLString: String)
在新版本的Swift中,对于可能初始化失败的情况,我们应该始终使用可返回 nil 的初始化方法,而不是类型工厂方法。
static 和 class
很早版本(Swift 1.2)时候 class 也可被用于修饰全局静态的方法,现在已经只能用 static 来修饰。
class 仅用于声明一个类
多类型和容器
Array、Dictionary、Set
default参数
现在已经不能使用 = default,而需要指定具体的值。
1
2
3
func increasedNum(num: Int, add: Int = 1) -> Int {
return num + add
}
正则表达式
使用 NSRegularExpression 做一个包装来使用。
模式匹配 switch case
switch case 支持的模式匹配使得可以写出如下的代码:
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
// - mark ex1
let num: Int? = nil
switch num {
case nil: print("nil")
default:
print("(num!)")
}
// - mark ex2
let x=0.5
switch x {
case -1.0...1.0: print("get")
default: print("error")
}
// - mark ex3
let contact = ("http://onevcat.com", "onev@onevcat. com")
let mailRegex: NSRegularExpression
let siteRegex: NSRegularExpression
mailRegex =
try ~/"^([a-z0-9_ \\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
siteRegex =
try ~/"^(https?:\\/\\V/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([V\\w \\.-]*)*\\/?$"
switch contact {
case (siteRegex, mailRegex): print ("同时拥有有效的网站和邮箱")
case (_ , mailRegex): print(" 只拥有有效的邮箱")
case (siteRegex,_ ): print("只拥有有效的网站")
default: print("嘛都没有")
}
//输出
//同时拥有网站和邮箱
.. 和 ..< Range 操作符
Range 操作。支持泛型。
1
2
3
4
5
6
7
8
9
let test = "helLo"
let interval = "a"..."z"
for c in test. characters {
if !interval.contains(String(c)) {
print("\(c)不是小写字母")
}
}
//输出
// L不是小写字母
AnyClass, 元类型和 .self
表示任意的概念:Any,AnyObject,AnyClass。
1
2
3
4
5
6
7
8
9
10
11
12
13
//通过 .Type 得到一个元类型(Meta)
typealias AnyClass = Any0bject.Type
//例如
class A {
static func print(){}
}
let typeA: A.Type = A.self
typeA.print()
let typeAA: AnyClass = A.self
(typeAA as! A.Type).print()
协议和类方法中的 Self
在声明协议时,我们希望在协议中使用的类型就是实现这个协议本身的类型的话,就需要使用返回 Self 进行指代。
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
//例如下边
protocol IntervalType (
//..
/// Return rhs clamped to self . The bounds of the result, even
/// if it is empty, are always within the bounds of self
func clamp(intervalToClamp: Self) -> Self
//...
]
//这是一个个人实现的例子
class MyClass: Copyable {
var num=1
func copy() -> Self {
let result = type(of: self).init()
result.num = num
return result
}
required init() {
}
}
//测试 如下
let object = MyClass()
object.num = 100
let new0bject = object.copy()
object.num = 1
print (object.num) // 1
print(new0bject.num) // 100
动态类型和多方法
Swift中我们虽然可以通过 dynamicType 来获取一个对象的动态类型(也就是运行时的实际类型,而非代码指定或编译器看到的类型)。但在使用中,Swift 现在却是不支持多方法的,也就是说,不能根据对象在动态时的类型进行合适的重载方法调用。
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
//这样实现很方便
class Pet {}
class Cat: Pet {}
class Dog: Pet {}
func printPet(_ pet: Pet) {
print("Pet")
}
func printPet(_ cat: Cat) {
print("Cat")
}
func printPet(_ dog: Dog) {
print("Dog")
}
//但是注意这里的坑
func printThem(_ pet: Pet,_ cat: Cat) {
printPet(pet)
printPet(cat)
}
printThem(Dog(), Cat())
//Pet
//Cat
//打印时的Dog() 的类型信息并没有被用来在运行时选择合适的printPet(dog: Dog)版本的方法,
//而是被忽略掉,并采用了编译期间决定的Pet 版本的方法。因为Swift默认情况下是不采用动态
//派发的,因此方法的调用只能在编译时决定。
//要想绕过这个限制,我们可能需要进行通过对输入类型做判断和转换:
func printThem2(_ pet: Pet,_ cat: Cat) {
if let aCat = pet as? Cat{
printPet(aCat)
} else if let aDog = pet as? Dog {
printPet(aDog)
}
printPet(cat)
}
printThem2(Dog(), Cat())
//Dog
//Cat
属性观察
set, get; willSet, didSet
final
final关键字可以用在class, func 或者var 前面进行修饰,表示不允许对该内容进行继承或者重写操作。
通常取决于下边几点:
- 权限控制
- 类已经完善了
- 为了某些方法必须执行
- 性能考虑
lazy修饰符和lazy方法
延时加载或者说延时初始化是很常用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费不少时间,如果有-些对象的属性和内容非常复杂的话,这个时间更是不可忽略。
Reflection 和 Mirror
swfit 的反射功能相较于运行时要弱很多。且并没有公开官方文档,有可能在未来会被去除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Person {
let name: String
let age: Int
}
let xiaoMing = Person(name: "XiaoMing", age: 16)
let r = Mirror(reflecting: xiaoMing) // r是MirrorType
print("xiaoMing是\(r.displayStyle!)")
print("属性个数:\(r.children.count)")
for child in r.children {
print("属性名:\(child.label),値: \(child.value)")
}
//輸出:
//xiaoMing是Struct
//属性个数:2
//属性名:name,値:XiaoMing
//属性名:age,値:16
AnyForwardCollection 是遵守 collectionType 协议的,因此我们可以简单地使用count 来获取元素的个数,而对于具体的代表属性的多元组,则使用下标进行访问。在对于我们的例子中,每个 Child 都是具有两个元素的多元组,其中第一个是属性名,第二个是这个属性所存储的值。需要特别注意的是,这个值有可能是多个元素组成嵌套的形式(例如属性值是数组或者字典的话,就是这样的形式)。
如果觉得一个个打印太过于麻烦,我们也可以简单地使用dump 方法来通过获取一个对象的镜像 并进行标准输出的方式将其输出出来。比如对上面的对象 xiaoMing:
1
2
3
4
5
dump(xiaoMing)
/输出:
// V Person
// name: XiaoMing
// age: 16
在这里因为篇幅有限,而且这部分内容很可能随着版本而改变,我们就不再一一介绍Mirror 的更详细的内容了。有兴趣的读者不妨打开Swift的定义文件并找到这个协议,里面对每个属性和方法的作用有非常详细的注释。
在现在的版本中,Swift 的反射特性并不是非常强大,我们只能对属性进行读取,还不能对其设 定,不过我们有希望能在将来的版本中获得更为强大的反射特性。
隐式解包 Optional
!来隐式解包。是因为跟 OC 桥接的历史遗留,所以存在这个东东。
在 Apple 的不断修改 (我相信这是一件消耗大量人月的手工工作) 下,在 Swift 的正式 版本中,已经没有太多的隐式解包的 API 了。最近 Objective-C 中又加入了像是 nonnull 和 nullable 这样的修饰符,这样一来,那些真正有可能为 nil 的返回可以被明确定义为普通的 Optional 值,而那些不会是 Optional 的值,也根据情况转换为了确定的类型。现在比较常见的隐 式解包的 Optional 就只有使用 Interface Builder 时建立的 IBOutlet 了:
@IBOutlet weak var button: UIButton !
多重Optional
会有陷阱。
1
2
3
4
5
6
7
var aNil: String? = nil
var anotherNil: String?? = aNil
var literalNil: String?? = nil
// anotherNil 和 literalNil 不等价
// 因为 anotherNil 是一个 Optional 对象,不是 nil
Optional Map
array 等集合使用 .map 过滤 nil
Protocol Extension
Swift 2 开始引入的。
整理一下相关的规则的话:
- 如果类型推断得到的是实际的类型。
那么类型中的实现将被调用;如果类型中没有实现的话,那么协议扩展中的默认实现将被使用
- 如果类型推断得到的是协议,而不是实际类型。
并且方法在协议中进行了定义,那么类型中的实现将被调用; 如果类型中没有实现,那 么协议扩展中的默认实现被使用。否则(也就是方法没有在协议中定义),扩展中的默认实现将被调用
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
protocol A2 {
func method1() -> String
}
extension A2 {
func method1() -> String {
return "hi"
}
func method2() -> String {
return "hi"
}
}
struct B2: A2 {
func method1() -> String {
return "hello"
}
func method2() -> String {
return "hello"
}
}
let b2 = B2()
b2.method1() // hello
b2.method2() // hello
let a2 = b2 as A2
a2.method1() // hello
a2.method2() // hi
where 和模式匹配
早前版本在判断语句中 n > 3 即相当于一个 where 模式匹配。 Swift 3 语法省略了这一部分,不过源码还有一些端倪。
现在多用于 prottocol extension, 限定某些类的,默认协议方法
1
2
3
4
extension Sequence where Self. Iterator . Element : Comparable {
public func sorted() -> [Self. Iter ator . Element ]
}
indirect 和嵌套 enum
当我们创建一些特殊的数据结构(链表,树,图等),我们可以使用可选值来指定头尾。但是如果用来表示一个空的链表(例如),就不得不再外层包一个 Optional。
这时候我们可以使用枚举来定义这种结构。
1
2
3
4
5
6
7
indirect enum LinkedList<Element: Comparable> {
case empty
case node(Element, LinkedList<Element>)
}
let linkedList = LinkedList.node(1, .node(2, .node(3, .node(4, .empty))))
非常帅,就是这样。
为枚举添加方法如下;
1
2
3
4
5
6
7
8
9
10
11
12
func removing(_ element: Element) -> LinkedList<Element> {
guard case let .node(value, next) = self else {
return . empty
}
return value == element ?
next : LinkedList.node(value, next .removing(element))
}
let result = linkedList.removing(2)
print(result)
//1->3->4