建设网站合同,基本网站建设知识,网站中搜索栏怎么做,小程序 wordpressEffective objective-c-- 内存管理 前言理解引用计数引用计数工作原理属性存取方法中的内存管理自动释放池保留环要点 以ARC简化引用计数使用ARC时必须遵循的方法和命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理方法要点 在dealloc方法中只释放引用并解除监听要点… Effective objective-c-- 内存管理 前言理解引用计数引用计数工作原理属性存取方法中的内存管理自动释放池保留环要点 以ARC简化引用计数使用ARC时必须遵循的方法和命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理方法要点 在dealloc方法中只释放引用并解除监听要点 编写“异常安全代码“时留意内存管理问题以弱引用避免保留环unsafe_unretained 和 weak要点 以”自动释放池“释放内存峰值要点不要使用retainCount 前言
寒假比较忙没能能认真看就拖到学校了来看了进度落后很多了只能尽量赶了
理解引用计数 Objective-C 语言使用引用计数来管理内存也就是说每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活那就递增其引用计数用完了之后就递减其计数。计数变为 0就表示没人关注此对象了于是就可以把它销毁。 要注意开启ACR功能后引用计数的方法无法使用
引用计数工作原理
在引用计数架构下对象有个计数器用以表示当前有多少个事物想令此对象继续存活下去。这在 Objective-C 中叫做 “保留计数” retain count不过也可以叫 “引用计数”reference count。NSObject 协议声明了下面三个方法用于操作计数器以递增或递减其值
Retain 递增保留计数。 release 递减保留计数。 autorelease 待稍后清理 “自动释放池”autorelease pool时再递减保留计数。
查看保留计数的方法叫做 retainCount此方法不太有用
图演示了对象自创造出来之后历经一次 “保留” 及两次 “释放” 操作的过程。
使用引用计数的方法要关闭ACR方法如下在bulid settings-all-Combined-Apple Clang-language-oBjectiveC-Automatic Reference Counting设置为NO
看下面这段代码 NSMutableArray* array [[NSMutableArray alloc] init] ;NSNumber* number [[NSNumber alloc] initWithInt:15] ;
// [array addObject:number] ;[number release] ;NSLog(%,number) ;
// NSLog(%lu,(unsigned long)[number retainCount]) ;
// NSLog(%,[array objectAtIndex:0]) ;[array release] ;上面这段代码我是想看看number在引用计数为0是调用该对象会不会崩溃不过出乎意料的是是没有崩这里的解释是 在某些情况下对已释放的对象进行访问可能不会立即导致崩溃。这是因为已释放的对象在内存中仍然存在一段时间并且指针仍然指向该内存位置。这被称为“悬垂指针”Dangling Pointer。 当你尝试访问已释放的对象时可能会发生以下情况之一 你可能会幸运地访问到一个仍然有效的对象。在这种情况下你可能不会立即遇到崩溃或错误。 你可能会访问到无效的对象或者已经被其他对象覆盖的内存。在这种情况下你可能会遇到崩溃、内存访问错误或者其他不可预测的行为。
书上的例子 objectivec
NSMutableArray *array [[NSMutableArray alloc] init];NSNumber *number [[NSNumber alloc] initWithInt:2023];[array addObject:number];[number release];[array release];创建数组之后把number加入其中的时候系统会为number retain一次也就是number的引用计数为2接下来不需要number对象的时候我们释放了它在这个例子里能知道number的引用计数现在还为1
为了避免在不经意间使用了无效对象一般relase之后都会清空指针这样能保证不出现悬空指针
NSMutableArray *array [[NSMutableArray alloc] init];NSNumber *number [[NSNumber alloc] initWithInt:2023];[array addObject:number];[number release];[array release];number nil;属性存取方法中的内存管理
不光是数组其他对象也可以保留别的对象这一般通过访问 “属性”来实现而访问属性时会用到相关实例变量的获取方法及设置方法。若属性为 “strong 关系”strong relationship则设置的属性值会保留。比方说有个名叫 foo 的属性由名为 _foo 的实例变量所实现那么该属性的设置方法会是这样 此方法将保留新值并释放旧值然后更新实例变量令其指向新值。顺序很重要。假如还未保留新值就先把旧值释放了而且两个值又指向同一个对象那么先执行的 release 操作就可能导致系统将此对象永久回收。而后续的 retain 操作则无法令这个已经彻底回收的对象复生于是实例变量就成了悬挂指针了。这里上面也提到了
自动释放池 在 Objective-C 的引用计数架构中自动释放池是一项重要特性。调用 release 会立刻递减对象的保留计数而且还有可能令系统回收此对象然而有时候可以不调用它改为调用 autorelease此方法会在稍后递减计数通常是在下一次 “事件循环”event loop时递减不过也可能执行得更早些。 对于上面这个方法我们需要延迟str对象的回收释放也就是说我们要用automatic release 用 autorelease它会在稍后释放对象从而给调用者留下了足够长的时间使其可以在需要时先保留返回值。换句话说此方法可以保证对象在跨越 “方法调用边界”method callboundary后一定存活。实际上释放操作会在清空最外层的自动释放池参见第 34 条时执行除非你有自己的自动释放池否则这个时机指的就是当前线程的下一次事件循环
autorelease 能延长对象生命周期使其在跨越方法调用边界后依然可以存活一段时间。
保留环
使用引用计数机制时经常要注意的一个问题就是 “保留环”retain cycle也就是呈环状相互引用的多个对象。这将导致内存泄漏因为循环中的对象其保留计数不会降为 0。对于循环中的每个对象来说至少还有另外一个对象引用着它。图里的每个对象都引用了另外两个对象之中的一个。在这个循环里所有对象的保留计数都是 1。
在垃圾收集环境中通常将这种情况认定为 “孤岛”island of isolation。此时垃圾收集器会把三个对象全都回收走。而在 Objective-C 的引用计数架构中则享受不到这一便利。通常采用 “弱引用”weak reference参见第 33 条来解决此问题或是从外界命令循环中的某个对象不再保留另外一个对象。这两种办法都能打破保留环从而避免内存泄漏。
要点
引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后其保留计数至少为 1。若保留计数为正则对象继续存活。当保留计数降为 0 时对象就被销毁了。在对象生命期中其余对象通过引用来保留或释放对象。保留于释放操作分别会递增及递减保留计数。
以ARC简化引用计数 此代码有内存泄漏问题因为 if 语句块末尾并未释放 message 对象。由于在 if 语句之外无法引用 message所以此对象所占的内存泄漏了这里“泄漏”的意思是没有正确释放已经不再使用的内存。
由于 ARC 会自动执行 retain、release 、autorelease 等操作所以直接在 ARC 下调用这些内存管理方法是非法的。
ARC的优点除了方便外还有ARC 在调用这些方法时并不通过普通的 Objective-C 消息派发机制而是直接调用其底层 C 语言版本。这样做性能更好因为保留及释放操作需要频繁执行所以直接调用底层函数能节省很多 CPU 周期。
使用ARC时必须遵循的方法和命名规则
将内存管理语义在方法名中表示出来早已成为 Objective-C 的惯例而 ARC 则将之确立为硬性规定。这些规则简单地体现在方法名上。若方法名以下列词语开头则其返回的对象归调用者所有
allocnewcopymutableCopy
归调用者所有的意思是: 调用上述四种方法的那段代码要负责释放方法所返回的对象。也就是说这些对象的保留计数是正值而调用了这四种方法的那段代码要将其中一次保留操作抵消掉。
要注意如果还有其他对象保留此对象并对其调用了 autorelease那么保留计数的值可能比 1 大这也是 retainCount 方法不太有用的原因之一。
若方法名不以上述四个词语开头则表示其所返回的对象并不归调用者所有。 在这种情况下返回的对象会自动释放
维系这些规则所需的全部内存管理事宜均由 ARC 自动处理
变量的内存管理语义
ARC 也会处理局部变量与实例变量的内存管理。 默认情况下每个变量都是指向对象的强引用。
对于以下的代码 在非ARC下执行的setter方法的实现是这样的
但这个方法很明显是不安全的如果只有当前对象还在引用这个值那么设置方法中的释放操作会使该值的保留计数降为0从而导致系统将其回收。接下来再执行保留操作就会令应用程序崩溃。
ARC 会用一种安全的方式来设置先保留新值再释放旧值最后设置实例变量。 在应用程序中可用下列修饰符来改变局部变量与实例变量的语义 __strong: 默认语义保留此值。 __unsafe_unretained: 不保留此值这么做可能不安全因为等到再次使用变量时其对象可能已经回收了。 __weak: 不保留此值但是变量可以安全使用因为如果系统把这个对象回收了那么变量也会自动清空。 __autoreleasing: 把对象 “按引用传递” pass by reference给方法时使用这个特殊的修饰符。此值在方法返回时自动释放。 我们经常会给局部变量加上修饰符用以打破由“块”block所引入的“保留环”retain cycle。块会自动保留其所捕获的全部对象而如果这其中有某个对象又保留了块本身那么就可能导致 “保留环”。可以用 __weak 局部变量来打破这种 “保留环”:
ARC如何清理实例变量
要管理其内存ARC 就必须在 “回收分配给对象的内存”deallocate 当手动管理引用计数时你可能会像下面这样自己来编写 dealloc 方法
如果有非 Objective-C 的对象不需要像原来那样调用超类的 dealloc 方法。 ARC 环境下dealloc 方法可以像这样写:
因为 ARC 会自动生成回收对象时所执行的代码所以通常无须再编写 dealloc 方法。这能减少项目源代码的大小而且可以省去其中一些样板代码(boilerplate code)。
覆写内存管理方法
不使用ARC时可以覆写内存管理方法。比方说在实现单例类的时候因为单例不可释放所以我们经常覆写release方法将其替换为“空操作”
要点
有 ARC 之后程序员就无须担心内存管理问题了。使用 ARC 来编程可省去类中的许多 “样板代码”。ARC 管理对象生命期的办法基本上就是在合适的地方插入 “保留” 及 “释放”操作。在 ARC 环境下变量的内存管理语义可以通过修饰符指明而原来需要手工执行 “保留” 及 “释放”操作。由方法所返回的对象其内存管理语义总是通过方法名来体现。ARC 将此确定为开发者必须遵守的规则。ARC 只负责管理 Objective-C 对象的内存。尤其要注意 CoreFoundation 对象不归 ARC 管理开发者必须适时调用 CFRetain/CFRelease。
在dealloc方法中只释放引用并解除监听
对象在经历其生命期后最终会为系统所回收这时就要执行 dealloc 方法了。在每个对象的生命期内此方法仅执行一次也就是当保留计数降为 0 的时候。 实际上程序库会以开发者察觉不到的方式操作对象从而使回收对象的真正时机和预期的不同。你决不应该自己调用 dealloc 方法运行期系统会在适当的时候调用它。
那么应该在 dealloc 方法中做什么呢
主要就是释放对象所拥有的引用也就是把所有 Objective-C 对象都释放掉对象所拥有的其他非 Objective-C 对象也要释放。比如 CoreFoundation 对象就必须手工释放因为它们是由纯C 的API 所生成的。 所以可以是
最好还要加上[super dealloc]
编写 dealloc 方法时还需要注意不要在里面随便调用其他方法。
如果在这里所调用的方法又要异步执行某些任务或是又要继续调用它们自己的某些方法那么等到那些任务执行完毕时系统已经把当前这个待回收的对象彻底摧毁了。这会导致很多问题且经常使应用程序崩溃因为那些任务执行完毕后要回调此对象告诉该任务已完成而此时如果对象已摧毁那么回调操作就回出错。在 dealloc 里也不要调用属性的存取方法因为有人可能会覆写这些方法并与其中做一些无法在回收阶段安全执行的操作。
要点
在 dealloc 方法里应该做的事情就是释放指向其他对象的引用并取消原来订阅的“键值观测”KVO或 NSNOtificationCenter 等通知不要做其他事情。如果对象持有文件描述符等系统资源那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定: 用完资源后必须调用 close 方法。执行异步任务的方法不应该在 dealloc 里调用; 只能在正常状态下执行的那些方法也不应在 dealloc 里调用因为此时对象已处于正在回收的状态了。
编写“异常安全代码“时留意内存管理问题
纯 C 中没有异常而 C 与 Objective-C 都支持异常。在当前的运行期系统中C 与 Objective-C 的异常相互兼容也就是说从其中一门语言里抛出的异常能用另外一门语言所编写的 “异常处理程序”exception handler来捕获。
比如使用 Objective-C 来编码时或是编码中用到了第三方程序库而此程序库所抛出的异常又不受你控制时就需要捕获及处理异常了。
C 的析构函数destructor由 Objective-C 的异常处理例程exception-handle routine来运行。这对于 C 对象很重要由于抛出异常会缩短其生命周期所以发生异常时必须析构不然就会泄漏
手动管理的方式如下
但上面的方法中如果dosomethingThatMayThrow方法判断抛出异常会直接进入catch块所以没能release导致内存泄漏解决方法时在设一个finalllay块将release操作放到其中。
以弱引用避免保留环
首先我们要了解什么是保留环 对象图里经常会出现一种情况就是几个对象都以某种方式互相引用从而形成“环”cycle。由于 Objective-C 内存管理模型使用引用计数架构所以这种情况通常会泄漏内存因为最后没有别的东西会引用环中的对象。
下面给一个书上的例子 两个类之间的引用关系如图 上面就是一个简单的保留环即使外界不在引用这两个对象这两个对象依赖于彼此的引用关系其引用计数不会降到0即他们不会被系统自动回收造成内存泄漏 下面用图表示一个更复杂的保留环 如果只剩一个引用还指向保留环中的实例而现在又把这个引用移除那么整个保留环就泄漏了。也就是说没办法再访问其中的对象了。图中所示的保留环更复杂一些其中有四个对象只有 ObjectB 还为外界所引用把仅有的这个引用移除之后四者所占内存就泄漏了。 避免保留环的最佳方式就是弱引用。这种引用经常用来表示 “非拥有关系”nonowning relationship)。将属性声明为 unsafe_unretained 可以把上面的代码修改成下面这样 修改之后EOCClassB 实例就不再通过 other 属性来拥有 EOCClassA 实例了。属性特质 (attribute) 中的 unsafe_unretained 一词表明属性值可能不安全而且不归此实例所拥有。如果系统已经把属性所指的那个对象回收了那么在其上调用方法可能会使应用程序崩溃。由于本对象并不保留属性对象因此其有可能为系统所回收。
unsafe_unretained 和 weak
weak与 unsafe_unretained 的作用完全相同。然而只要系统把属性回收属性值就会自动设为 nil。 使用 weak 而非 unsafe_unretained 引用可以令代码更安全。应用程序也许会显示出错误的数据但不会直接崩溃。这么做显然比令终端用户直接看到程序退出要好。
要点
将某些引用设为 weak可避免出现 “保留环”。weak 引用可以自动清空也可以不自动清空。自动清空autonilling是随着 ARC 而引入的新特性由运行期系统来实现。在具备自动清空功能的弱引用上可以随意读取其数据因为这种引用不会指向已经回收过的对象。
以”自动释放池“释放内存峰值
创建自动释放池所用语法如下: 一般情况下无须担心自动释放池的创建问题。 通常只有一个地方需要创建自动释放池那就是在 main 函数里我们是自动释放池来包裹应用程序的主入口点 (main application entry point)。 自动释放池于左花括号处创建并于对应的右花括号处自动清空。位于自动释放池范围内的对象将在此范围末尾处收到 release 消息。自动释放池可以嵌套。系统在自动释放对象时会把它放到最内层的池里。 将自动释放池嵌套用的好处是可以借此控制应用程序的内存峰值使其不致过高。 上面这段代码的for循环中会产生许多多余的变量占用内存由于无法通过指针来直接调用release来释放内存所以这里最好设置自动释放池来控制内存
自动释放池机制就像 “栈”stack一样。系统创建好自动释放池之后就将其推入栈中而清空自动释放池则相当于将其从栈中弹出。在对象上执行自动释放操作就等于将其放入栈顶的那个池里。
是否应该用池来优化效率完全取决于具体的应用程序。首先得监控内存用量判断其中有没有需要解决的问题如果没完成这一步那就别急着优化。尽管自动释放池块的开销不太大但毕竟还是有的所以尽量不要建立额外的自动释放池。
要点
自动释放池排布在栈中对象收到 autorelease 消息后系统将其放入最顶端的池里。合理运用自动释放池可降低应用程序的内存峰值。autoreleasepool 这种新式写法能创建出更为轻便的自动释放池。
不要使用retainCount
如果在 ARC 中调用编译器就会报错这和在 ARC 中调用 retain、release、autorelease 方法时的情况一样。虽然此方法已经正式废弃了但还是经常有人误解它其实这个方法根本就不应该调用。若在不启用 ARC 的环境下编程说真的还是在 ARC 下编程比较好那么仍可调用此方法而编译器不会报错。 此方法之所以无用其首要原因在于它返回的保留计数只是某个给定时间点上的值。该方法并未考虑到系统会稍后把自动释放池清空因而不会将后续的释放操作从返回值里减去这样的话此值就未必能真实反映实际的保留计数了。 那么只为了调试而使用 retainCount 方法行不行呢即便只为调试此方法也不是很有用。由于对象可能处在自动释放池中所以其保留计数未必如想象般精确。而且其他程序库也可能自行保留或释放对象这都会扰乱保留计数的具体取值。看了具体的计数值之后你可能还误以为是自己的代码修改了它殊不知其实是由深埋在另外一个程序库中的某段代码所改的。
对象的保留计数看似有用实则不然因为任何给定时间点上的“绝对保留计数”absolute retain count都无法反映对象生命期的全貌。引入 ARC 之后retainCount 方法就正式废止了在 ARC 下调用该方法会导致编译器报错。