TSL对象的生命周期中,采用的是引用计数模式,依赖对象的引用计数。
在对对象进行赋值等操作时,不是新增一个对象,而是指向该对象,并用引用数来记录该对象的生命周期。其过程为,当创建或引用对象时会使引用计数加1,失去引用时将使得引用计数减1,当引用计数为0时,会对该对象进行释放,这就是对象的一个完整的生命周期。
为了诠释这个过程,我们可以看下面这段代码:
A:=new TA(); //创建一个实例对象,增加一个引用数(创建对象,yN=1)
B:=A; //B指向A的实例对象,增加一个引用数(yN=2)
Echo "Set A to NIL\r\n";
A:=nil; //注销对象,引用数减一(yN=1)
Echo "Set B to NIL\r\n";
B:=nil; //注销对象,引用数减一(yN=0,此时对象被完全释放)
Echo "Run end\r\n";
Type TA=class
Public
Function Destroy();
Begin
Echo "Destroy\r\n";
End;
End;
执行后打印结果为:
Set A to NIL
Set B to NIL
Destroy
Run end
从这个打印结果可以看出,创建对象A并被赋值给B后,当设置A的值为nil时(即注释原A创建的对象),该对象并没有直接被析构,而是等到B也被赋值为nil时,才被析构释放。
这种采用对象引用计数的设计它的优势有易于使用,不会非法引用,生命周期可以自动管理,性能高,低内存开销等。有些类似于CopyOnWrite模式,当将对象赋值给另一个变量时不会对内存进行拷贝,而只是产生引用计数。与CopyOnWrite的区别是,即便是对对象进行写入,也不会产生新的对象,而是直接与入当前对象。
采用引用计数在方便应用的同时,也带来了循环引用的问题。那么,什么是循环引用?再看一段代码:
A:=new TA();
Echo "Set A to NIL\r\n";
A:=nil;
Echo "Run end\r\n";
Type TA=class
FB;
Public
Function Create();
Begin
FB:=new TB(self);
end;
Function Destroy();
Begin
Echo "Destroy\r\n";
End;
End;
| type TB=class
FOwner;
Public
function create(Owner);
begin
FOwner:=Owner;
end;
end;
打印结果:
Set A to NIL
Run end |  |
通过上面的代码可以看出,A创建了一个TA实例对象,而在创建的时候T.FB指向了类TB的一个实例,而TB在创建实例时,又将A.FB.Fowner指向了A,于是就产生了循环引用,这时,通过打印结果可以看出,将A设置为nil时,立马做了Echo “Run end\r\n”操作,而没有对实例对象进行析构(Destroy()方法没有被执行),这是为什么呢?
原因是循环引用会让自身拥有多一个引用计数,循环引用后,引用计数因为自身的循环引用导致永远无法被减到0。
也就是说循环引用会导致对象无法被释放,这类对象我们称之为无主对象,即垃圾对象。这种情况会导致原对象的析构函数无法被执行以及当产生很多垃圾对象时会导致占用许多内存,影响内存开销。
循环引用的主要产生场景
可以看出,在上面这些场景下,循环引用是必然会发生的事情,在这种情况下,用户就需要主动对循环引用对象在不需要的时候提前进行解耦,显然,这种方式下应用起来是比较麻烦的,因此,天软引入弱引用,能够比较好地解决该问题。我们本文中重点就是介绍,如何使用弱引用解决在对象中存在的循环引用问题。