直接跳到Swift语言吧。Swift很快很安全,而且就我的感受来说,写Swift有一种写脚本语言的快感。但是Swift还有很多路要走,现在最感到头疼的就是和ObjC的混编吧。

Swift

我在这里就按照书中的顺序来做个笔记和添加我所知道的更多知识。

Class和Struct和Enum

对于Class和Struct,一个是引用类型,一个是值类型我就不赘述了,书里写的很清楚。

先说说Struct:

默认下Struct内部的函数是不能修改Struct内部的属性(properties)的,但是我们可以在func前面加mutating关键字来打破默认。例如下面的doSomethingToInternalProperties()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct TV {
var height: Float
var width: Float
static var Tag = "electronics"

var diagonals: Int {
// calculated here
return result
}

var spaceTaken: Double {
get {
// calculate here
return result
}
set {
height = Double(newValue)
}
}

mutating func doSomethingToInternalProperties() {
// do something
}
}

更多:

上面的例子里一共有三种不同的属性(properties)。

  • height和width是实例属性(instance properties)。这种属性存储了关于一个实例的信息,如高和宽的具体数值。
  • diagonals是计算属性(Computed Propertie)。这种属性其实根本就不存储任何值,它只是提供了一个getter或者setter让你直接获取一些计算后的值或直接修改实例属性。
  • Tags是类属性,开头要用static关键字。即不需要创建实例就能获得的关于这个类的属性。let tvTag = TV.Tag

然后Enum:

个人感觉Swift的Enum真的很强大啊,可以有不同的case,也可以有func。Enum和Struct很相似,就不赘述了。我们体验Enum的强大吧,下面是用Enum做一个数学表达式,然后用Enum内部的函数来求值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// example
indirect enum Expression {
case number(Int)
case add(Expression, Expression)

func evaluate() -> Int {
switch self {
case let .number(value):
return value
case let .add(left, right):
return left.evaluate() + right.evaluate()
}
}
}

var first = Expression.number(50)
var second = Expression.number(12)
var sum = Expression.add(first, second)
second.evaluate() // 12
first.evaluate() // 50
sum.evaluate() // 62

解释一下,这里定义了一个数学表达式Expression的Enum。数学表达式有很多很多中,这里我写了:

  1. 代表一个整数的表达式number(Int)
  2. 和加法表达式add(Expression, Expression),之所以括号里面的类型是Expression,是因为它不仅可能是两个整数相加,也可能是两个相加的表达式在求和,类似于(1 + 2)+ 3。

然后内部有一个求值的方程,他会判断,如果这个表达式是一个整数表达式,那么就返回整数的值,如果是加法表达式,这里用到递归,左边表达式求值后再加上右边表达式的值。

你看的没错,Enum里面可以用递归嵌套。这是因为有递归嵌套,所以Enum前面需要加上indirect关键字。

最后Class:

对比与Struct的区别,一个是传递的值得类型不同,另一个是Struct中的属性(properties)是可以不初始化的,就像上面的列子里,height和width我都没有初始化,甚至这两个属性我都没有定义为optional。

但是Class中的属性是要定义的,如果你不定义,那么你多半会得到Class ‘XXX’ has no initializers这样的错误警告。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Compiler error: Class 'Address' has no initializers
class Address {
var street: String
}

// you can solve by these ways
// add a init function
class Address {
var street: String

init(street: String) {
self.street = street
}
}

// init the street by default
class Address {
var street: String = ""
}

// set street to optional
class Address {
var street: String?
}

更多:

Class作为对象当然是可以继承的。一个类继承另一个类后,可以覆盖(override)它父类func的实现方法。如果你不希望子类获得这样的能力,你可以在父类的func前面加上final关键字。Swift不允许类的多继承。

在Swift里面,比较推荐用protocal来代替继承,因为继承会增加程序见的耦合度,增加程序出错的可能性。用protocal的话,就是底耦合度,protocal类似于Java的interface,protocal提供所需的属性和方法,但不实现他们。这些属性和方法由遵循(comfirm)该协议的类来实现。

我看书的目录有关于面向协议的编程一节,这里就不多说了,看到那一节在说。

Optional:

简单理解,就是定义一个value能不能是空的(nil)。一般用if let else或是guard let else来判断。

用guard比if let的好处在于,可以减少if else的嵌套。

Protocol协议:

Swift中的协议真的用法很灵活,大家做好多看看官方Swift教科书。这里列出一些:

  • Class,Struct,Enum都可以遵循Protocal。你也可以通过在protocal前面加@objc或是在protocal后加class来强制只有Class能用这个Protocal
1
2
3
4
5
6
7
@objc protocol SomeProtocal {
}

// 或者

protocol SomeProtocal: class {
}
  • protocal可以定义特定的初始化方法。Class在实现这类初始化方法时,前面要加require
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protocol SomeProtocal {
var aVar: Int { get set }
init()
}

class SomeClass: SomeProtocal {

var aVar: Int

required init() {
aVar = 7
}

init(_ aVar: Int) {
self.aVar = aVar
}
}

struct SomeStruct: SomeProtocal {
var aVar: Int
init() {
aVar = 7
}
}
  • protocal可以作为一个类来成为(protocal as type):
    • 一个方程的返回值类型
    • 一个值和属性的类型
    • 数组,字典或者其他容器中存储值得类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protocol Vehicle {
}

struct Car: Vehicle {
}

struct Bike: Vehicle {
}

let car = Car(), bike = Bike()
var vehicles : [Vehicle] = [car, bike]

// 更多:类型判定
vehicles.forEach { v in
if v is Car {
print("I'm a car")
} else if v is Bike {
print("I'm a bike")
}
}
  • 一个class或struct可以继承多个协议。
  • 默认下,继承协议的class和struct必须实现协议中的所有属性和方法。但是可以通过转化Swift协议到ObjC协议来增加optional。
1
2
3
4
@objc protocol SomeProtocal {
@objc optional func someMethod() -> Int
@objc optional var someProperty: Int { get }
}
  • 代理(Delegation)不是协议(Protocal),它是iOS中的一种设计模式,但是依赖于协议这个技术。用代理的好处在于,委托方不会知道代理方的任何内部情况,通过协议,委托方制定代理方需要实现的功能,然后委托方按照协议实现功能就好了。代理是可以传递消息的,它也是iOS种的一种消息传递方式(其他有:通知NSNotification,Block或Closure,target action,KVO)。

泛型Generic:

书里面简单介绍的Swift泛型怎么用。在实际运用的时候,我们也可以限制泛型值的类型。看个例子,下面的方程返回泛型数组中一个特定值的下标。

1
2
3
4
5
6
7
8
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}

这段程序是有问题的,因为不是所有类型的值都能使用==来判断相等,特别是工程师自己写的类。只有满足实现了Equatable协议的Class和Struct,程序才能正常运行。所以我们可以对泛型T做限制。

1
2
3
4
5
6
7
8
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
// same as above
}

// 或者用where关键字
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? where T: Equatable {
// same as above
}

Open vs. Public

open

  • open 修饰的 class 在 Module 内部和外部都可以被访问和继承
  • open 修饰的 func 在 Module 内部和外部都可以被访问和重载(override)

public

  • public 修饰的 class 在 Module 内部可以访问和继承,在外部只能访问
  • public 修饰的 func 在 Module 内部可以被访问和重载(override),在外部只能访问

Copy-on-write

Swift将很多ObjC中的数据类型从引用类(reference type)改成了值类(value type)。这样及降低了内存泄露的风险,也提高了内存效率。书中给了一个例子:

1
2
3
4
5
6
// arrayA 是一个数组,为值类型
let arrayA = [1, 2, 3]
// arrayB 这个时候与 arrayA 在内存中是同一个东西,内存中并没有生成新的数组
let arrayB = arrayA
// arrayB 被修改了,此时 arrayB 在内存中变成了一个新的数组,而不是原来的 arrayA
arrayB.append(4)

然后说到,当arrayB没有改变的时候,arrayB和arrayA指向同一个内存。意思就是他们应该有相同的address。

然后我就像去证明一下,首先通过看官方文献我找到了这样一个方程来返回地址withUnsafePointer(to:_:)。

1
2
3
4
5
6
7
8
9
10
11
12
13
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1

withUnsafePointer(to: &array1) {
print(" 1 has address: \($0)")
}
withUnsafePointer(to: &array2) {
print(" 2 has address: \($0)")
}

// 输出:
// 1 has address: 0x000000011aabf4d0
// 2 has address: 0x000000011aabf4d8

结果输出地址不一样,我当时就很怀疑人生,然后翻看一些书籍和别人的博客寻求答案。我看到的各种东西都证明《iOS面试之道》里是正确的,但是证明呢?

接着我看到了这个方法,用UnsafeRawPointer

1
2
3
4
5
6
7
8
9
func print(address o: UnsafeRawPointer ) {
print(String(format: "%p", Int(bitPattern: o)))
}

var array1: [Int] = [0, 1, 2, 3]
var array2 = array1

print(address: array1) //0x60c00006f4a0
print(address: array2) //0x60c00006f4a0

终于得到了相同的地址。那么UnsafeRawPointer和UnsafePointer的区别是什么,我的理解是,UnsafeRawPointer是指向没有类型(untyped)的数据,可以理解为存在内存中裸的数据,例子中就是0在内存中所在位置,而UnsafePointer是指向有类型(typed)的数据,例子中应该是指向我们建立的Array在内存中的地址。

static methods vs. class methods

  • static 和 class都是用来指定类方法
  • class关键字指定的类方法 可以被 override
  • static关键字指定的类方法 不能被 override

as? vs. as!

这个属于Swift的类型转换(type cast)。

  • as? - 表示这个类型转换过程是optional的。被赋予的值肯定是一个optional的值。如果类型转换失败,返回nil。
  • as! - 表示这个类型转换过程不是optional的. System will crash if down casting fails.如果类型转换失败,程序崩溃。

加了些我知道的,但是书里没有提及的知识,先这么多吧,我以前的笔记挺乱的,整理好了有什么要加的在加进来。

Reference:
https://xiaozhuanlan.com/ios-interview 故胤道长和唐巧两位大神的书《iOS 面试之道》
https://swiftdoc.org/
https://developer.apple.com/documentation/swift/unsaferawpointer