1.好多人问我Swift的内存管理是怎样的,当你也想问这样类似的问题或困惑时时,你可以参考本文。也从内存的角度上解释为什么会出现循环引用!
2.你需要了解Swift 、知道[weak self] 以及 [unowned self] 是什么?(提示:与循环引用有关 ),你最好明白这个否则会很疑惑我下面所说的。
首先我们要明白几件事:
- 值类型与引用类型
- 栈(stack)与堆(heap)
- 循环引用导致内存泄露
- 如何解决内存泄露
1.ARC与内存管理有什么关系?
ARC是内存管理的一种,还有MRC和自动释放池,在Swift中不支持MRC,而且MRC也不如ARC管理的好;无论是ARC还是MRC都是对引用类型的引用计数进行管理,如果引用计数为0,则由系统自动释放对象,MRC和ARC的最主要区别就是是否人为控制引用计数;所以了解ARC也就是了解了iOS的内存管理。
2.引用类型与ARC的关系?
ARC 名为自动引用计数,只有引用类型,才有引用计数
引用类型储存在内存当中的堆区(heap),创建引用对象时,引用计数自动加一,当不需要该对象时引用计数减一,引用对象类型就是多个对象指向一个地址,有多少个对象只想这个地址,那么引用计数就是几。
3.在swift中引用类型有哪些?
引用类型:class closure 即:类和闭包
4.值类型呢?内存管理不需要考虑值类型吗?
值类型储存在栈(stack)中,由系统自动分配,并不需要程序猿释放内存,同时在函数执行完毕后这些空间都将要被释放
5.常见的循环引用
1.引用类型总是被分配到“堆”上。
2.值类型总是分配到它声明的地方:
a.作为引用类型的成员变量分配到“堆”上
b.作为方法的局部变量时分配到“栈”上
线程开始执行函数,函数参数被压入线程堆栈。
引用类型,它被分配在“堆”上,并且由一个位于“栈”上的指针引用。
函数执行完毕后,“栈”同样会被清空,此时堆上存留保存引用类型的内存,也就是垃圾,我们需要手动释放掉,苹果为了不用人为控制内存释放,也并非用GC机制来处理,于是乎判断引用计数是否为零,如果为零就释放掉内存,如果是手动释放的就叫MRC,自动释放的就叫ARC。
如果,两个引用类型之间相互引用,引用指针由于是引用类型的成员变量,所以他会被分配在堆中,当清空栈的时候,此时推上还有两个相互引用的内存,我们需要释放掉这块内存,堆上的指针相互引用,导致ARC没有办法释放掉内存,
1). 两个类互相持有对方,导致系统没有办法释放内存。
2). 类中有闭包,导致内存泄露
6.swift中解决循环引用
- [weak self] self?/.
- [weak weakSelf = self] weakSelf?.
- [unowned self] self.
weak与 unowned 的作用 ,weak是一个弱引用,当释放一个对象时,他并不会阻挠,从而破坏循环引用,unowned 是不持有引用,也就是不持有self 从而破坏循环引用。
7.关于GC机制与ARC的区别?
推荐文章 http://zhang.hu/arc-vs-gc/
- 引用计数 (ARC,Automatic Reference Counting)
- GC (Garbage Collection)
因为 Java 的流行,GC 被广泛的认知。GC 简单的说是定期查找不再使用的对象,释放对象占用的内存。
基于 GC,申请的对象不需要手动释放,只需要确认对象在不再需要时,不再被其他对象引用。
引用计数早期主要用于底层系统,比如文件系统的 inode 管理,后来 C++ 的 boost 库实现了一套完整的 ARC,目前流行的系统还有 Objective C 也是采用的 ARC。
ARC 的特点是,一个对象被引用时,引用计数增加 1,引用对象释放时,引用计数减少 1,如果引用计数为 0,释放对象。
这个时候你可能想知道堆和栈的具体差异:
参考这篇文章:http://blog.csdn.net/changyourmind/article/details/51816768
c++语言的,但原理是一样的。
堆和栈的主要区别由以下几点:
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
(1)管理方式
对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄露。
(2)空间大小:
一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看 堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M。当然,这个值可以修改。
(3)碎片问题
对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构。
(4)生长方向
对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
(5)分配方式
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的, 比如局部变量的分配。动态分配由malloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
(6)分配效率
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
8.关于深拷贝与浅拷贝
浅拷贝:拷贝指针
深拷贝:拷贝指针和地址
值类型的赋值操作是深拷贝,引用类型的赋值操作是浅拷贝
9.关于单例,这个其实和内存管理没有太大关系,为了方便写在这里?
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
单例首先要保证只能被初始化一次,static这个关键字可以保证他是静态局部变量,并且只初始化一次,这样我们就可以获取到这个实例,private 能够保证他不能被通过其他方式获取到对象。关于线程是否安全,这个有待考虑。
10.全局变量、局部变量、静态全局变量、静态局部变量在内存里的区别?
这个时候你再看下面的这些是不是很清晰?
一个由C/C++编译的程序占用的内存分为以下几个部分:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放
4、文字常量区 — 常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区 — 存放函数体的二进制代码。
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
从分配内存空间看:
全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间。
从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
当关于内存你能理解这些时,你已经是中高级程序猿了,再研究就到了底层机制,可能话费很长时间也不会有太多效果。
网友评论