示例代码来源于 《iOS 11 Programming Fundamentals with Swift》
概览
语句分隔符
Swift的语句是可以通过分析断句的,如果一个语句结束后换行就开始下一个语句,如果写了分号也表示结束这个语句,但是有了换行就不需要分号了,写上也没有问题。如果一个语句没有结束,换行没有实际效果。下边的代码都是合法的:
print("hello")
print("world")
print("hello"); print("world")
print("hello");
print("world");
print(
"world")
注释
依旧是://和/…/,其中/* … */可以嵌套
对象的类型
Swift中一切都是对象。按照类型划分,有6种:
- struct, Bool,Int, Double, String, Array,Dictionary等
- enum, Optional类型
class, 用户自定义类型。这篇文章提到了Swift中有3个预定义的class类型,但是没有指出是哪个。
protocol,ExpressibleByIntegerLiteral
- tuple,函数返回多值时使用。
- function,print,自定义函数。
举个例子:
字面变量是对象:
let s = 1.description
#### 值类型和引用类型
按照内存管理来划分,Swift对象有值类型和引用类型,值类型在赋值的时候是copy的(不考虑Swift优化),引用类型是共享内存的。
官方文档对值类型和引用类型的解释:
Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.
在Swift中,class和function是引用类型,struct,enum,tuple都是值类型。protocol本身不允许有实例,但是采用protocol的可以是struct, enum或者class。
数据类型
变量与常量
let one = 1
var two = 2
使用let声明的是常量,使用var声明的是变量
类型推断
从上面例子可以看出,如果在声明变量的时候就赋值,有时候是可以不写类型的,让编译器推断。上文中one和two都是Int类型。
那么什么时候需要些类型呢?
- 只声明,不初始化。
var x : Int
- 想要的类型和推断的类型不符合
let separator : CGFloat = 2.0
- 不能推断出类型
let opts : UIViewAnimationOptions = [.autoreverse, .repeat]
- 还有一种情况是提醒自己这个变量是啥类型
let duration : CMTime = track.timeRange.duration
基本类型用法
Bool
- Bool是一个struct类型
- 只有true和false两个值,不能做它解释。
Int
- Int是struct类型
- Int的取值在Int.max和Int.min之间,平台相关
Double
- Double是struct类型
- 64位架构处理器上,Double的精度是15位
- Double的边界是Double.infinity,还有Double.pi等
- 使用isZero来判断Double是否为0
数字类型转换
只有字面变量可以被隐式转换!
let d : Double = 10
将字面变量10转换成了Double类型,但是变量就不可以,下列的代码不能通过编译:
let i = 10
let d : Double = i // compile error
正确的写法是:
let i = 10
let d : Double = Double(i)
String
let str = "Hello World" //欧耶,终于不用写@了
多行字面变量的写法:
func f() {
let s = """
Line 1
Line 2
Line 3
"""
// ...
}
func f() {
let s = """
Line "1"
Line 2 \
and this is still Line 2
"""
// ...
}
在String字面变量中使用(…)来计算表达式
let n = 5
let s = "You have \(n) widgets."
String支持+号和+=号
let s = "hello"
let s2 = " world"
let greeting = s + s2
String的utf8编码:
let s = "\u{BF}Qui\u{E9}n?"、
for i in s.utf8 {
print(i) // 194, 191, 81, 117, 105, 195, 169, 110, 63
}
String和数值的转化:
let i = 7
let s = String(i) // "7"
let i = 31
let s = String(i, radix:16) // "1f"
Range
Range是一个struct。 字面变量: a…b表示区间[a, b] a..< b表示区间[a, b)
最常见的就是在for循环中使用:
for ix in 1...3 {
print(ix) // 1, then 2, then 3
}
Range 有实例方法:
let ix = // ... an Int ...
if (1...3).contains(ix) { // ...
let s = "hello"
let ix2 = s.index(before: s.endIndex)
let s2 = s[..<ix2] // "hell"
Tuple
tuple是一个有序的轻量级的collection。
tuple的声明:
var pair : (Int, String)
初始化:
var pair : (Int, String) = (1, "Two")
var pair = (1, "Two")
tuple可以同时给多个变量赋值:
let ix: Int
let s: String
(ix, s) = (1, "Two")
tuple在for-in中的应用:
let s = "hello"
for (ix,c) in s.enumerated() {
print("character \(ix) is \(c)")
}
对Tuple中值的引用:
let pair = (1, "Two")
let ix = pair.0 // now ix is 1
如果在声明的时候给值一个label,可以通过label引用:
let pair : (first:Int, second:String) = (1, "Two")
//or: let pair = (first:1, second:"Two")
var pair = (first:1, second:"Two")
let x = pair.first // 1
pair.first = 2
let y = pair.0 // 2
还可以给Tuple起一个别名
typealias Point = (x:Int, y:Int)
func piece(at p:Point) -> Piece? {
let (i,j) = p
// ... error-checking goes here ...
return self.grid[i][j]
}
可选类型
Swift中变量如果不初始化是不能使用的。这点和OC不同,OC中值类型会有一个默认值,引用类型默认为nil。Swift中如何表示nil呢?答案就是Optional(可选类型)
Optional类型的底层是enum类型,可以包装一个其他类型,具体内部实现这里不讨论。
比如:
var stringMaybe = Optional("howdy")
就定义了一个包装了String的Optional类型。包装不同类型的Opational也是不同的类型,不能互相赋值。Optional(String)类型可以简写为String?
如果没有给Optional的变量装箱一个值,那么它就是空的,空的Optional变量可以和nil比较:
var stringMaybe : String? = "Howdy"
print(stringMaybe) // Optional("Howdy")
if stringMaybe == nil {
print("it is empty") // does not print
}
stringMaybe = nilprint(stringMaybe) // nil
if stringMaybe == nil {
print("it is empty") // prints
}
在Swift中nil是一个关键字,不是一个值,可以将nil赋值给Optional的类型。
自动装箱,将一个值直接值给包装它的Optional类型。
var stringMaybe: String? = "farewell
根据自动装箱机制,可以在任何需要Optional类型的地方传入原始类型,但是反过来不行。
let stringMaybe : String? = "howdy"
let upper = stringMaybe.uppercased() // compile error
不能给Optional类型直接发送消息,需要拆箱得到原始数据。
拆箱
let stringMaybe : String? = "howdy"
let upper = stringMaybe!.uppercased()
在变量后边加上叹号,就拆箱得到原始类型。
自动拆箱,在定义变量的时候使用!而不是?就定义了一个自动拆箱的Opational变量,在需要使用原始类型的地方,直接传入自动解包的Opational变量即可。
func realStringExpecter(_ s:String) {}
var stringMaybe : String! = "howdy"
realStringExpecter(stringMaybe) // no problem
注意,如果自动解包的Optional是nil,会引起Crash。不能给一个是nil的Optional类型解压,这是Swift最重要的规则之一。 所以,如果不是必须,最好不要使用这个特性,因为这样就失去了Swift中可选类型的安全特性。
!定义的Optional和?定义的Optional是同一个类型,比如self.view是一个UIView!,但是如下代码却产生编译错误。
var stringMaybe : String! = "howdy"
var anotherStr = stringMaybe //ok
var pureStr: String = stringMaybe //ok
var errStr: String = anotherStr // compile error
stringMaybe是自动拆箱的String?,所以赋值给String类型是可以的;但是anotherStr却没有自动拆箱的标志,仅仅是一个String?,所以不能赋值给String类型。
Optianal Chain是Swift中很重要的一个概念。
拆箱nil会引起Crash,那么如果每次拆箱都得判断是否为nil,代码就会很难看。于是Swift提供了语法糖:
var stringMaybe : String?
// ... stringMaybe might be assigned a real value here ...
let upper = stringMaybe?.uppercased()
在拆箱的时候,不用!而是用?,这叫做选择性拆箱。英文很有意思:unwarp the Optional optionally。
选择性拆箱实际上替你做了判断工作,就是如果stringMaybe是nil,那么什么也不做,如果不是nil,拆箱得到String,然后发送uppercased消息。
这很好,但是如果“什么也不做”返回值upper是啥?答案是nil。那么nil是不能赋值给String类型的,于是又引入一个规则:
如果一个Optional Chain上有一个可能的Optional的类型