可选项绑定(Optional Binding)
可以使用可选项绑定来判断可选项是否包含值 p如果包含就自动解包,把值赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false
if let number = Int("123") {
print("字符串转换整数成功:(number)") // number是强制解包之后的Int值 // number作用域仅限于这个大括号 } else { print("字符串转换整数失败") }
等价写法
f let first = Int("4") { if let second = Int("42") { if first < second && second < 100 { print("(first) < (second) < 100") } } } // 4 < 42 < 100 if let first = Int("4"), let second = Int("42"), first < second && second < 100 { print("(second) < (second) < 100") } // 4 < 42 < 100
空合并运算符 ??(Nil-Coalescing Operator)
let a: Int? = 1
let b: Int? = 2
let c = a ?? b // c是Int? , Optional(1)
let a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int , 1
let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int , 2
let a: Int? = nil
let b: Int? = 2
let c = a ?? b // c是Int? , Optional(2)
let a: Int? = nil
let b: Int = 2// 如果不使用??运算符 let c: Int
if let tmp = a {
c = tmp
} else {
c=b
}
let a: Int? = nil
let b: Int? = nil
let c = a ?? b // c是Int? , nil
多个 ?? 一起使用
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 // c是Int , 1
guard语句
当guard语句的条件为false时,就会执行大括号里面的代码
当guard语句的条件为true时,就会跳过guard语句
guard语句特别适合用来“提前退出”
当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用
func login(_ info: [String : String]) { guard let username = info["username"] else { print("请输入用户名") return } guard let password = info["password"] else { print("请输入密码") return } // if username .... // if password .... print("用户名:(username)", "密码:(password)", "登陆ing") }
guard 条件 else {
// do something....
退出当前作用域
// return、break、continue、throw error }
隐式解包(Implicitly Unwrapped Optional)
在某些情况下,可选项一旦被设定值之后,就会一直拥有值
在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
可以在类型后面加个感叹号 ! 定义一个隐式解包的可选项
let num1: Int! = 10 let num2: Int = num1 if num1 != nil { print(num1 + 6) // 16 } if let num3 = num1 { print(num3) } let num1: Int! = nil // Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value let num2: Int = num1
结构体
在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分
比如Bool、Int、Double、 String、Array、Dictionary等常见类型都是结构体
所有的结构体都有一个编译器自动生成的初始化器(initializer,初始化方法、构造器、构造方法)
类
类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
成员的初始化是在这个初始化器中完成的
class Point { var x: Int = 10 var y: Int = 20 } let p1 = Point() class Point { var x: Int var y: Int init() { x = 10 y = 20 } } let p1 = Point() //上面2段代码是完全等效的
结构体与类的本质区别
结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
值类型
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
引用类型
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份 p类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy)
在Swift中,创建类的实例对象,要向堆空间申请内存
枚举、结构体、类都可以定义方法
一般把定义在枚举、结构体、类内部的函数,叫做方法
方法占用对象的内存么?
不占用,
方法、函数都存放在代码段
闭包(Closure)
闭包是可以在你的代码中被传递和引用的功能性独立代码块
全局函数是一个有名字但不会捕获任何值的闭包;
内嵌函数是一个有名字且能从其上层函数捕获值的闭包;
可以把闭包想象成是一个类的实例对象
内存在堆空间
捕获的局部变量常量就是对象的成员(存储属性)
组成闭包的函数就是类内部定义的方法
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
逃逸闭包
当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了,因为它是在函数返回之后调用的。
当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。
属性
存储属性(Stored Property)
类似于成员变量这个概念
存储在实例的内存中
结构体、类可以定义存储属性 ü 枚举不可
在创建类 或 结构体的实例时,必须为所有的存储属性设置一个合适的初始值
可以在初始化器里为存储属性设置一个初始值
可以分配一个默认的属性值作为属性定义的一部分
计算属性(Computed Property)
本质就是方法(函数)
不占用实例的内存
枚举、结构体、类都可以定义计算属性
set传入的新值默认叫做newValue,也可以自定义
只读计算属性:只有get,没有set
定义计算属性只能用var,不能用let
let代表常量:值是一成不变的
计算属性的值是可能发生变化的(即使是只读计算属性)
枚举原始值rawValue的本质是:只读计算属性
单例模式
public class FileManager { public static let shared = FileManager() private init() { } } public class FileManager { public static let shared = { // .... // .... r eturn FileManager() }() private init() { } }
初始化器
类、结构体、枚举都可以定义初始化器
类有2种初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
默认初始化器总是类的指定初始化器
类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
初始化器的相互调用规则
指定初始化器必须从它的直系父类调用指定初始化器
便捷初始化器必须从相同的类里调用另一个初始化器
便捷初始化器最终必须调用一个指定初始化器
// 指定初始化器 init(parameters) { statements } // 便捷初始化器 convenience init(parameters) { statements }
重写
当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override
因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
1、如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)
required
用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
如果子类重写了required初始化器,也必须加上required,不用加override
属性观察器
父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
反初始化器(deinit)
einit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
class Person { deinit { print("Person对象销毁了") } }
deinit不接受任何参数,不能写小括号,不能自行调用
父类的deinit能被子类继承
子类的deinit实现执行完毕后会调用父类的deinit
协议(Protocol)
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
协议中定义属性时必须用var关键字
实现协议时的属性权限要不小于协议中定义的属性权限
协议的继承
一个协议可以继承其他协议
protocol Runnable { func run() } protocol Livable : Runnable { func breath() } class Person : Livable { func breath() {} func run() {} }
CaseIterable
让枚举遵守CaseIterable协议,可以实现遍历枚举值
num Season : CaseIterable { case spring, summer, autumn, winter } let seasons = Season.allCases print(seasons.count) // 4 for season in seasons { print(season) } // spring summer autumn winter
is、as?、as!、as
is用来判断是否为某种类型,as用来做强制类型转换
自定义错误
Swift中可以通过Error协议自定义运行时的错误信息
enum SomeError : Error { case illegalArg(String) case outOfBounds(Int, Int) case outOfMemory }
函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
unc divide(_ num1: Int, _ num2: Int) throws -> Int {
if num2 == 0 {
throw SomeError.illegalArg("0不能作为除数")
}
return num1 / num2
}
需要使用try调用可能会抛出Error的函数
var result = try divide(20, 10)
do-catch
可以使用do-catch捕捉Error
func test() { print("1") do { print("2") print(try divide(20, 0)) print("3") } catch let SomeError.illegalArg(msg) { print("参数异常:", msg) } catch let SomeError.outOfBounds(size, index) { print("下标越界:", "size=(size)", "index=(index)") } catch SomeError.outOfMemory { print("内存溢出") } catch { print("其他错误") } print("4") }
try?、try!
可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
assert(断言)
很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断 n 默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
func divide(_ v1: Int, _ v2: Int) -> Int { assert(v2 != 0, "除数不能为0") return v1 / v2 } print(divide(20, 0))
泛型(Generics)
泛型可以将类型参数化,提高代码复用率,减少代码量
泛型函数赋值给变量
func swapValues<T>(_ a: inout T, _ b: inout T) { (a, b) = (b, a) }
继承(Inheritance)
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,称为:基类
Swift并没有像OC、Java那样的规定:任何类最终都要继承自某个基类
子类可以重写父类的下标、方法、属性,重写必须加上override关键字
重写属性
子类可以将父类的属性(存储、计算)重写为计算属性
子类不可以将父类属性重写为存储属性
只能重写var属性,不能重写let属性
重写时,属性名、类型要一致
子类重写后的属性权限 不能小于 父类属性的权限
如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的
重写类型属性
被class修饰的计算类型属性,可以被子类重写
被static修饰的类型属性(存储、计算),不可以被子类重写
可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
final
被final修饰的方法、下标、属性,禁止被重写
被final修饰的类,禁止被继承
访问控制(Access Control)
在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列, 实体指被访问级别修饰的内容)
open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(open只能用在类、类成员上)
public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
internal:只允许在定义实体的模块中访问,不允许在其他模块中访问
fileprivate:只允许在定义实体的源文件中访问
private:只允许在定义实体的封闭声明中访问
绝大部分实体默认都是internal 级别
Swift调用OC
新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h
在{targetName}-Bridging-Header.h 文件中#import OC需要暴露给Swift的内容
OC调用Swift
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h
Swift暴露给OC的类最终继承自NSObject
使用@objc修饰需要暴露给OC的成员
使用@objcMembers修饰类
Xcode会根据Swift代码生成对应的OC声明,写入{targetName}-Swift.h 文件
String |
⇌ |
NSString |
String |
← |
NSMutableString |
Array |
⇌ |
NSArray |
Array |
← |
NSMutableArray |
Dictionary |
⇌ |
NSDictionary |
Dictionary |
← |
NSMutableDictionary |
Set |
⇌ |
NSSet |
Set |
← |
NSMutableSet |
KVCKVO
Swift 支持 KVC KVO 的条件
属性所在的类、监听器最终继承自 NSObject
用 @objc dynamic 修饰对应的属性
class Person: NSObject { @objc dynamic var age: Int = 0 var observer: Observer = Observer() override init() { super.init() self.addObserver(observer, forKeyPath: "age", options: .new, context: nil) } deinit { self.removeObserver(observer, forKeyPath: "age") } } var p = Person() // observeValue Optional(20) p.age = 20 // observeValue Optional(25) p.setValue(25, forKey: "age") class Observer: NSObject { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { print("observeValue", change?[.newKey] as Any) } }