Swift不允许隐式类型转换
[玩转Swift字符串]:https://www.swiftmi.com/topic/104.html
[Swift中数组(Array)及成员方法介绍] https://www.swiftmi.com/topic/71.html
[窥探 Swift 之数组与字典]http://www.cocoachina.com/swift/20151230/14802.html
[Swift 中枚举高级用法及实践]http://swift.gg/2015/11/20/advanced-practical-enum-examples/)
[初學Swift:愛恨交織的 Optional]http://www.appcoda.com.tw/swift-optional/)
[Swift 烧脑体操(一) - Optional 的嵌套]http://www.infoq.com/cn/articles/swift-brain-gym-optional
类的属性:使用static修饰,是懒加载的,线程安全的
类的构造函数:必须全部属性都初始化或有默认值,否则编译不过
继承类的构造函数原则,必须先初始化好本身的属性,然后才初始化父类的属性
Swift中类的构造与析构
Swift:什么时候使用结构体和类
guard & defer
Swift:类型转换
错误和异常处理
PROTOCOL EXTENSION
“错误”的使用 Swift 中的 Extension
访问控制
Swift -- 访问控制Access Control
内存管理,WEAK 和 UNOWNED
Swift之自动引用计数
3.0[TOC]
变量和常量
声明常量和变量
var Name = Value
//: Integer
var hours = 24
//: Double
var PI = 3.14
//: Bool
var swiftIsFun = true
//: String
var swift = “swift”
// tuple 元组类型,可按索引访问成员,用在函数上,可以有多个返回值,很好用
var me = (“James”, 18, "[email protected]”)
me.0
me.1
- Type annotation 类型注释
通过:Type指定变量类型的形式,在Swift中叫做Type annotation。比如
var x: Int
var s: String
- Type inference 类型推导
Swift编译器会根据我们为变量的赋值自动推导变量的类型,这个特性,在Swift里叫做Type Inference - 输出常量和变量
用 let 修饰,表示常量不可被修改
let minutes = 30
用 var 修饰,表示变量,可被修改
var fireIsHot = true
整数与浮点数
- Swift编译器会根据目标编译平台,把Int或UInt转换成对应的整数类型,在64位平台上,分别使用min和max方法,来查看Int和Int64可以表达的数值范围:
Int.min // -9223372036854775808
Int.max // 9223372036854775807
Int64.min // -9223372036854775808
Int64.max // 9223372036854775807
let fifteenInDecimal = 15 10进制
let fifteenInHex = 0xF 16进制
let fifteenInOctal = 0o17 8进制
let fifteenInBinary = 0b1111 2进制
在数字中,使用分隔符:
let million = 1_000_000 等价于 1000000
- Float: 最多表达6位精度的浮点数;
- Double: 至少可以表达15位精度的浮点数;
如不是有明确的需求,我们应该统一使用Double来定义浮点数
在Swift里,我们使用一个整数,编译器会把它推导成Int,使用一个浮点数,编译器会把它推导成 Double
var three = 3
type(of: three) // Int.Type
var zeroPointForteen = 0.14
type(of: zeroPointForteen) // Double.Type
不同类型数字的字面值直接进行运算(类型转换格式 Double(Value)
)
PI = Double(three) + zeroPointForteen
Swift中的字符串
Swift中,我们可以使用\u{1F496}这样的方式,来表示一个unicode scalar :
Swift里,String已经彻底不再是一个集合类型。而是一个提供了从多个维度展现一个Unicode视图的类型。你可以得到它的多个Characters,可以看到它的UTF-8
/ UTF-16 / Unicode scalar值等等
面对unicode复杂的组合规则,我们很难保证所有的集合算法都是安全并且语义正确的。为了解决这个问题,Swift中的String不是一个集合类型。
理解表达字符串的view
因为字符串看上去就一个一连串单个字符的组合。所以,Swift的开发者还是决定让这个类型用起来像一个集合类型。
为了达到这个目的,第一个要解决的就是,如何让Swift理解字符串中的“字符集合”。为了不存在歧义,String为开发者提供了一些不同的"view"。简单来说,就是告诉String类型如何去理解字符串中的内容,它们是String的不同属性。unicode scalar的编码方式划分的“view”
unicodeScalar:按照字符串中每一个字符的unicode scalar来形成集合;
utf8:按照字符串中每一个字符的UTF-8编码来形成集合;
utf16:按照字符串中每一个字符的UTF-16编码来形成集合;
let cafee = "caf\u{0065}\u{0301}" // café
cafee.unicodeScalars.dropLast(1) // cafe 使用unicodeScalars这个view,移除最后一个元素
cafee.utf16.dropLast(1) // cafe 使用utf16这个view,移除最后一个元素
cafee.utf8.dropLast(1) // cafe 使用utf8这个view,移除最后一个元素 ,由于声调符需要两个UTF-8编码,因此最后一个会留意下无法识别的乱码
- String的另外一个view:characters
它是一个String.CharacterView类型的属性。这个“view”是按照unicode grapheme clusters计算字符串的字符个数,也就是最接近我们肉眼看到的字符的view。因此String.characters形式上就可以理解为“由我们看到的字符构成的字符数组”。 - 获取前缀
不建议使用 cafee[0 ..< 3] 来获取前缀,因为这样的操作的复杂度是 O(n2)的,Swift提供了一个叫做prefix(_:)的方法,它返回一个特定的CharacterView:
String(cafee.characters.prefix(3)) // Caf
- 遍历字符串中的每一个字符
var mixStr = "Swift很有趣"
for (index, value) in mixStr.characters.enumerated() {
print("\(index): \(value)")
}
⁃ 插入内容
if let index = mixStr.characters.index(of: "很") {
mixStr.insert(contentsOf: " 3.0".characters, at: index)
// "Swift 3.0很有趣"
}
- 基于Range的查找和替换
if let cnIndex = mixStr.index(of: "很") {
// 2. Replace a specific range
mixStr.replaceSubrange(
cnIndex ..< mixStr.endIndex,
with: " is interesting!")
// Swift 3.0 is interesting!
}
- 字符串切片
let swiftView = mixStr.characters.suffix(12).dropLast()
String(swiftView) // interesting
let strViews = mixStr.characters.split(separator: " ") //按空格分割
strViews.map(String.init) // 将数组每个元素生成字符串对象
// ["Swift", "3.0", "is", "interesting!"]
// 通过闭包指定分割条件 :每个奇数位置的字符当成分隔符
var i = 0
let singleCharViews = mixStr.characters.split { _ in
if i > 0 {
i = 0
return true
}
else {
i += 1
return false
}
}
singleCharViews.map(String.init) // ["S", "i", "t", "3", "0", "i", " ", "n", "e", "e", "t", "n", "!"]
- Tuple 元组类型
定义方式:
let success = (200, "HTTP OK") //
不指定参数名称方式,只能通过下标访问
success.0
success.1
let me = (name: “Jams”, no: 18, email: “session.cn”) //指定参数名称方式
可以通过参数名称访问
me.name
me.no
me.email
- Tuple Decomposition
把一个Tuple的值,一一对应的拆分到不同的变量上
var (successCode, successMessage) = success
print(successCode) // 200
print(successMessage) // HTTP OK
修改部分值: “_”表示
let (_, errorMessage) = fileNotFound
print(errorMessage)
- tuple 的比较:只有元素个数相同的Tuple变量之间(最多包含6个元素的Tuple变量进行比较,超过这个数量,Swift会报错)
let tuple11 = (1, 1)
let tuple12 = (1, 2)
tuple11 < tuple12 // true
- 运算操作符
- Swift 3不再允许浮点数取模。
例如:8 % 2.5这样的写法在Swift 3中将会报错。如果要对浮点数取模,只能这样: 8.truncatingRemainder(dividingBy: 2.5) - Swift不会把数字自动转换成Bool类型。在需要Bool值的地方,你必须明确使用一个Bool变量。
- Swift 3中不再支持自增(++)和自减(--)操作符,使用它们的前缀和后缀版本都会得到一个编译器错误。因此,需要+1/-1的时候,只能使用b += 1 / b -= 1来实现。
- Swift特有的操作符,用来处理和Optional有关的判断
- Swift 3不再允许浮点数取模。
var userInput: String? = "A user input"
let value = userInput ?? "A default input"
如果opt是一个optional,当其不为Nil时,就使用optional变量自身的值,否则,就使用??后面的“默认值”
- 闭区间
// begin...end
for index in 1...5 {
print(index)
}
- 半开半闭区间
// begin..
Control Flow
- 条件分支判断语句
switch light {
case "red":
action = "stop"
case "yellow":
action = "caution"
case "green":
action = "go"
default:
action = "invalid"
}
和C++/Java这样语言相比:
- 每个case语句不会自动“贯通”到下一个case,因此我们也无需在每个case最后一行写break表示结束
- 必须考虑default情况,不写编译器会报错;明确表示你考虑到了其他的情况,只是你不需要更多额外处理而已
循环控制语句
遍历一个集合类型或者范围
let vowel = ["a", "e", "i", "o", "u"]
for char in vowel {
print(char)
}
// aeiou
for number in 1...10 {
print(number)
}
// 12345678910
传统C风格的三段式for循环,已经在Swift 3中被移除
// for var i = 0; i < 10; i += 1 {
// print(i)
// }
var i = 0
while i < 10 {
print(i)
i += 1
}
// do ... while
repeat {
print(i)
i -= 1
} while i > 0
匹配值的方式
-
case 匹配的值 = 要检查的对象
let origin = (x: 0, y: 0) if case (0, 0) = pt1 { print("@Origin") } 用(_, 0)和(0, _)表示忽略掉_的部分,仅对tuple中某一部分的值进行匹配
-
循环语句,用于控制循环条件
let array1 = [1, 1, 2, 2, 2] for case 2 in array1 { print("found two") // Three times }
-
把匹配的内容绑定到变量 value binding
switch pt1 { case (let x, 0): print("(\(x), 0) is on x axis") case (0, let y): print("(0, \(y)) is on y axis") default: break }
-
绑定enum中的关联值
enum Direction { case north, south, east, west(abbr: String) } let west = Direction.west(abbr: "W") if case .west = west { print(west) // west("W") } 访问enum的关联值 if case .west(let direction) = west { print(direction) // W }
-
自动提取optional的值
let skills: [String?] = ["Swift", nil, "PHP", "JavaScirpt", nil] 打印出不为nil的元素 for case let skill? in skills { print(skill) // Swift PHP JavaScript }
-
自动绑定类型转换的结果
let someValues: [Any] = [1, 1.0, "One"] for value in someValues { switch value { case let v as Int: print("Integer \(v)") case let v as Double: print("Double \(v)") case let v as String: print("String \(v)") default: print("Invalid value") } } // Integer 1 // Double 1.0 // String One 仅仅想判断类型,而不需要知道具体内容 for value in someValues { switch value { case is Int: print("Integer") // omit for simplicity... } }
-
使用where约束条件
for i in 1...10 where i % 2 == 0 { print(i) } 搭配switch switch battery { case .normal(let percentage) where percentage <= 0.1: print("Almost out of power") case .normal(let percentage) where percentage >= 0.8: print("Almost fully charged") default: print("Normal battery status") }
-
使用逗号串联条件
switch halfPower { // ... case .fullyCharged, .outOfPower print("Fully charged or out of power") // ... } if case .normal(let percentage) = battery, case 0...0.1 = percentage { // 要比较的值的范围是写在等号左侧,而不是 percentage = case 0...0.1 print("Almost out of power") } if嵌套 应该这样写 if A, B, C { } 不应该这样写 if A { if B { if C { } } }
-
tuple简化多个条件的比较
//原始方式 let username = "ssession.cn" let password = 11111111 if username == "ssession.cn" && password == 11111111 { print("correct") } //更简洁的方式 if case ("ssession.cn", 11111111) = (username, password) { print("correct") }
Swift 3 Collections
Array
- 添加和删除元素
array1.append(1) // [1]
array1 += [2, 3, 4] // [1, 2, 3, 4]
// [1, 2, 3, 4, 5]
array1.insert(5, at: array1.endIndex)
array1.remove(at: 4) // [1, 2, 3, 4]
array1.removeLast() // [1, 2, 3]
array2.removeLast() // This will crash!!! 要对removeLast()的应用安全负责,当你删除一个空数组中最后一个元素的时候,会直接引发运行时错误。
值语义实现的Array 复制一个Array对象时,会拷贝整个Array的内容;如果仅仅复制了Array而不对它修改时,真正的复制是不会发生的,两个数组仍旧引用同一个内存地址。只有当你修改了其中一个Array的内容时,才会真正让两个Array对象分开。
绝大多数时候,不需要[] 访问元素,使用[]访问元素,一旦数组越界直接崩溃
- 访问数组中的每一个元素:
a.forEach { print($0) }
// or
for value in a {}
- 获得数组中每一个元素的索引和值:
for (index, value) in a.enumerated() {}
- 查找数组中元素的位置时(例如,查找等于1的元素的索引):
a.index { $0 == 1 }
- 过滤数组中的某些元素:
a.filter { $0 % 2 == 0 }
- 通过closure(闭包)参数化对数组元素的变形操作
简单的Fibonacci序列:[0, 1, 1, 2, 3, 5]。如果我们要计算每个元素的平方
最朴素的做法是for循环:
var fibonacci = [0, 1, 1, 2, 3, 5]
var squares = [Int]()
for value in fibonacci {
squares.append(value * value)
}
- 更简洁的方式: 闭包
// [0, 1, 1, 4, 9, 25]
let constSquares = fibonacci.map { $0 * $0 } // “$”:Swift 内建的用来代表参数的简写 $0 ; map :对数组每个元素执行该闭包
参数化数组元素的执行动作
fibonacci.min() // 0
fibonacci.max() // 5 使用min和max很安全,因为当数组为空时,这两个方法将返回nil。
- 比较数组
// false
fibonacci.elementsEqual([0, 1, 1], by: { $0 == $1 })
// true
fibonacci.starts(with: [0, 1, 1], by: { $0 == $1 })
- 对数组进行排序
/ [0, 1, 1, 2, 3, 5]
fibonacci.sorted()
// [5, 3, 2, 1, 1, 0] 从大到小 ,>是{ $0 > $1 }的简写形式。
fibonacci.sorted(by: >)
let pivot = fibonacci.partition(by: { $0 < 1 }) //partion(by:)则会根据指定的条件返回一个分界点位置
// [0, 1, 1]
fibonacci[0 ..< pivot]
// [2, 3, 5]
fibonacci[pivot ..< fibonacci.endIndex]
- 把数组的所有内容,“合并”成某种形式的值
fibonacci.reduce(0, +) // 12 ;初始值是0,和第二个参数+,则是{ $0 + $1 }的缩写。
Dictionary
- 定义方式:[KeyType: ValueType]
enum RecordType {
case bool(Bool)
case number(Int)
case text(String)
}
let record11: [String: RecordType] = [
"uid": .number(11),
"exp": .number(100),
"favourite": .bool(true),
"title": .text("Dictionary basics")
]
- 更新value的时候,同时获得修改前的值
record10.updateValue(.bool(true),
forKey: "favourite") // .bool(false)
- 添加元素
record10["watchLater"] = .bool(false)
- 删除特定的key
record10["watchLater"] = nil
- 遍历Dictionary
for (k, v) in record10 {
print("\(k): \(v)")
}
record10.forEach { print("\($0): \($1)") }
Set的代数运算
var setA: Set = [1, 2, 3, 4, 5, 6]
var setB: Set = [4, 5, 6, 7, 8, 9]
- 取出相同的部分
let interSectAB: Set = setA.intersection(setB)// {5, 6, 4}
- 去除相同部分
let symmetricDiffAB: Set = setA.symmetricDifference(setB) // {9, 7, 2, 3, 1, 8}
- 整合
let unionAB: Set = setA.union(setB) // {2, 4, 9, 5, 6, 7, 3, 1, 8}
- 去除掉与B相同的部分
let aSubstractB: Set = setA.subtracting(setB) // {2, 3, 1}
- 可修改Set自身”,加上form
setA.formIntersection(setB) // { 5, 6, 4 }
Optional
对各种值为"空"的情况处理不当,几乎是所有Bug的来源,Swift里,明确区分了"变量"和"值有可能为空的变量"这两种情况。
let number: Int? = 1 // 类型后面加 ?,表示Optional类型,即值可能为空
if let 形式
if let number = number {
print(number)
} // 第一个 number(if代码块中新定义的变量)的类型已被解包,也就是类型是 Int;如果 number的值不为nil,就 print(number)
if let `number` = number, `number` % 2 != 0 {
print(number)
} // number % 2 != 0中的number,指的是在if代码块中新定义的变量
从某个url加载一张jpg的图片,可以这样实现。
if let url = URL(string: imageUrl), url.pathExtension == "jpg",
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
{
let view = UIImageView(image: image)
}
while let 形式
let numbers = [1, 2, 3, 4, 5, 6]
var iterator = numbers.makeIterator()
//遍历一个数组
while let element = iterator.next() {
print(element)
}
guard 形式
if let 形式,解包的变量的作用域仅仅在if let{}括号内,guard 可以解决这种弊端。
如果条件不成立,执行 else 后面的 {},如果成立,则会 print(first),且 `first`的作用域在`{}`下面都有效,且已经解包
func arrayProcess(array: [Int]) {
guard let first = array.first else `{`
return
`}`
print(`first`)
}
optional chaining 可选链
绝大多数时候,如果你只需要在optional不为nil时执行某些动作,使用 optional chaining
var swift: String? = "Swift"
let SWIFT: String
//optional chaining 形式,简洁
let SWIFT = swift?.uppercased() // Optional("SWIFT")
//而不需要这样写,显得啰嗦
if let swift = swift {
SWIFT = swift.uppercased()
}
else {
fatalError("Cannot uppercase a nil")
}
force unwrapping 强制解包
对于一个optional变量来说,可以用!来强行读取optional包含的值,
但是强制读取值为nil的optional会引发运行时错误
func 和 closure
func
函数声明格式
func funcName( [param list] ) { /*Body*/ }
func multipleOf(multiplier: Int, andValue: Int) {
print("\(multiplier) * \(andValue) = \(multiplier * andValue)")
}
调用
multipleOf(5, andValue: 10)
InnerName 与 OuterName
InnerName :参数的内部名字,来帮助函数的实现者实现函数逻辑
OuterName:函数的调用者使用OuterName向函数传递参数
默认情况下,函数参数的InnerName和OuterName是相等的;
函数的第一个参数,它默认是没有OuterName
可以强行指定一个OuterName
func createTable(rowNumber `row`: Int, colNumer `column`: Int) {
print("Table: \(row) x \(column)")
}
这里 `innerName` 分别是 `row`,`column`,outerName分别是 rowNumber,colNumber
createTable(rowNumber:10, colNumber:10)
closure �闭包
{ ( param list ) -> RetType in
/* Closure body */
}
闭包组成的元素有 参数,返回值,方法体; 用 in 关键字 将 参数,返回值 和 方法体 分成2部分。
var addClosure: (Int, Int) -> Int =
{ (a: Int, b: Int) -> Int in
return a + b
} // 这个闭包类型是:接收2个Int类型参数,返回值是 Int, 方法体是 return a + b
闭包的简化
addClosure已经明确要求它“接受两个Int参数,返回Int”,因此,我们完全可以通过type inference,去掉closure定义中的类型和返回值描述:
addClosure = { a, b in return a + b }
Closure只包含一条执行语句,我们可以省略掉return(Single Expression Closure)
addClosure = { a, b in a + b }
Swift提供了一种描述Closure参数名的方法:$0, $1, $2; $0表示第一个参数,$0表示第二个参数,再次简化
addClosure = { $0 + $1 }
闭包更多的是直接实现在函数的参数位置
func execute(a: Int,
_ b: Int,
operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
} //函数的第三个参数是一个闭包
闭包直接实现在函数的参数位置
execute(1, 2, operation: { (a: Int, _ b: Int) -> Int in
return a + b
})
Trailing Closures 尾随闭包
如果函数类型参数是函数的最后一个参数,可以把closure写在函数调用的外面
execute(1, 2) { $0 + $1 }
Capturing Values
当closure访问一个变量时,它就会“捕获”这个变量,即便离开了这个变量的作用域,这个变量仍旧是存在并且可以访问的,就像是closure把这个变量“关在了closure内部"一样。
func counting() -> () -> Int {
var count = 0 // 临时变量 count 被闭包捕获了
let incrementCount: () -> Int = { ++count }
return incrementCount
}
let c1 = counting()
c1() //1 每调用一次 count 加1
c1() //2
let c2 = counting()
c2() //1
c2() //2
c2() //3
Protocol
格式
protocol Engine {
func start()
func stop()
}
添加属性
在protocol中添加属性的时候,必须明确指定该属性支持的操作:只读(get)或者是可读写(get set)。
protocol Engine {
var cylinder: Int { get set }
func start()
func stop()
func getName(label: String)
func getName()
}
protocol也可以继承
---
protocol TurboEngine : Engine {
func startTurbo()
func endTurbo()
}
computed property
尽管在protocol中,Engine.cylinder被定义成了一个computed property,但是我们在实现的时候,却可以把它定义为一个简单的stored property:
class V8 : TurboEngine {
var cylinder = 8
}
Swift的标准库的protocol
protocol - Equatable
struct Rational {
var numerator: Int
var denominator: Int
}
要判断2个Rational 是否相等,需要遵守 Equatable协议
extension Rational: Equatable {}
func == (lhs: Rational, rhs: Rational) -> Bool {
let equalNumerator = lhs.numerator == rhs.numerator
let equalDenominator = lhs.denominator == rhs.denominator
return equalNumerator && equalDenominator
}
protocol - Comparable 它继承自Equatable
extension Rational: Comparable {}
func < (lhs: Rational, rhs: Rational) -> Bool {
let lQuotient =
Double(lhs.numerator) / Double(lhs.denominator)
let rQuotient =
Double(rhs.numerator) / Double(rhs.denominator)
return lQuotient < rQuotient
}
定义了"<",Swift就会自动推导">";定义了"==",Swift就会自动推导"!=“。因此,只实现"<"和"==“就可以了。
基于Array的操作
把Rational对象放入支持Comparable的集合类型时,例如Array,集合的各种排序,比较,包含操作,就可以对Rational对象生效了
var rationals: Array = []
for i in 1...10 {
var r = Rational(numerator: i, denominator: i+1)
rationals.append(r)
}
print("Max in rationals: \(rationals.maxElement()!)")
print("Min in rationals: \(rationals.minElement()!)")
rationals.startsWith([oneHalf])
rationals.contains(oneHalf)
protocol -CustomStringConvertible 自定义类型的print
extension Rational : CustomStringConvertible {
var description: String {
return "\(self.numerator) / \(self.denominator)"
}
}
protocol- Hashable 实现这个protocol才可以作为Dictionary或者Set的元素
extension Rational: Hashable {
var hashValue: Int {
let v = Int(String(self.numerator) + String(self.denominator))!
return v
}
}
let oneHalf = Rational(numerator: 1, denominator: 11)
var dic: Dictionary = [oneHalf: "1/2"]
var rSet: Set = [oneHalf]
protocol可以提供默认实现
声明一个 Flight protocol
protocol Flight {
var delay: Int { get }
var normal: Int { get }
var flyHour: Int { get }
func delayRate() -> Double
}
额外添加属性
通过定义一个protocol extension来实现,totalTrips的默认实现是返回 delay + normal
extension Flight {
var totalTrips: Int {
return delay + normal
}
}
给已有方法提供默认实现
extension Flight {
func delayRate() -> Double {
return Double(delay) / Double(totalTrips)
}
}
where限定
通过 where来限定 某个类型同时遵从OperationalLife和Flight,才能使用maxFlyHours属性
protocol OperationalLife {
var maxFlyHours: Int { get }
}
extension Flight where Self: OperationalLife {
func isInService() -> Bool {
return self.flyHour < maxFlyHours
}
}
自定义类型
struct
struct作为一个值类型,Swift默认不允许我们在method里修改成员的值,如果我们要修改它,需要在对应的方法前面使用mutating关键字。
编译器会给struct提供默认的init方法
struct Location {
let x: Double
var y: Double
// Initializer
init(stringPoint: String) {
// "100,200"
let xy = stringPoint.characters.split(",")
x = atof(String(xy.first!))
y = atof(String(xy.last!))
}
mutating func moveHorizental(dist: Double) {
self.x = self.x + dist;
}
}
var pointA = Location("100,200”) // 使用字符串初始化
Enumeration 枚举
- 可以指定rawValue
enum Direction: Int { // 默认把EAST / SOUTH / WEST / NORTH“绑定”上0 / 1 / 2 / 3
case EAST
case SOUTH
case WEST
case NORTH
}
//访问rawValue
let north = Direction.NORTH.rawValue
let jan = Month.January.rawValue
//通过rawValue生成enum
let north = Direction(rawValue: 4)
- Associated values
可以给每一个case“绑定”不同类型的值
enum HTTPAction {
case GET
case POST(String)
case PUT(Int, String)
}
var action1 = HTTPAction.GET
var action2 = HTTPAction.POST("BOXUE")
switch action1 {
case .GET:
print("HTTP GET")
case let .POST(msg):
print("\(msg)")
case .DELETE(let id, let value):
print("\(id)=\(value)")
}
- 不是每一个case必须有associated value,例如.GET就只有自己的enum value;
- 当我们想“提取”associated value的所有内容时,我们可以把let或var写在case后面,例如.POST的用法;
- 当我们想分别“提取”associated value中的某些值时,我们可以把let或var写在associated value里面,例如.DELETE的用法;
属性
- Computed properties
在每次被访问的时候,要被计算出来,而不是内存中读取出来。比如例子的center属性
struct MyRect {
var origin: Point
var width: Double
var height: Double
var center: Point {
get {
let centerX = origin.x + self.width / 2
let centerY = origin.Y + self.height / 2
return Point(x: centerX, y: centerY)
}
set(newCenter) {
self.origin.x = newCenter.x - self.width / 2
self.origin.y = newCenter.y - self.height / 2
}
}
}
- Property observer
willSet和didSet,它们分别在stored property被赋值前和后被调用
struct MyRect {
var origin: Point
var width: Double {
willSet(newWidth) {
print("width will be updated")
}
didSet(oldWidth) {
if width <= 0 {
width = oldWidth
}
else {
self.height = width
}
}
}
}
- 在didSet里,我们可以直接使用width读取MyRect的width属性,但是我们必须使用self读取其它的属性;
- willSet和didSet不在对象的init过程中生效,仅针对一个已经完整初始化的对象在对属性赋值的时候生效;
- 如果我们不指定willSet和didSet的参数,Swift默认使用newValue和oldValue作为它们的参数;
- Type property
除了对象属性之外,有些属性是属于一个类型的,它们并不和具体的对象相关
enum Shape {
case RECT
case TRIANGLE
case CIRCLE
}
struct MyRect {
var origin: Point
var width: Double
var height: Double
// Type property 描述一个类型所有对象的属性
static let shape = Shape.RECT
}
let shape = MyRect.shape // 直接使用类型的名字访问
内存管理
Swift跟objective-c一样,使用ARC来管理内存。
ARC只针对类对象才生效,struct和enum都是值类型,它们的对象并不被ARC管理
处理对象reference cycle的三种方式
weak var name: Type
weak reference的特定,它只能被定义成var
weak reference用于解决成员允许为nil的reference cycle。
class Apartment {
let unit: String
weak var tenant: Person?
// omit for simplicity...
}
unowned let name: Type
unowned reference用于解决成员不允许为nil的reference cycle
和strong reference相比,unowned reference只有一个特别:不会引起对象引用计数的变化。
class Apartment {
let unit: String
weak var tenant: Person?
unowned let owner: Person
// Omit for simplicity...
}
implicitly unwrapped optional
unowned reference和implicitly unwrapped optional配合在一起,用于解决引起reference cycle的两个成员都不允许为nil的情况。
class Country {
let name: String
// Implicitly Unwrapped Optional
var capital: City! // default to nil
init(name: String, capitalName: String) {
self.name = name
// Syntax Error!!!
self.capital = City(name: capitalName, country: self)
}
}
unowned var cn: Country? = Country(name: "China", capitalName: "Beijing")
var bj: City? = City(name: "Beijing", country: cn!)
处理closure和类对象之间的reference cycle
Swift无法确认当我们在Closure中使用self时,它已经被完整的初始化过了。如果我们需要这种初始化约束,我们可以把asHTML定义为lazy。
class HTMLElment {
let name: String
let text: String?
var asHTML: Void -> String = { // WRONG SYNTAX!!!
if let text = self.text {
return "<\(self.name)>\(self.text)\(self.name)>"
}
else {
return "<\(self.name)>"
}
}
// Omit for simplicity...
}
lazy可以确保一个成员只在类对象被完整初始化过之后,才能使用。
class HTMLElment {
// Omit for simplicity
lazy var asHTML: Void -> String = {
// Omit for simplicity...
}
// Omit for simplicity...
}
- 用capture list解决reference cycle
本质上来说,closure作为一个引用类型,解决reference cycle的方式和解决类对象之间的reference cycle是一样的,如果引起reference cycle的"捕获"不能为nil,就把它定义为unowned,否则,定义为weak。而指定“捕获”方式的地方,叫做closure的capture list。
使用一对 [] 表示closure的capture list
class HTMLElment {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
// text
// Capture list
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(self.text)\(self.name)>"
}
else {
return "<\(self.name)>"
}
}
// Omit for simplicity...
}
如果closure带有完整的类型描述,capture list必须写在参数列表前面;
如果我们要在capture list里添加多个成员,用逗号把它们分隔开;
class HTMLElment {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
// text
// Capture list
[unowned self /*, other capture member*/] () -> String in
if let text = self.text {
return "<\(self.name)>\(self.text)\(self.name)>"
}
else {
return "<\(self.name)>"
}
}
// Omit for simplicity...
}