自定义
Description
内容打印
- 通过遵守
CustomStringConvertible
、CustomDebugStringConvertible
协议来自定义实例打印字符串- 遵守
CustomStringConvertible
、CustomDebugStringConvertible
协议 - 实现协议中的
description: String
、debugDescription: String
类型变量get
方法来确保内容自定义
- 遵守
- 需要注意的点
-
print
调用的是CustomStringConveritable
协议的description
-
debugPrint
、po
调用的是CustomDebugStringConveritable
协议的debugDescription
-
关于
Self
的使用
- 特点
-
Self
代表当前的类型 -
Self
一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)。比如协议中定义一个方法,方法的返回值可以是Self
来确保实现协议的类型描述
-
关于断言
assert
- 特点
- 常用于调试阶段,默认debug模式生效,发布模式会忽略
- 通过在
other Swift flags
中设置Debug
模式下的-assert-coinfig Release
来强制关闭断言 - 通过在
other Swift flags
中设置Release
模式下的-assert-coinfig debug
来强制关闭断言
//条件满足true的时候才会继续执行,否则中断进程,打印错误
assert(fasle, "断言错误")
关于
fatalError
错误
- 特点
-
fatalError
错误是无法被do-catch
所捕获的 - 使用了
fatalError
函数,不需要再写return
返回值了 - 在某些不得不实现、但是又不希望别人调用的方法,可以考虑内部使用
fatalError
函数
-
访问控制
-
Swift提供了五个不同层面的访问限制
-
open
: 允许其他模块访问,并允许其他模块进行继承、重写(open
只能用在类、类成员上) -
public
: 允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写 -
internal
: 只允许在定义实体的模块访问,不允许在其他模块访问 -
fileprivate
:只允许定义实体的源文件中访问 -
private
:只允许定义实体的封闭声明中访问
注: 绝大部分实体默认都是
internal
级别 -
-
访问级别的使用准则
- 一个实体不能被更低访问级别的实体定义
- 变量/常量类型 >= 变量/常量
- 父类 >= 子类
- 父协议 >= 子协议
- 原类型 >= typealias
- 原始值类型、关联值类型 >= 枚举类型
- 定义类型A时用到的其他类型 >= 类型A
class Person{}
public typealias MuyPerson = Person //该语句会报错,原因是public访问级别低于class Person 的默认访问级别 internal
元组类型
元组类型的访问级别是所有成员类型最低的那个泛型类型
泛型类型的访问级别是类型的访问级别
以及所有泛型类型参数的访问级别
中最低的那个-
成员嵌套类型
- 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
- 一般情况下,类型为
private
或者fileprivate
,那么成员/嵌套类型默认也是private
或者fileprivate
- 一般情况下,类型为
internal
或者public
,那么成员/嵌套类型默认是internal
注:
- 父类的访问权限要小于子类
- 直接在全局作用域下定义的
private
等价于fileprivate
public class PublicClass {
public var p1 = 0
var p2 = 0 //internal
fileprivatre func f1() {} //fileprivate
private func f2() {} //private
}
class InternalClass { // internal
var p = 0 // internal
fileprivate func f1() {} //fileprivate
private func f2 () {} //private
}
class Test {
private class Person {} //private所作用的范围是外面的大括号
fileprivate class Student : Person {} //会报错,因为子类Student的访问权限低于父类
}
private class Person {} //private所作用的范围是整个文件,由于文件声明在全局区域,所以private作用域等同于fileprivate
fileprivate class Student : Person {} //不会报错,此时两者的权限是一致的
/*
报错原因:
1. Dog中的成员变量是由private修饰,所以在其所在的作用域内是能够访问的,由于Person中walk方法在Dog成员变量的作用域外,所以不能够访问其成员变量以及方法
2. 由于 Dog 定义在全局区,即使使用关键字 private 修饰,也等同于 fileprivate 修饰,所以 Person 类与其成员变量 Dog 类型访问权限一致,所以能够正常实例
*/
private struct Dog {
private var age: Int = 0
private func run() {}
}
fileprivate struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run() // 会报访问错误
dog.age = 1 // 会报访问错误
}
}
-
getter
、setter
访问权限- 两者默认自动接收他们所属环境的访问级别
- 可以给
setter
单独设置一个比getter
更低的访问级别,用以限制写的权限
fileprivate(set) public var num = 10 class Person { private(set) var age = 0 fileprivate(set) public var weight: Int { set {} get { 10 } } internal(set) public subscript(index: Int) -> Int { set {} get { index } } }
-
公开部分方法的访问权限可以通过
public
修饰如果想让某个类的方法能够提供给其他模块使用,可以通过
public
修饰方法,表示其可以提供给其他模块使用 -
间接访问权限影响
如果在赋值的时候设置了当前的变量访问权限,那么其他的没有被修饰的成员变量也会因此收到同样的权限管理,均为修饰变量一样的访问权限
struct Point { fileprivate var x = 0 var y = 0 } var p = Point(x: 10, y: 20)
初始化器
如果一个public
类想要在另一个模块调用编译生成的默认无参初始化器
,必须显示提供public
的无参初始化器,因为public
类的默认初始化器是 internal
访问级别
注意:
-
required
初始化器必须跟它所属的类拥有相同的访问级别 - 如果结构体有
private/fileprivate
的存储实例属性,那么它的成员初始化器也是private/fileprivate
,否则默认就是internal
枚举类型的
case
访问权限
- 特点
- 不能给
enum
的每个case
单独设置访问级别 - 每个
case
自动接收enum
的访问级别 -
public enum
定义的case
也是public
- 不能给
协议的访问权限
- 特点
- 协议中定义的内容的权限,将自动接收协议的访问级别,不能单独设置访问级别
- 协议实现的访问级别必须 >= 类型的访问级别,或者 >= 协议的访问级别
注意:
public
修饰的类型/结构体,成员变量以及方法的默认类型是 internal
类型,权限比public
权限小,所以此时遵守的协议的权限是 public
,所以会报错
public protocol Runnable {
func run()
}
public class Person : Runable {
internal func run {}
}
扩展的访问权限
-
特点
- 如果有显示设置扩展的访问级别,扩展添加的成员自动接收扩展级别
- 如果没有显示设置扩展的访问级别,扩展添加的成员默认访问级别,跟直接在类型中定义的成员一样
- 可以单独给扩展添加的成员设置访问级别
- 不能给用于遵守协议的扩展显示设置扩展的访问级别
-
扩展的多重声明
- 在同一个文件中的扩展,可以写成类似多个部分的类型声明
- 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
- 扩展中声明一个私有成员,可以在同一个文件的其他扩展中、原本声明中访问它
public class Person {
private func run0() {}
private func eat0() {
run1()
}
}
extension Person {
private func run1() {}
private func eat1() {
run0()
}
}
extension Person {
private func eat2() {
run1()
}
}
- 技巧:方法的拆分存储调用
有时候需要将某些方法暂存到变量中,等到合适的实际动态去调用它,可以参照如下的方法:
- 将需要调用的方法取出来并存到变量中
- 实例方法所在的类并传入到第一步的方法中,并存储到变量中
- 通过第二步存储的方法进行相关调用
struct Person {
var age: Int
func run(_ v: Int) { print("func run"), age, v }
}
/*
调用步骤
*/
var fn1 = Person.run
var fn2 = fn1(Person(age: 10))
fn2(20)
当存在两个重名的类型方法与实例方法的时候,在取值的时候,设定取值结果的类型来确定当前的静态方法或者实例方法
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
//取实例方法
var fn: (Person) -> (Int) -> () = Person.run
fn(Person(age: 20))(20)
//取静态方法
var fn1: (Int)->() = Person.run
fn1(20)
内存管理
- 特点
- 跟OC一样,
Swift
也是采用引用技术ARC内存管理技术方案来管理内存的(针对于堆空间) - 引用默认都是强引用
- 通过
weak
定义弱引用(ARC自动给弱引用设置为nil的时候,不会触发属性观察器) - 无主引用(unowned reference):通过
unowned
定义无主引用,改引用不安全,会产生野指针
-
weak
、unowned
的使用只能使用在类的实例上面
- 跟OC一样,
-
循环引用
-
weak
、unowned
可以解决循环引用问题,unowned
要比weak
少一些性能损耗 - 在生命周期中可能会变成
nil
的时候使用weak
- 初始化赋值之后再也不会变成
nil
的时候使用unowned
-
闭包的循环引用问题
闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行 retain
操作)
/*
案例一
*/
class Person {
var fn: (()->())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
//会导致循环引用问题
p.fn = { p.run() }
//解决方法1:weak
p.fn = {[weak p] in
p?.run()
}
//解决方法2:unowned
p.fn = {[unowned p] in
p?.run()
}
//声明多个弱引用
p.fn = {[weak wp = p, unowned up = p, a = 10 + 20] in
wp?.run()
}
}
/*
案例二:闭包中引用self, self中引用闭包
*/
class Person1 {
//使用weak去除循环引用问题
lazy var fn: (()->()) = {[weak p = self] in
p?.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
/*
案例三:闭包中引用self之后直接实例运行,不差生循环引用
以下不会产生引用循环:
闭包声明之后立即调用,调用完成之后,闭包销毁,返回闭包结果,此时getAge不会对闭包产生引用,引用的是闭包返回的值结果,所以此种情况下不会出现循环引用问题
*/
class Person2 {
var age: Int = 0
//使用weak去除循环引用问题
lazy var getAge: Int = {
self.age
}()
func run() { print("run") }
deinit { print("deinit") }
}
逃逸闭包
@escaping
- 特点
- 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
- 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在其作用域范围内
- 逃逸闭包:闭包有可能会在函数结束后调用,闭包调用逃离了函数的作用域,需要通过
@escaping
声明修饰
typealias Fn = ()->()
var gFn: Fn?
//非逃逸闭包
func test(_ fn: Fn) { fn() }
//fn逃逸闭包
func test2(_ fn: @escaping Fn) { gFn = fn }
//fn放在dispatch中同样是逃逸闭包
func test3(_ fn: @escaping Fn) {
DispatchQueue.global().async {
fn()
}
}
func run(){
//这一块可以根据具体业务来定,如果想要在异步线程执行完成之后强制执行当前dispatch所在区域方法,
//那么这里直接引用self确保当前作用域范围内能够执行完成并不会立即销毁,可以保证在执行完成dispatch之后销毁内容达到延迟销毁的目的
//如果需要立即中断,则可以使用弱引用的方式来完成
DispatchQueue.global().async {[weak p = self] in
p?.fn()
}
}