Swift
直接跳到Swift语言吧。Swift很快很安全,而且就我的感受来说,写Swift有一种写脚本语言的快感。但是Swift还有很多路要走。
我在这里就按照书中的顺序来做个笔记和添加我所知道的更多知识。
Class和Struct和Enum
对于Class和Struct,一个是引用类型,一个是值类型我就不赘述了,书里写的很清楚。
先说说Struct:
默认下Struct内部的函数是不能修改Struct内部的属性(properties)的,但是我们可以在func
前面加mutating
关键字来打破默认。例如下面的doSomethingToInternalProperties()。
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内部的函数来求值。
// 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。数学表达式有很多很多中,这里写了:
- 代表一个整数的表达式number(Int)
- 和加法表达式add(Expression, Expression),之所以括号里面的类型是Expression,是因为它不仅可能是两个整数相加,也可能是两个相加的表达式在求和,类似于(1 + 2)+ 3。
然后内部有一个求值的方程,他会判断,如果这个表达式是一个整数表达式,那么就返回整数的值,如果是加法表达式,这里用到递归,左边表达式求值后再加上右边表达式的值。
你看的没错,Enum里面可以用递归嵌套。这是因为有递归嵌套,所以Enum前面需要加上indirect
关键字。
最后Class:
对比与Struct的区别,一个是传递的值得类型不同,另一个是Struct中的属性(properties)是可以不初始化的,就像上面的列子里,height和width我都没有初始化,甚至这两个属性我都没有定义为optional。
但是Class中的属性是要定义的,如果你不定义,那么你多半会得到Class ‘XXX' has no initializers这样的错误警告。例子:
// 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
@objc protocol SomeProtocal {
}
// 或者
protocol SomeProtocal: class {
}
- protocal可以定义特定的初始化方法。Class在实现这类初始化方法时,前面要加
require
。
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):
- 一个方程的返回值类型
- 一个值和属性的类型
- 数组,字典或者其他容器中存储值得类型
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。
@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泛型怎么用。在实际运用的时候,我们也可以限制泛型值的类型。看个例子,下面的方程返回泛型数组中一个特定值的下标。
func findIndex(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
这段程序是有问题的,因为不是所有类型的值都能使用==
来判断相等,特别是工程师自己写的类。只有满足实现了Equatable协议的Class和Struct,程序才能正常运行。所以我们可以对泛型T做限制。
func findIndex(of valueToFind: T, in array:[T]) -> Int? {
// same as above
}
// 或者用where关键字
func findIndex(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)。这样及降低了内存泄露的风险,也提高了内存效率。书中给了一个例子:
// arrayA 是一个数组,为值类型
let arrayA = [1, 2, 3]
// arrayB 这个时候与 arrayA 在内存中是同一个东西,内存中并没有生成新的数组
let arrayB = arrayA
// arrayB 被修改了,此时 arrayB 在内存中变成了一个新的数组,而不是原来的 arrayA
arrayB.append(4)
然后说到,当arrayB没有改变的时候,arrayB和arrayA指向同一个内存。意思就是他们应该有相同的address。
然后我就像去证明一下,首先通过看官方文献我找到了这样一个方程来返回地址withUnsafePointer(to:_:)。
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
。
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