GeekBand.Swift-Homework13

2016/5/24 posted in  iOS学习笔记  

题目

请说出下面代码存在的问题,以及改进方式。

class Customer{
    let name:String;
    var card:CreditCard?;
    init(name:String){
        self.name = name;
    }
    deinit{
        print("\(name) is being deinitialized");
    }
}
class CreditCard{
    let number:UInt64;
    let customer:Customer;
    init(number:UInt64,customer:Customer){
        self.number = number;
        self.customer = customer;
    } 
    deinit{
        print("Card #\(number) is being deinitialized");
    }
}

回答

问题分析

题目代码所描述的关系为用户——信用卡关系。在设计过程中,用户类中有信用卡属性,信用卡中有用户属性。
如果在实际操作过程中,执行如下代码:

var customer :Customer?;
customer = Customer(name:"Jobs");
var card :CreditCard;
card = CreditCard(number: 10000,customer: customer!);
customer!.card = card;

执行之后内存关系如图:

可见,执行之后card和customer之间构成了循环引用,在ARC(自动引用计数)模式之下,会导致两个变量没办法被回收,造成内存溢出。执行如下代码:

card = nil;
customer = nil;

内存关系如图:

card和customer的引用计数非0,无法吸收。

解决方法

方案A——手动打破循环引用

程序员通过自己的操作,打破循环引用,使引用计数减少。

card = nil;
customer.card = nil;
customer = nil;

执行结果,deinit析构器成功执行

方案B-weak(弱)引用

弱引用是语言提供的打破循环引用的方式,弱引用在计算引用计数时计算。代码如下:

class Customer{
    let name:String;
    //注意这里!!
    weak var card:CreditCard?;
    init(name:String){
        self.name = name;
    }
    deinit{
        print("\(name) is being deinitialized");
    }
}
class CreditCard{
    let number:UInt64;
    let customer:Customer;
    init(number:UInt64,customer:Customer){
        self.number = number;
        self.customer = customer;
    }
    
    deinit{
        print("Card #\(number) is being deinitialized");
    }
}

var customer :Customer?;
customer = Customer(name:"Jobs");
var card :CreditCard?;
card = CreditCard(number: 10000,customer: customer!);
customer!.card = card;

card = nil;
customer = nil;

内存结构:

执行结果,deinit析构器成功执行

方案C-unowned(无主)引用

如果不允许属性的值为nil,我们可以将其设置为unowned引用,打破循环引用。

class Customer{
    let name:String;
    var card:CreditCard?;
    init(name:String){
        self.name = name;
    }
    deinit{
        print("\(name) is being deinitialized");
    }
}
class CreditCard{
    let number:UInt64;
    //注意这里!!!!
    unowned let customer:Customer;
    init(number:UInt64,customer:Customer){
        self.number = number;
        self.customer = customer;
    }
    
    deinit{
        print("Card #\(number) is being deinitialized");
    }
}

var customer :Customer?;
customer = Customer(name:"Jobs");
var card :CreditCard?;
card = CreditCard(number: 10000,customer: customer!);
customer!.card = card;
card = nil;
customer = nil;

内存模型:

执行结果,deinit析构器成功执行。

方案B和C的区别

弱引用在对象被释放后,ARC会将引用设置为nil,无主引用在对象被释放后,ARC不会设置nil,访问是会抛运行时错误(空悬指针)。

写在最后

其实还有一个不太合理的,Customer应该可以拥有多张卡。所以card属性应该为

var cards:Array<CreditCard>?;