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()
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容