swift4.1 系统学习二十七 swift访存模型与引用机制

/*
swift 编程语言的访存模型与对象引用机制

前面已经学习过,swift将类型根据访存机制分为两大类: 值类型和引用类型。

值类型:
像枚举、结构体都是数据值类型,传递过程中是值拷贝。

引用类型:
像类和函数则属于引用类型,传递过程中采用的是引用计数的机制。

既然有引用类型就不得不介绍内存管理中的引用机制——自动引用计数,也称为ARC。学习过OC的肯定对ARC并不陌生。

ARC是相对于很久之前的MRC(手动内存管理)说的,经历过MRC的童鞋们肯定觉得ARC实在是一项伟大的发明啊,
简化了多少代码啊。ARC能够将引用计数为0的对象自动释放掉,而不需手动释放。那么ARC就万无一失吗?当然
不是了,还是会有很多注意点,一不小心还是会掉坑里的。

*/

/*

  1. ARC工作机制与强引用

在每次创建一个类的新实例的时候,ARC都会创建一个存储块用于存放该实例的信息。这些信息包括该实例的类型
以及与该对象实例相关联的存储式属性。
当这个实例不再需要的时候,ARC就会释放这个存储块,其他实例就可以使用这个存储块了。
什么情况下会崩溃呢?
当ARC释放了某个存储块的实例,但是这个实例又被访问了,这个时候就会直接崩溃啦。
*/

class MyObject {
    
    var tag = 0
    deinit {
        print("MyObject \(tag) 被释放了。")
    }
}

class MyContainer {
    
    // MyObject的对象引用实例
    var objReference: MyObject?
    
    func method(refObj: MyObject, tmpTag: Int) {
        objReference = refObj
        let tmpObj = MyObject()
        tmpObj.tag = tmpTag
    }
    
    deinit {
        print("MyContainer 被销毁了")
    }
}

_ = {
    
    let obj = MyObject()
    let container = MyContainer()
    container.method(refObj: obj, tmpTag: 1)
    print("闭包结束")
}()

/*
打印结果:
MyObject 1 被释放了。
闭包结束
MyContainer 被销毁了
MyObject 0 被释放了。

分析打印:
在外部的闭包里,我们首先创建一个 obj 对象实例,此时它的引用计数为1。然后创建一个 container 对象实例,
此时它的引用计数也为1。当我们调用 container 对象的 method 方法时,由于该方法的 refObj 形参
对实参 obj 对象作了一次引用,因此这里 obj 对象的引用计数再次递增,变成2。而在 method 方法内部,
MyContainer 类的对象的 objReference 属性对 refObj 形参做了一次引用,因此对于外部的 obj
对象来说,其引用计数再次递增,变为了3。而这里又创建了一个 MyObject 类的一个局部临时对象 tmpObj,
其引用计数为1。当 method 方法退出之时,refObj 形参由于不再被使用而被释放掉,因此引用计数减1,
这样外部的 obj 对象的引用计数变为了2。而由于局部临时对象 tmpObj 也不再被使用,因此将其释放,
让它引用计数减1,由于此时 tmpObj 的引用计数变为了0,因此它被真正地释放掉了,这里会输出:
“MyObject 1 is deallocated!”。而对于外部闭包,当它结束调用时先输出:“closure over!”。
由于 obj 对象不再被使用,因此它被释放掉,即将它的引用计数递减,此时由原来的2变为1。同样,
由于 container 对象不再被使用,因此它也被释放掉,将其引用计数递减,由一开始的1变为了0,
触发对它真正的释放,这里会输出:“MyContainer is deallocated!”。既然 container 对象被真正
释放掉了,那么它所引用的所有其他对象实例也会被释放掉,这样使得其 objReference 属性所引用的对象
引用计数递减,这样外部的 obj 也被减到了0,触发对它的真正释放,这里将会输出:“MyObject 0 is deallocated!”。
*/

/*
大部分时候强引用都没有问题,但是强引用也会引起麻烦——强引用循环(循环引用)。
什么是循环引用?
如果有两个类类型,一个称为类A,另一个叫做类B,如果A中有一个存储式实例属性对类B实行了强引用,而该
类B的对象实例也有一个存储式实例属性对此类A的对象实例也进行了强引用,那么这就形成了类A与类B对象实例
的强引用循环。
更形象一点呢就像是两个人打架,A掐着B的脖子说,你放手,B抱着A的腰说,你先放,结果两个人僵在那里,
谁也不肯放手。这就是循环引用了。
*/

class A {
    
    var b: B?
    deinit {
        print("A 被释放了")
    }
}

class B {
    
    var a: A?
    deinit {
        print("B 被释放了")
    }
}

_ = {
    
    let a = A()
    let b = B()
    
//    a.b = b
//    b.a = a
}()

/*
上述闭包执行完成的时候,a和b都不会被释放,因为他们的引用计数都不是0,造成了内存泄漏。那么该如何解决这种问题呢?
这里我们就可以使用“弱引用”来解决这种困境。
*/

/*

  1. 弱引用

什么是弱引用?
一个弱引用不会对它所引用的实例进行强保持,也就是说不会使得它引用的实例对象引用计数增加。
使用弱引用,我们只需要在一个属性或者变量前面添加一个weak关键字来指明该对象引用为一个弱引用。

那么,弱引用何时释放呢?
当它引用的对象实例被释放时,弱引用依然对它进行引用着。因此,ARC会自动将这个弱引用设置为空(nil)。
同时,由于弱引用需要允许他们的值在运行时变为空,所以总是将弱引用声明为变量,而不是常量,同时,
弱引用类型应该是一个Optional类型。

注意:
当ARC将一个弱引用设置为空时,属性观察者不会被调用。
*/

class C {
    
    // 声明一个弱引用
    weak var d: D? {
        willSet(value) {
            print("d will be set")
        }
    }
    
    deinit {
        print("C 被释放了")
    }
}

class D {
    
    var c: C?
    deinit {
        print("D 被释放了")
    }
}

_ = {
    let c = C()
    var d: D? = D()
    
    c.d = d
    d?.c = c
    
    d = nil
    
    print("c.d = \(String(describing: c.d))")
}()

/*

  1. 非所属引用

与弱引用一样,非所属引用并不对它所引用的对象实例做强保持。不过,与弱引用不同的是,一个非所属引用
所引用的对象实例 必须 至少要与该非所属引用的对象实例具有相同的生命周期。

我们可以通过在一个属性声明之前放置一个 unowned 关键字来声明它作为一个非所属引用。
一个非所属引用总是期望具有一个值。这样,ARC不会将一个非所属引用设置为空值,这也就意味着非所属引
用不能使用Optional类型进行声明。所以非所属引用可以是一个常量。因此我们这里需要注意,只有当我们
确定某一引用总是能够引用一个对象实例,而不在该引用所属的对象实例被释放前释放所引用的对象实例时,
我们才能将它设置为一个非所属引用。
如果我们在一个非所属引用所引用的对象实例被释放之后再去访问它,那么此时会引发运行时错误,应用则会崩溃。
*/

do {
    
    class A {
        
        unowned let b: B
        init(instanceB: B) {
            b = instanceB
        }
        
        deinit {
            print("A 被释放了")
        }
    }
    
    class B {
        
        var a: A?
        deinit {
            print("B 被释放了")
        }
    }
    
    _ = {
        
        var b: B? = B()
        let a = A.init(instanceB: b!)
        b?.a = a
        
        b = nil
        
        //print("a.b = \(a.b)")
        
        /*
         崩溃:
         Fatal error: Attempted to read an unowned reference but object 0x101a04070 was already deallocated2018-11-08 16:06:18.702012+0800 swift28(swift访存模型与引用机制)[10549:761100] Fatal error: Attempted to read an unowned reference but object 0x101a04070 was already deallocated
         
         */
        
    }()
}

/*

  1. 解决针对闭包的强循环引用

swift的闭包在默认情况下会对外部所引用的对象进行强引用。为了避免闭包对外部对象因为进行强引用而导致的
强引用循环,swift对闭包引入了一组 捕获列表 。

比如,对于两个雷的对象实例之间的强引用循环,我们可以定义每个所捕获的引用采用弱引用或者是非所属引用
而不是强引用。
*/

print("\n----4-----\n")

class RootViewController {
    
    var subController: SubViewController?
    
    func presentViewController() {
        subController = SubViewController.init(controller: self)
    }
    
    func popViewController() {
        subController = nil
    }
    
    func info() {
        print("这是一个 RootViewController")
    }
    
    deinit {
        print("RootViewController 释放了")
    }
}

class SubViewController {
    
    /// 非所属引用
    unowned let superViewController: RootViewController
    
    var closure: ((Int) -> Int)?
    
    init(controller: RootViewController) {
        superViewController = controller
    }
    
    func setup() {
        
        closure = {
            // 这里是一个闭包捕获列表
            // 其中将self作为unowned引用
            [unowned self, weak ref = self.superViewController]
            (param: Int) -> Int in
            
            ref?.info()
            self.showInfo()
            return param + 1
        }
        
        print("the value is \(closure!(100))")
    }
    
    func showInfo() {
        superViewController.info()
    }
    
    deinit {
        print("SubViewController 释放啦")
    }
}

_ = {
    
    let rootVC = RootViewController()
    rootVC.presentViewController()
    
    rootVC.subController?.setup()
    rootVC.popViewController()
}()

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

推荐阅读更多精彩内容