[Swift]学习笔记--内存管理

Swift中的内存是自动管理的(ARC),但是在有些情况下,还是会引起内存泄露的,下面就来记录一下哪些情况需要我们注意内存问题;

1. 强引用引起的内存泄露

1.1. weak

下面我们来定义这样一个场景:

People: 拥有姓名,和居住的公寓,但是公寓可有可无
Apartment: 拥有名称,和居住的人,但是人可有可无

代码如下:

class People {
    
    var name: String
    var apartment: Apartment?
    
    init(name: String) {
        
        self.name = name
        print(name,"is initialized")
    }
    
    deinit {
        print("People",name,"is being deinitialized")
    }
}

class Apartment {
    
    let name: String
   var tenant: People?
    
    init(name: String) {
        self.name = name
        print("Apartment",name,"is initialized")
    }
    
    deinit {
        print("Apartment",name,"is being deinitialized")
    }
}

这里的apartment和tenant属性都是可以为nil的,所以定义为可选型;
下面我们进行下面的额操作:
分别初始化两个对象:

var zl: People? = People.init(name: "zl")
var apart: Apartment? = Apartment.init(name: "apart")

然后分别赋值:

zl?.apartment = apart
apart?.tenant = zl

接下来,我们将两个对象置为nil(假设需要释放了)

apart = nil
zl = nil

这时候,我们看控制台的输出:

zl is initialized
Apartment apart is initialized

只有初始化时的输出,没有走deinit方法,也就是对象没有释放掉,产生了内存泄露.
我们都知道这里产生内存泄露的原因就是: 因为强引用,发生了循环引用,互相得不到释放.具体原因,不做过多解释,一般都能理解,这里只给出解决方法.
因为两个对象的相关属性都是强类型,才会导致循环引用,所以只需将其中的一个改为弱引用即可,这就用到了weak关键字

weak关键字的使用条件:

  1. weak关键字修饰的属性必须是变量,即:必须使用var定义
  2. weak关键字修饰的属性必须是可选型

同时满足这两个条件即可使用weak,显然,这里的需求是满足的,因此,只需将People的apartment或者Apartment的tenant,其中之前加上weak关键字即可:

weak var tenant: People?
// 或者
weak var apartment: Apartment?

这时控制台输出:

zl is initialized
Apartment apart is initialized
People zl is being deinitialized
Apartment apart is being deinitialized

两个对象都正常释放掉了.

1.2. unowned

下面我们来看一下这样的场景:

Person: 拥有姓名和信用卡
CreditCard: 拥有卡号和持卡人

注意: 这里的信用卡的卡号和持卡人是必须存在且不可更改的,因此,我们这样定义这两个类:

class Person {
    
    var name: String
    var careditCard: CreditCard?
    
    init(name: String) {
        self.name = name
        
        print(name,"is initialized")
    }
    
    deinit {
        print("Person1",name,"is being deinitialized")
    }
}

class CreditCard {
    
    let number: String
    let customer: Person
    
    init(number: String, customer: Person) {
        
        self.number = number
        self.customer = customer
        
        print("CreditCard",number,"is initialized")
    }
    
    deinit {
        print("CreditCard",number,"is being deinitialized")
    }
}

然后,我们进行如下操作:

var lzz: Person? = Person1.init(name: "lzz")
var goldCard: CreditCard? = CreditCard.init(number: "123444", customer: lzz!)

goldCard = nil
lzz = nil

同样,因为相同的原因,导致循环引用,没有走deinit方法:

lzz is initialized
CreditCard 123444 is initialized

这里的修改,我们可以将Person的属性careditCard使用weak修饰:

weak var careditCard: CreditCard?

这样也能避免内存泄露;
如果想将CreditCard的属性customer改为弱引用,就不能使用weak修饰了,因为他不满足使用weak关键字的条件,这就用到了unowned关键字:

unowned 关键字也是一个弱引用,其可以修饰常量(let定义),也可修饰变量(var定义),但是其修饰的量一定不能是可选型,也就是不能为nil

这里CreditCard的customer属性满足使用unowned关键字的条件,所以可以使用:

unowned let customer: Person

这时也能解决内存泄露问题; 但是unowned关键字有一个缺点:

使用unowned关键字修饰的量,在释放掉后,不能再访问,否则会编译报错,使程序crash掉

如果,把lzz置为nil后,这时goldCard不为nil,如果这时访问其属性customer,就会编译报错:

lzz = nil
goldCard?.customer
goldCard = nil
不能访问

所以,如果使用了unowned关键字修饰的量,一定要注意不要提前释放内存空间;如果下面这样写,是可以的:

goldCard = nil
lzz = nil
goldCard?.customer

这里不会报错的原因是: 在对goldCard进行解包的时候就返回nil了,而不会继续去看customer了,所以,这样使用是安全的.

2. 闭包中的循环引用

看这样一个例子:

class SmartAirConditioner {
    
    var temperature: Int = 26
    var temperatureChange: ((Int) ->())!
    
    init() {
        
        temperatureChange = { newTemperature in
            
            if abs(newTemperature - self.temperature) >= 10 {
                
                print("It's not healthy to do it")
            } else {
                self.temperature = newTemperature
                print("New temperature \(self.temperature) is set")
            }
        }
    }
    
    deinit {
        print("Smart Air-conditioner is being deinitialized")
    }
}

然后进行使用:

var airCon: SmartAirConditioner? = SmartAirConditioner.init()
airCon?.temperature
airCon?.temperatureChange(100)
airCon?.temperatureChange(24)

airCon = nil

会发现,控制台输出:

It's not healthy to do it
New temperature 24 is set

并没有走deinit方法,产生了内存泄露:

这是因为temperatureChange是类的一个属性,也就是类持有temperatureChange,而在temperatureChange内又使用了self,即temperatureChange引用了self(类),这就形成了强引用循环.

因为闭包捕获了外部引用self,这样我们可以声明一个捕获列表,在这里对self进行处理,声明捕获列表的方法如下:

在闭包参数列表之前,用方括号([ ])括起来即可:

将init方法修改如下:

init() {
        
        temperatureChange = { [unowned self] newTemperature in
            
            if abs(newTemperature - self.temperature) >= 10 {
                
                print("It's not healthy to do it")
            } else {
                self.temperature = newTemperature
                print("New temperature \(self.temperature) is set")
            }
        }
    }

这里使用的是unowned关键字的弱引用,当然也可以使用weak关键字进行修饰,需要注意的是: 如果使用weak修饰,那么self就是一个可选型,在使用的时候需要解包:

init() {
        
        temperatureChange = { [weak self] newTemperature in
            
            if abs(newTemperature - self!.temperature) >= 10 {
                
                print("It's not healthy to do it")
            } else {
                self!.temperature = newTemperature
                print("New temperature \(self!.temperature) is set")
            }
        }
    }

这里使用的是强制解包,也可以使用? 解包;当然,也可以使用if let进行解包:

init() {
        
        temperatureChange = { [weak self] newTemperature in
            
            if let weakSelf = self {
                
                if abs(newTemperature - weakSelf.temperature) >= 10 {
                    
                    print("It's not healthy to do it")
                } else {
                    weakSelf.temperature = newTemperature
                    print("New temperature \(weakSelf.temperature) is set")
                }
            }
        }
    }

3. 代理中的循环引用

在使用代理的时候, 如果把含有代理的对象设置为某个类的属性, 再设置其代理为当前类的时候, 就有可能引起循环引用.

3.1 模拟循环引用

这里我设置了一个这样的场景: 实例化一个People类, 设置一个协议 PeopleProtocol, 为People类添加一个遵循该协议的代理属性:
People 类 :

// People 类 
import UIKit

class People: NSObject {

    var name = ""
    var age = 0
    var delegate: PeopleProtocol?
    
    func eat() {
        
        print("eat action")
    }
}

PeopleProtocol 协议 :

// PeopleProtocol 协议
import UIKit

protocol PeopleProtocol {
    
    func people(_ people: People, didEatSomeThing thing: String)
}

然后新建一个控制器PeopTestViewController, 设置一个People属性, 实例化, 并设置其代理为当前控制器:

class PeopTestViewController: UIViewController, PeopleProtocol {

    var op: People!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        self.view.backgroundColor = UIColor.green

        op = People()
        op.name = "张三"
        op.age = 19
// 设置代理
        op.delegate = self
        
        let btn = UIButton(type: .custom)
        btn.backgroundColor = UIColor.red
        btn.frame = CGRect(x: 100, y: 100, width: 100, height: 30)
        btn.addTarget(self, action: #selector(buttonClick), for: .touchUpInside)
        self.view.addSubview(btn)
    }
// 实现协议方法
    func people(_ people: People, didEatSomeThing thing: String) {
        
        print("People \(people.name) eat \(thing)")
    }
    
    func buttonClick() {
        
        self.dismiss(animated: true, completion: nil)
    }
    
    deinit {
        print("deinit")
    }

这里的代理没有做任何操作, 实现协议方法及设置协议代理, 都是模拟使用.
最后, 我们在ViewController内实例化一个PeopTestViewController, 并模态出来, 在返回的时候, 会发现并没有释放实例化的PeopTestViewController实例, 即没有走 deinit 方法:

PeopTestViewController实例对象--持有-->People实例对象
People 实例对象的代理---持有---> PeopTestViewController实例对象

这就引起了循环引用.

3.2 避免循环引用

避免循环引用的方法很简单, 只需要在People类中的协议代理设置为弱引用即可, 好, 加上 weak 修饰吧.
这时候会发现, 报错了:

g

这是因为weak只能修饰类, 只需要将我们的协议继承自NSObjectProtocol即可:

// 修改之后的 PeopleProtocol 
protocol PeopleProtocol: NSObjectProtocol {
    
    func people(_ people: People, didEatSomeThing thing: String)
}

这样, 就可以正常编译, 并正常释放内存了

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

推荐阅读更多精彩内容