Swift 4 语言笔记¶
快速开始¶
- 版本判断
#if swift(>=3.2)
print(">=3.2")
#else
print("<3")
#endif
- 常量
let
声明: 显式/隐式,类型转换必须显式 -
变量
var
声明 -
字符串格式化模板:
"I have \(apples + oranges) pieces of fruit."
-
多行字符串使用
"""
:
let quotation = """
I am joker.
And She is a girl.
We are family.
"""
print(quotation)
-
数组和字符允许最后一个元素带
,
号,如果类型信息能推断,可能使用[]
、[:]
来定义空数组和字典,否则使用初始化器:[String]()
、[String:String]()
-
if
、switch
、for-in
、while
、repeat-while
的条件部分可以不带括号,但执行体必须用花括号包住 -
正常类型后面跟个
?
号就是Optional
类型,使用if let local = optional {}
是使用optional
类型的一种常用方式,local
变量只在if - let
执行体内部有效 -
??
是给Optional
类型变量取默认值的方法 -
switch
支持各种类型,case
默认不需要加break
,使用fallthrough
可以漏到下一个case
let v = "raff"
switch v {
case "red":
print("red")
case let x where x.hasPrefix("r"):
print("r prefix")
fallthrough
case let "r":
print("fallthrough")
default:
print("default")
}
-
字典枚举时是无序的:
for (key, value) in dict {}
-
循环:
var n = 2 while n < 100 { n *= 2 } print(n)
-
最少执行一次的循环
var m = 2 repeat { m *= 2 }while m < 100 print(m)
-
..<
和...
可以生成一个序列,右边界不同 -
定义函数
func
,->
指明返回值类型, 默认参数标签和参数名相同func greet(person: String, day: String) -> String { return "Hello, \(person), today is \(day)." } greet(person: "joker", day: "Thursday")
-
可以使用
tuple
来返回多值,可根据位置索引取tuple
各元素值,也可以命名可元素标签 -
函数可以嵌套,可访问外层变量
-
闭包
{ 参数及返回会 in 体 }
,$0
,闭包的第一个参数, 闭包作为函数的最后一个参数时可以放在最后,即trailing closure
-
class
声明类,:
表示继承,init
、deinit
初始化和反初始化器 -
override
覆盖父类方法必须是显式指定 -
getter和setter,
newValue
和var Name: Type { get { } willSet { } set(newValue) { } }
-
链式操作中经常使用
?
符号 -
enum
定义枚举,rawValue
可以指定,不仅限定于Int -
struct
定义结构体,和类的主要区别是结构体是值类型 -
protocol
定义协议,类、结构体和枚举都可以使用协议,mutating
修饰结构体和枚举中的协议方法实现,可以修改值类型成员 -
可以使用扩展
extension
来使用协议,可以把协议名作为一种类型来调用所有遵循该协议类型对象,只限于协议定义的内容 -
throw
可以抛出一个异常,throws
指明一个函数可以抛出异常,使用do-try[?]-catch...
来处理异常,try?
把结果转化为Optional
类型,抛出错误时返回nil, 否则返回包含结果的Optional
-
defer
描述的代码块会在函数返回前一刻执行,不论函数执行是否抛出错误 -
泛型
<TypeName:TypeProtocol>
where TypeRequirement
在执行体前面使用:<T: Equatable>
和<T> ... where T: Equatable
等价
语言细节¶
-
keyword
使用swift关键字同名的名字命名变量 -
//
单行注释 -
/* */
多行注释, 可以嵌套多行注释 -
句尾分号可以省略
-
0b
、0o
、0x
表示二进制、八进制、十六进制 -
指数表示:
1e3 = 10 * 10 * 10
、1p2 = 2 * 2
-
易读数字格式:
1_000_000 = 1000000
-
typealias NewTypeName = existedTypeName
类型别名定义 -
元组
(name: ele1, ele2, _)
,元组可以通过下标或元素来访问单个元素,不适合复杂数据结构 -
Optionals
类型的变量可以有值也可以没有值,统一可以表示各种类型变量没有值的情况 -
nil
不能给非optionals
变量赋值,OC中的nil是指向一个不存在对象的指针,只对对象类型有效,而swift中的nil不是一个指针,它只是表示一种不存在值的状态,对所有类型有效。 -
!
强制解包,如果一个Optional
变更确定有值, 就可以使用!
来使用其值 -
Optionals 绑定
,用于从Optional
变量中提取值并赋于局部变量使用,在单个if语句中可以使用多个Optional绑定,只要有一个为假,则整个判断为假 -
隐式解绑
Optional!
,确保它声明后一直有值且不为nil, 但它本身还是一个Optional
,只是在访问时会自动解包 -
函数能抛出错误用
throws
指明,当调用一个可以抛出错误的函数时,前面要加上try
-
Assertions
只在调试期间起作用,precondition
可调试和生产环境中都起作用。Assertions
在单元测试中经常用到,如果编译器开了-Ounchecked
选项,precondition
会被一直认为成功。
assert(exp, msg) 失败时显示msg,成功时继续执行
assertionFailure(msg)直接表示失败时的msg显示
precondition(exp, msg)调试和生产环境都起作用
preconditionFailure(:file:line)
fatalError(_:file:line:)严重错误,不会被编译器优化
-
swfit的
=
赋值不会有返回值,数值默认不允许溢出,但可以明确指定可以溢出 -
一元运算符紧挨运算子,没有空格:
-a, b!
,二元运算:a + b
,三元运算:a ? b : c
-
赋值运算的右边如果是元组,可以自动解析为多个元素赋给左边的变量
-
%
运算的原理:a = (b x some multiplier ) + remainder
, 不考虑b
的符号 -
混合赋值运算也不会返回值,这一点和赋值一致
-
全等运算
===
,不全等!==
,用来判断两个引用是否指向同一个对象 -
如果两个元组有相同的类型和元素个数,它们也可以进行比较,从左到右依次比较,直到发现两个元素不相等时停止比较后面的元素,只有所有元素都支持同一比较运算时才可以比较两个元组,最高能比较有六个元素的无组,再多元素的比较需要自己实现
-
三元运算符不要嵌套使用,使程序更加可读
-
a ?? b
,如果a有值就返回,如果a为nil,则返回b -
闭区间
a...b
,半开区间:a..<b
单端区间在数组上的应用[a...]
用在数组索引,表示从a开始及其后的所有元素,还有[...a]
,表示从头开始到a索引 -
"""
多行字符串中也可以使用\
来转义换行符来续行,在闭合的"""
前面的空格指名多行字符要忽略的缩进 -
String
是值类型 -
字符串是字符的集合,可以被枚举,元素的类型是
Character
-
\()
字符串插入可以用于多行字符串 -
\u{n}
1~8位的Unicode字符标量 -
Character
每一个实例都是一个扩展图像簇,每一个扩展图像簇都可以包含多个Unicode标量,用来生成单个可读字符,可以用来显示一些组合字符,使用count
发生获取字符串长度,也是由于这个原因,swift的字符串中的字符不能使用索引来直接引用,endIndex
不是一个有效的字符索引,String的indices
是所有字符元素的索引数组 -
子字符串类型
Substring
, 只是临时使用 -
Character
有utf8
、utf16
和unicodeScalars
三种Unicode兼容的编码数组 -
集合类型:
Array
、Set
、Dictionary
-
Array(repeating:count:)
可以在定义时初始化, -
enumerated()
方法会返回以(index, emelemt)为元素的数组 -
集合中元素的类型必须是能hash的,以确保唯一性,集合没有简短写法,声明集合必须显式指明类型,不能类型推断
-
switch中的case 后面可以跟
where
-
控制转移关键字:
continue
、break
、fallthrough
、return
、throw
-
语句标签
label:
可以和break
、continue
一起使用,可以实现与C语言中goto相同的效果 -
guard - else
如果guard的条件不成立,就执行else块儿的语句 -
#available(iOS 10, macOS 10.12, *)
API有效性判断 -
函数有
参数标签
和参数名
,默认参数标签和参数名一样,也可以分别指定,可以使用_
忽略参数标签 -
可变参数类型
Double...
,一个函数最多只能有一个可变参数,可变参数要放在参数列表的最后 -
函数的参数默认是常量,
inout
修改参数可以在函数体内修改参数值,只能把变量传给inout类型的参数,并且传参时要在前面加一个&
号,以表示这个参数可以被修改 -
函数就是命名的闭包,嵌套函数可以捕获外层的变量,可以推断变量,单表达式闭包默认会返回值,还有
Trailing Closure
语法 -
闭包的语法:
{(parameters) -> return type in statements }
, 闭包中参数的简写:$0
-
如果闭包被赋给类的成员变量,并且闭包本身又引用了类的成员,就会造成循环引用,Swfit使用捕获列表来解决这种循环引用问题
-
闭包是引用类型
-
escaping
闭包:当一个闭包被当作参数传入一个函数,却在该函数返回后被调用。关键字@escaping
-
自动闭包:自动创建来包裹表达式并传给其它函数,关键字:
@autoclosure
-
枚举可以有结合值和默认值(rawValue),
-
递归枚举:有另一个枚举作为case结合值的枚举类型,使用
indirect
表明它是一个递归枚举 -
class
是引用类型,使用===
和!==
来判断两个引用是否指向同一个实例 -
swift中一些基本类型都是使用
struct
来实现的,像String
、Array
、Dictionary
等,不像Foundation中的实现是使用类 -
Struct
和class
可以声明存储属性,但是Struct
、Class
和Enumeration
都可以声明计算属性 -
储存属性可以定义属性观察器
-
当一个值类型的实例被声明为常量,它的所有成员都不可变,
class
的实例如果被声明为常量,它的成员还是可以改变,因为它是引用类型 -
lazy
属性,只在第一次使用时创建, 只能使用var
声明,因为常量在初始化完成时必须有值, 而lazy
变量可能在初始化完成时没有被创建, 多线程同时调用lazy
变量时,如果它还没有被创建,则它不能保证只被创建一次 -
计算属性也必须使用
var
来声明,属性观察器willSet(newValue)
、didSet(newValue/oldValue)
-
全局常量和变量都是计算懒惰的,只是不需要
lazy
修饰 -
使用
static
关键字定义类型属性,使用class
修饰的计算属性可以被子类重新实现 -
Struct
和Enumeration
是值类型,默认不能从这们的实例方法修改属性 ,如果修改修改,需要在实例方法前面加mutaing
关键字,甚至可以修改self为另一个实例 -
类型方法使用
static
关键词放在函数前面定义,使用class
定义的类型方法允许子类覆盖其实现 -
@discardableResult
属性修改指时时数调用时可以忽略考虑返回值 -
类、结构体、枚举都可以定义下标操作,使用关键字
subscript
定义
subscript(index: Int) -> {
get {
// return an appropriate subscript value here
}
set(newValue){
// perform a suitable setting action here
}
}
-
Dictionry
的元素赋nil
相当于删除这个键值对 -
subscript
不能使用inout
修改参数,也不能给参数提供默认值 -
继承过来的属性不管是存储还是计算类型的都可以添加属性观察器
-
使用关键字
final
来阻止属性、类型、下标或方法被子类覆盖或继承 -
结构体或类型在实例创建时一定要把所有的存储属性都初始化,可以在初始化器里设置,也可以在属性定义时设置默认值,这两种方式都是直接设置属性值,不会调用属性观察器
-
定制初始化器:没有函数名,使用关键字
init
指定,可以有参数标签和参数名称 -
如果属性在初始化时没有值或者之后可能没有值,这样的属性应该定义为
Optional
-
初始化时可以赋值常量成员,类和结构体默认会提供初始化器
-
初始化器可以调用其它初始化器来完成初始化过程,这种调用叫作初始化代理,值类型和
Class
类型的初始化代理方式有些区别 -
值类型不支持继承,它们的初始化代理过程比较简单,只使用自身提供的初始化器作代理
-
如果要自定义初始化器,在值类型中就不会自动生成的初始化器了需要自己显式的写出来,如果要保留自动生成的初始化器,自定义的初始化器要写在扩展里面
-
所以类的存储属性包括继承而来的都必须在初始化赋予初值
-
class
定义两类初始化器:指定初始化器和便利初始化器,用来确保所有的存储属性都能被初始化 -
指定初始化器是一个类的主要初始化器,它负责初始化本类的所有属性并调用合适的父类指定初始化器初始化父类
-
每个类至少要有一个指定初始化器,便利初始化器通过调用同类的指定初始化器来作一些初始化实例的工作,像它的名字一样,便利初始化器只是为了方便初始化流程而设计的
-
便利初始化器使用
convenience
关键词标示,指定初始化器不用 -
初始化代理的过程有三个规则:
- 子类指定初始化器必须调用父类指定初始化器(纵向)
- 便利初始化器必须调用同类的初始化器(横向)
- 便利初始化器最终需要调用同类的指定初始化器完成初始化工作
-
两阶段初始化,保证属性在初始化前不被访问。第一阶段:类先把自己的属性初始化 第二阶段:按需赋值初始化过的属性
-
初始化安全检查:
- 在指定初始化器调用父类指定初始化器之前,要确保本类的所有存储属性都先被初始化
- 修改父类属性值前必须先调用父类的指定初始化器,否则新赋的值就会在父类指定初始化器被调用后而改变
- 便利初始化器在改变属性值前要调用另一个初始化器
- 在第一阶段初始化完成前,初始化器不可以调用实例方法或读任意属性值或引用
self
-
初始化阶段1:
- 初始化器被调用
- 实例内存被分配,但还没有初始化
- 本类的指定初始化器调用来初始化所有的本类存储变量
- 父类的指定初始化器被调用来初始化所有父类的存储变量,直到所有的父类存储变量都被初始化为止
- 这时所有的存储变量都被初始化,第一阶段完成
-
初始化阶段2:从最高父类向下的过程中就可以进一步配置属性值了,这一阶段实例已经可以被正常使用了
-
failable
初始化器init?
标识,return nil
指明初始化失败 -
带
rawValue
的枚举类型会自动生成failable
初始化器 -
init!
创建自动解包的初始化器 -
用
required
修饰的初始化器子类必须实现 -
可以使用闭包来初始化一个变量
var a = { return 0 }()
-
反初始化
deinit
只作用于class
,一个类最多只有一个deinit {}
, 可以用来释放资源 -
Optional
链式调用?.
-
try
、try?
、try!
三种错误处理的区别try!
抑制错误try?
如果发生错误时返回niltry
-
defer
块的代码会和正常的代码执行顺序不同,写在第一名的最后执行 -
is
和as
判断类型和类型转换,as?
尝试类型转换,as!
强制类型转换, -
Any
可以表示所有类型的实例,甚至是函数类型 -
AnyObject
可以表示任何类实例 -
扩展可以给类、结构体、枚举和协议添加功能:
- 可以添加计算实例属性和计算类属性
- 可以定义实例方法和类方法
- 提供新初始化器
- 定义下标
- 定义嵌套类型
- 遵循协议
- 不能覆盖已存在函数
- 不能添加存储属性,不能添加属性观察器
- 只能添加便利初始化器,不能添加指定初始化器
- 不能添加
deinit
-
协议如果继承了
AnyObject
协议就只能由Class
类型继承它 -
组合类型
protocol & antoherProtocol
表示任何同时遵循两个协议的类型 -
is
、as
、as?
,可以用来检查协议遵循 -
协议部分前有
optional
修饰的被遵循后可以不必实现,@objc
修饰的协议部分只能被继承自Objective-C的类遵循 -
协议扩展可以定义默认实现,可以使用
where
定义约束 -
泛型类型也可以添加约束
-
协议可以定义
associatedtype
类型,使用时可以typealias associatedType = Int
,也可以给附加类型加约束 -
weak
和unowned
修改变量或属性可以用来解决循环引用问题,weak
修改的一定是optional
的变量,因为它会在运行时变为nilunowned
修改的变量一定要有值,它不会是nil,unowned(unsafe)
需要自己确保运行时安全 -
闭包引起的循环引用可以使用捕获列表(capture list)解决:
[weak self, unowned delegate]
放在闭我的参数列表前面 -
重叠访问可能会造成不一致问题,通常与
inout
和mutating
有关,造成访问冲突,如果编译器不能保证安全访问,就不会允许访问 -
swift访问控制模型基于模块和源文件,模块是代码分发单位,可以是框架或者应用,每一个
target
都是一个模块 -
open
和public
可以在同一个模块的任何源文件中访问,也可以在被导入的另一个模块的源文件中被访问,open
的范围比public
更大点,包括了被导入的另一个模块的范围 -
internel
只能在本模块中的源文件中被访问 -
fileprivate
只在文件内部可被访问 -
private
只在一个声明的范围内部访问
-@testable
修饰导入声明时,单元测试就可以访问模块的内部实体了
-
swfit默认运算不允许溢出,
&+
、&-
、&*
显式的指定允许溢出 -
自定义运算和运算符:
prefix|infix|postfix operator +++: precedence
static prefix|infix|postfix func
-
indirect
可以用来定义递归枚举 -
~=
是枚举中表达式匹配时使用的运算符
语言参考¶
-
->
表示由后面的构成 -
|
表示或者 -
opt
下标,表示可有可无