Swift中的访问控制与内存管理

自定义Description内容打印

  • 通过遵守 CustomStringConvertibleCustomDebugStringConvertible 协议来自定义实例打印字符串
    1. 遵守 CustomStringConvertibleCustomDebugStringConvertible 协议
    2. 实现协议中的 description: StringdebugDescription: String类型变量 get 方法来确保内容自定义
  • 需要注意的点
    1. print 调用的是 CustomStringConveritable 协议的description
    2. debugPrintpo 调用的是 CustomDebugStringConveritable 协议的 debugDescription

关于 Self 的使用

  • 特点
    1. Self 代表当前的类型
    2. Self 一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)。比如协议中定义一个方法,方法的返回值可以是 Self 来确保实现协议的类型描述

关于断言 assert

  • 特点
    1. 常用于调试阶段,默认debug模式生效,发布模式会忽略
    2. 通过在 other Swift flags中设置 Debug 模式下的 -assert-coinfig Release 来强制关闭断言
    3. 通过在 other Swift flags中设置 Release 模式下的 -assert-coinfig debug 来强制关闭断言
//条件满足true的时候才会继续执行,否则中断进程,打印错误
assert(fasle, "断言错误")

关于 fatalError 错误

  • 特点
    1. fatalError 错误是无法被 do-catch 所捕获的
    2. 使用了 fatalError 函数,不需要再写 return 返回值了
    3. 在某些不得不实现、但是又不希望别人调用的方法,可以考虑内部使用 fatalError 函数

访问控制

  • Swift提供了五个不同层面的访问限制

    1. open: 允许其他模块访问,并允许其他模块进行继承、重写(open 只能用在类、类成员上)
    2. public: 允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
    3. internal: 只允许在定义实体的模块访问,不允许在其他模块访问
    4. fileprivate:只允许定义实体的源文件中访问
    5. private:只允许定义实体的封闭声明中访问

    注: 绝大部分实体默认都是 internal 级别

  • 访问级别的使用准则

    1. 一个实体不能被更低访问级别的实体定义
    2. 变量/常量类型 >= 变量/常量
    3. 父类 >= 子类
    4. 父协议 >= 子协议
    5. 原类型 >= typealias
    6. 原始值类型、关联值类型 >= 枚举类型
    7. 定义类型A时用到的其他类型 >= 类型A
class Person{}
public typealias MuyPerson = Person //该语句会报错,原因是public访问级别低于class Person 的默认访问级别 internal
  • 元组类型
    元组类型的访问级别是所有成员类型最低的那个

  • 泛型类型
    泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个

  • 成员嵌套类型

    1. 类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
    2. 一般情况下,类型为 private 或者 fileprivate,那么成员/嵌套类型默认也是private 或者 fileprivate
    3. 一般情况下,类型为 internal 或者 public,那么成员/嵌套类型默认是 internal

注:

  1. 父类的访问权限要小于子类
  2. 直接在全局作用域下定义的 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 // 会报访问错误
}
}

  • gettersetter 访问权限

    1. 两者默认自动接收他们所属环境的访问级别
    2. 可以给 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 访问级别

注意:

  1. required 初始化器必须跟它所属的类拥有相同的访问级别
  2. 如果结构体有 private/fileprivate 的存储实例属性,那么它的成员初始化器也是 private/fileprivate,否则默认就是 internal

枚举类型的 case 访问权限

  • 特点
    1. 不能给 enum 的每个case 单独设置访问级别
    2. 每个 case 自动接收 enum 的访问级别
    3. public enum 定义的 case 也是 public

协议的访问权限

  • 特点
    1. 协议中定义的内容的权限,将自动接收协议的访问级别,不能单独设置访问级别
    2. 协议实现的访问级别必须 >= 类型的访问级别,或者 >= 协议的访问级别

注意:
public 修饰的类型/结构体,成员变量以及方法的默认类型是 internal 类型,权限比public权限小,所以此时遵守的协议的权限是 public ,所以会报错

public protocol Runnable {
    func run()
}

public class Person : Runable {
    internal func run {}
}

扩展的访问权限

  • 特点

    1. 如果有显示设置扩展的访问级别,扩展添加的成员自动接收扩展级别
    2. 如果没有显示设置扩展的访问级别,扩展添加的成员默认访问级别,跟直接在类型中定义的成员一样
    3. 可以单独给扩展添加的成员设置访问级别
    4. 不能给用于遵守协议的扩展显示设置扩展的访问级别
  • 扩展的多重声明

    1. 在同一个文件中的扩展,可以写成类似多个部分的类型声明
    2. 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
    3. 扩展中声明一个私有成员,可以在同一个文件的其他扩展中、原本声明中访问它
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()
    }
}

  • 技巧:方法的拆分存储调用

有时候需要将某些方法暂存到变量中,等到合适的实际动态去调用它,可以参照如下的方法:

  1. 将需要调用的方法取出来并存到变量中
  2. 实例方法所在的类并传入到第一步的方法中,并存储到变量中
  3. 通过第二步存储的方法进行相关调用
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)

内存管理

  • 特点
    1. 跟OC一样,Swift 也是采用引用技术ARC内存管理技术方案来管理内存的(针对于堆空间)
    2. 引用默认都是强引用
    3. 通过 weak 定义弱引用(ARC自动给弱引用设置为nil的时候,不会触发属性观察器)
    4. 无主引用(unowned reference):通过 unowned 定义无主引用,改引用不安全,会产生野指针
    5. weakunowned 的使用只能使用在类的实例上面
  • 循环引用

    1. weakunowned 可以解决循环引用问题, unowned 要比 weak 少一些性能损耗
    2. 在生命周期中可能会变成 nil 的时候使用 weak
    3. 初始化赋值之后再也不会变成 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

  • 特点
    1. 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
    2. 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在其作用域范围内
    3. 逃逸闭包:闭包有可能会在函数结束后调用,闭包调用逃离了函数的作用域,需要通过 @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()
    }
}

你可能感兴趣的:(Swift中的访问控制与内存管理)