iOS arc 内存管理

Cocoa内存管理机制

(1)当你使用new、alloc、copy方法创建一个对象时,该对象的保留计数器值为1.当不再使用该对象时,你要负责向该对象发送一条release或autorelease消息。这样,该对象将在其使用寿命结束时被销毁。
(2)当你通过其他方法获得一个对象时,这假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理。如果你打算在一段时间内拥有该对象,这需要保留它并确保在操作完成时释放它。
(3)如果你保留了某个对象,你需要(最终)释放或自动释放该对象。必须保持retain方法和release方法的使用次数相等。

“如果我使用了new、alloc或copy方法获得一个对象,则我必须释放或自动释放该对象。”只要你记住了这条规律,你就平安无事了。
无论什么时候拥有一个对象,有两间事情必须弄清楚:怎样获得该对象的?打算拥有该对象多长时间。

Objective-C的对象生成于堆之上,生成之后,需要一个指针来指向它。

(1) alloc:为一个新对象分配内存,并且它的引用计数为1。调用alloc方法,你便有对新对象的所有权
(2) copy:制造一个对象的副本(克隆体),该副本的引用计数为1,调用者具有对副本的所有权
(3) retain:使对象的引用计数加1,并且获得对象的所有权
(4) release:使对象的引用计数减1,并且放弃对象的所有权
(5) autorelease:使对象的引用计数在未来的某个时候减1,并且在那个时候放弃对象的所有

自动引用计数(ARC),是一项为Objective - C程序在编译时提供自动内存管理的功能。ARC可以让你把注意力集中在你感兴趣的代码,对象图,和你的应用程序中的对象之间的关系,让你不必再花费精力在retain和release操作上。正如下图所示,ARC可以减少开发中的内存管理步骤,简化开发。

ARC官方文档修订历史
This table describes the changes to Transitioning to ARC Release Notes.
Date
Notes
2012-07-17
Updated for OS X v10.8.
2012-03-14
Noted that under ARC properties are strong by default.
2012-02-16
Corrected out-of-date advice regarding C++ integration.
2012-01-09
Added note to search for weak references.
2011-10-12
First version of a document that describes how to transition code from manual retain/release to use ARC.

ARC Support Iphone Os 4.0 or later.

“parent” object should maintain strong references to its “children,” and that the children should have weak references to their parents.
You need to be careful about sending messages to objects for which you hold only a weak reference. If you send a message to an object after it has been deallocated, your application will crash. You must have well-defined conditions for when the object is valid.

使用ARC必须遵守的规则

不可以再显示调用dealloc、或实现调用retain、release、retainCount、autorelease这些方法。也不能使用@selector(retain), @selector(release),等等。

在ARC下去自定义dealloc方法不需要调用 [super dealloc],(实际上如果你调用了 [super dealloc],编译器会报错)。super的调用是由编译器自动强制执行的。

不能使用NSAllocateObject或NSDeallocateObject。

使用alloc来创建对象,由ARC来管理对象运行时的释放。

不能在C语言的结构体中使用对象指针。

建议使用Objective-C的class来管理数据格式,来代替C语言的struct。

不能隐式转换 id和void *。

你必须告诉编译器转换的类型。当你需要在obj-C的对象和Core Foundation 类型之间转换时,你可以通过函数的参数来做。详见“Managing Toll-Free Bridging”

不能使用NSAutoreleasePool对象。
不能使用memory Zone。

因为现在Objective-C运行时已经忽略NSZone了,所以没必要再使用NSZone了

Property 属性
assign: 简单赋值,不更改索引计数(Reference Counting)。
copy: 建立一个索引计数为1的对象,然后释放旧对象(开辟新的内存地址)
retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1

retain的实际语法为:

- (void)setName:(NSString *)newName {

    if (name != newName) {

       [name release];

       name = [newName retain];

       // name’s retain count has been bumped up by 1

    }

}

说了那么麻烦,其实接下来的话最重要:

如果你不懂怎么使用他们,那么就这样

  • 使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)
  • 使用copy: 对NSString
  • 使用retain: 对其他NSObject和其子类
nonatomic关键字:

atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

iOS 5 中对属性的设置新增了strong 和weak关键字来修饰属性(iOS 5 之前不支持ARC)

strong关键字:

strong 用来修饰强引用的属性;对应原来的retain。

该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者。

weak关键字:

weak 用来修饰弱引用的属性;对应原来的assign。

但是不同的是当对象被释放以后,对象自动赋值为nil;并且,delegate 和 Outlet 苹果推荐用 weak 属性来声明。同时,如上一回介绍的 iOS 5 之前的版本是没有 __weak 关键字的,所以 weak 属性是不能使用的。这种情况我们使用 unsafe_unretained。

OSMemoryNotification.h(内存警告)   
/*
 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
 
#ifndef _OSMEMORYNOTIFICATION_H_
#define _OSMEMORYNOTIFICATION_H_
 
#include <sys/cdefs.h>
 
/*
**  OSMemoryNotification.h
**  
**  Kernel-generated notification mechanism to alert registered tasks when physical memory
**  pressure reaches certain thresholds. Notifications are triggered in both directions
**  so clients can manage their memory usage more and less aggressively.
**
*/
 
__BEGIN_DECLS
 
struct timeval;
 
/*
** Opaque type for notification object
*/
 
typedef struct _OSMemoryNotification * OSMemoryNotificationRef;
 
/*
** Threshold values for notifications
*/
 
typedef enum {
        OSMemoryNotificationLevelAny      = -1,
        OSMemoryNotificationLevelNormal   =  0,
        OSMemoryNotificationLevelWarning  =  1,
        OSMemoryNotificationLevelUrgent   =  2,
        OSMemoryNotificationLevelCritical =  3
} OSMemoryNotificationLevel;
 
/*
** Creation routines. Returns the created OSMemoryNotificationRef in the note param.
** returns: 0 on success
**          ENOMEM if insufficient memory or resources exists to create the notification object
**          EINVAL if the threshold is not a valid notification level
*/
 
int OSMemoryNotificationCreate(OSMemoryNotificationRef *note);
 
/*
** returns: 0 on success
**          EINVAL if the notification is not an initialized notification object
*/
 
int OSMemoryNotificationDestroy(OSMemoryNotificationRef note);
 
/*
** Block waiting for notification
** returns: 0 on success, with the level that triggered the notification in the level param
**          EINVAL if the notification object is invalid
**          ETIMEDOUT if abstime passes before notification occurs
*/
int OSMemoryNotificationWait(OSMemoryNotificationRef note, OSMemoryNotificationLevel *level);
int OSMemoryNotificationTimedWait(OSMemoryNotificationRef note, OSMemoryNotificationLevel *level, const struct timeval *abstime);
 
/*
** Simple polling interface to detect current memory pressure level
*/
 
OSMemoryNotificationLevel OSMemoryNotificationCurrentLevel(void);
 
/*
** External notify(3) string for manual notification setup
*/
 
extern const char *kOSMemoryNotificationName;
 
__END_DECLS
 
#endif /* _OSMEMORYNOTIFICATION_H_ */
 

 

 

 

 

#import <libkern/OSMemoryNotification.h>

 

- (void)didReceiveMemoryWarning
 {
     NSLog(@"Recieve memory warning");
     NSLog(@"~~~~~~~~~~~~~~level~~~~~~~~~~~~~~~ %d", (int)OSMemoryNotificationCurrentLevel());
 }

程序通常情况下都先调用AppDelegate中的applicationDidReceiveMemoryWarning, 然后程序会通知各ViewController,调用其didRecieveMemoryWarning方法
为单独文件指定是否使用ARC

当你迁移一个久工程到ARC模式下, -fobjc-arc 编译开关被默认的设置在所有的Objective-C 源代码上。 你可以使用-fno-objc-arc 来为特殊的class停用ARC 。在Xcode的 target的“Build Phases”标签, 打开Compile Sources group,展开源代码列表, 双击你想要修改的源代码的名字,再弹出框里输入-fno-objc-arc,然后点Done按钮。

总结

  • 嵌入式设备中堆栈的内存大小都有严格的限制,所以内存的管理是个大问题,在编程过程中,及时释放我们不需要的内存对象,是基本原则。设计得不优雅的程序,可能会出现一系列的,你无可预料的问题,比如内存溢出,对象过早释放,导致程序直接crash。
  • ARC技术虽然能提供自动引用计数,省掉了让人烦人和容易遗漏的retain,release,autorelease等操作,其工作原理是将内存操作的代码(retain,release等)自动添加到需要的位置。即底层上使用和MRC手工引用技术一样的内存管理机制,所以使用ARC简化编码工作的同时,还是同样要对内存管理有深入的了解。
  • ARC技术和跟随Xcode4.2一起发布的,在缺省的工程模板里可以选择是否支持ARC技术。随着 iOS 5.1 的推出,Xcode也推出了4.3版本。在该版本下,ARC 有效时的属性(@property) 定义的时候,如果不明确指定所有权关键字,那么缺省的就是 strong。而在 Xcode4.2 中,即使 strong 也要显示指定。
arc下的内存泄露:

因为当一个对象存入到集合中的时候,默认会保存它的强指针,如果最后不对这个集合进行清空操作,一样会有内存溢出的情况

Person * p = [[Person alloc] init];
NSMutableArray * arr = [[NSMutableArray alloc] init];
[arr addObject:p];

把对象从集合中移除的时候,也会释放掉这个对象的强指针
[arr removeObject:p];
或者[arr removeAllObjects];
而接下来才是重点:
arr = nil;//如果不进行赋值为nil的操作,一样存在内存溢出的现象,赋值为nil系统会对其进行清空所有强指针的操作.
p = nil;

下面列举两种ARC导致内存泄露的情况。

1,循环参照

A有个属性参照B,B有个属性参照A,如果都是strong参照的话,两个对象都无法释放。

这种问题常发生于把delegate声明为strong属性了。

例,

@interface SampleViewController

@property (nonatomic, strong) SampleClass *sampleClass;

@end

@interface SampleClass

@property (nonatomic, strong) SampleViewController *delegate;

@end

上例中,解决办法是把SampleClass 的delegate属性的strong改为assing即可。

2,死循环

如果某个ViewController中有无限循环,也会导致即使ViewController对应的view关掉了,ViewController也不能被释放。

这种问题常发生于animation处理。

例,

比如,

CATransition *transition = [CATransition animation];

transition.duration = 0.5;

tansition.repeatCount = HUGE_VALL;

[self.view.layer addAnimation:transition forKey:"myAnimation"];

上例中,animation重复次数设成HUGE_VALL,一个很大的数值,基本上等于无限循环了。

解决办法是,在ViewController关掉的时候,停止这个animation。

-(void)viewWillDisappear:(BOOL)animated {

    [self.view.layer removeAllAnimations];

}

内存泄露的情况当然不止以上两种。
即使用了ARC,我们也要深刻理解iOS的内存管理机制,这样才能有效避免内存泄露。

arc的程序出现内存泄露怎办

实例一:

用arc和非arc混编,非arc的类在arc里实例化并且使用,在arc里居然出现内存泄露,而且应为是arc,所以无法使用release,autorelease和dealloc去管理内存。正常情况下应该是不会出现这种情况的,某一个类若是ARC,则在这个类里面都应该遵循ARC的用法,而无需关心用到的类是否是ARC的,同样,在非ARC类里面,就需要遵循内存管理原则。

用ARC,只是编译器帮你管理了何时去release,retain,不用ARC就需要你自己去管理,说到底只是谁去管理的问题,所以你再好好看看,可能问题与ARC无关。
如果实在找不到问题,建议你找到泄露的那个对象,将其赋值为nil,因为ARC里面,一旦对象没有指针指向,就会马上被释放。

实例二:

最近在学objective-c,我发现创建项目时如果使用了ARC,非常容易内存泄露,经常某个对象已经被释放掉了我还在使用,由于不太了解这个机制,现在我举出两个例子,请经验者帮我分析一下。
例子一:一开始,在AppDelegate.m的那个开始方法中时这样写的:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    

    // Override point for customization after application launch.

    self.window.backgroundColor = [UIColor whiteColor];

    //

    UITabBarController  tabBarController = [[UITabBarController alloc] init];

    [tabBarController setViewControllers:[self showConnectViewOnWindow]];

    [tabBarController setDelegate:self];

    //

    [[self window] addSubview: [tabBarController view]];

     

    [self.window makeKeyAndVisible];

    return YES;

}

然后,我还做了其他的工作:tabBarController中有tabBarItem,点击会调用一个方法。但是每次一点击,就会报unrecognized selector send to instance的错误,后来上网一查,说是要把tabBarController定义成全局变量,不然这个方法一结束,tabBarController就被释放掉了,这样点击产生时间的对象都没了,于是我把它定义成全局变量,确实可以了,但我的疑问是,为什么方法一结束他就会释放掉吗,[[self window] addSubview: [tabBarController view]];我这一句不是已经在self window里引用它了吗,他怎么还会被释放,我觉得java和C#里面这种情况是不会释放掉了。

例子二:在viewdidload方法里面:

    [self.navigationItem setTitle:Title];

     

    leftButton = [[UIBarButtonItem alloc] initWithTitle:Cancel 

                                                  style:UIBarButtonItemStyleBordered 

                                                 target:self 

                                                 action:@selector(CancleButtonClicked)];

    self.navigationItem.leftBarButtonItem = leftButton;

这里我给屏幕上方那个导航条加了一个左边的按钮,然后点击这个按钮后会用方法CancleButtonClicked来响应,但是我运行起来一点击,还是报unrecognized selector send to instances错误了,这里又是哪个对象释放了,leftButton吗?但是self.navigationItem.leftBarButtonItem = leftButton已经引用了啊。

解决方法:

例子一[[self window] addSubview: [tabBarController view]];
你只引用了tabBarController的view,没有引用tabBarController

例子二,不知道什么原因,看看有没有拼写错误吧。

另外,我感觉局部变量的内存一般只在它的生命周期内有效。出了它所定义的区域,即使不释放,也最好不要用了。

补充 : IOS开发 strong,weak,retain,assign,copy nomatic 等的区别与作用

一、strong,weak,retain,assign,copy nomatic 等的区别

copy与retain:
1、copy其实是建立了一个相同的对象,而retain不是;
2、copy是内容拷贝,retain是指针拷贝;
3、copy是内容的拷贝 ,对于像NSString,的确是这样,但是如果copy的是一个NSArray呢?这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制".
4、copy的情况:NSString *newPt = [pt copy];
此时会在堆上重新开辟一段内存存放@"abc" 比如0X1122 内容为@"abc 同时会在栈上为newPt分配空间 比如地址:0Xaacc 内容为0X1122 因此retainCount增加1供newPt来管理0X1122这段内存;

assign与retain:

1、assign: 简单赋值,不更改索引计数;
2、assign的情况:NSString *newPt = [pt assing];
此时newPt和pt完全相同 地址都是0Xaaaa 内容为0X1111 即newPt只是pt的别名,对任何一个操作就等于对另一个操作, 因此retainCount不需要增加;
3、assign就是直接赋值;
4、retain使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收;
5、retain的情况:NSString *newPt = [pt retain];
此时newPt的地址不再为0Xaaaa,可能为0Xaabb 但是内容依然为0X1111。 因此newPt 和 pt 都可以管理"abc"所在的内存,因此 retainCount需要增加1 ;

readonly:
1、属性是只读的,默认的标记是读写,如果你指定了只读,在@implementation中只需要一个读取器。或者如果你使用@synthesize关键字,也是有读取器方法被解析

readwrite:
1、说明属性会被当成读写的,这也是默认属性。设置器和读取器都需要在@implementation中实现。如果使用@synthesize关键字,读取器和设置器都会被解析;

nonatomic:
1、非原子性访问,对属性赋值的时候不加锁,多线程并发访问会提高性能。如果不加此属性,则默认是两个访问方法都为原子型事务访问;

weak and strong property (强引用和弱引用的区别):
1、 weak 和 strong 属性只有在你打开ARC时才会被要求使用,这时你是不能使用retain release autorelease 操作的,因为ARC会自动为你做好这些操作,但是你需要在对象属性上使用weak 和strong,其中strong就相当于retain属性,而weak相当于assign。
2、只有一种情况你需要使用weak(默认是strong),就是为了避免retain cycles(就是父类中含有子类{父类retain了子类},子类中又调用了父类{子类又retain了父类},这样都无法release)
3、声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。

ARC(Automatic Reference Counting):
1、就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 以后可以使用该特性。

strong,weak,copy 具体用法:
1.具体一点:IBOutlet可以为weak,NSString为copy,Delegate一般为weak,其他的看情况。一般来说,类“内部”的属性设置为strong,类“外部”的属性设置为weak。说到底就是一个归属权的问题。小心出现循环引用导致内存无法释放。
2.不用ARC的话就会看到很多retian。
3.如果你写了@synthesize abc = _abc;的话,系统自动帮你声明了一个_abc的实例变量。
使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等)
使用copy: 对NSString

使用retain: 对其他NSObject和其子类

readwrite,readonly,assign,retain,copy,nonatomic属性的作用

@property是一个属性访问声明,扩号内支持以下几个属性:

1,getter=getterName,setter=setterName,设置setter与getter的方法名

2,readwrite,readonly,设置可供访问级别

2,assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题

3,retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序(CC上有相关资料)

4,copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。

copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。

5,nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级(我是这么理解的...)。

atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错 误的结果。

懒加载
一直知道懒加载是什么东西,但是没怎么用过,最近准备用的时候一直不能调用懒加载的方法,最近趁着项目刚刚结束,所以有空研究了一些小东西。东西虽然很简单,但是感觉还是挺实用的。

所谓的懒加载可以定义为:延时加载,即当对象需要用到的时候再去加载。其实就是所谓的重写对象的get方法,当系统或者开发者调用对象的get方法时,再去加载对象。

需要注意:重写get方法时,先判断对象当前是否为空,为空的话再去实例化对象 当使用self.xxx会调用xxx的get方法而_xxx并不会调用,正确的使用个方式是通过self去调用才会执行懒加载方法 我就是因为一直没怎么注意self.xxxh 和 _xxx的区别,一直实用_xxx,所以才导致懒加载方法一直没有被调用
懒加载的优点 不需将对象的实例化写到viewDidLoad,可以简化代码,增强代码的可读性 对象的实例化在getter方法中,各司其职,降低耦合性 对系统的内存占用率会减小代码实例

import "ViewController.h" @interface ViewController () @property (nonatomic,strong)NSMutableArray groupCarArray; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self createButton]; } - (void)createButton{ UIButton button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(100, 100, 100, 50); button.baCKgroundColor = [UIColor orangeColor]; [button setTitle:@"按钮" forState:UIControlStateNormal]; [button addTarget:self action:@selector(Mybutton) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } - (void)Mybutton{ //调用方法 注意是self.groupCarArray,而不是_groupCarArray NSLog(@"%@",self.groupCarArray); } //懒加载数据 - (NSArray *)groupCarArray { if (_groupCarArray == nil) { NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"info.plist" ofType:nil]]; [_groupCarArray addObjectsFromArray:array]; } return _groupCarArray; } @end strong,weak,retain,assign,copy

weak和strong的区别:

weak和strong不同的是 当一个对象不再有strong类型的指针指向它的时候 它会被释放 ,即使还有weak型指针指向它。一旦最后一个strong型指针离去 ,这个对象将被释放,所有剩余的weak型指针都将被清除。

copy与retain的区别

copy其实是建立了一个相同的对象,而retain不是;copy是内容拷贝,retain是指针拷贝;copy是内容的拷贝 ,对于像NSString,的确是这样,如果拷贝的是NSArray这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制"

set和get方法
目的:为了能让类的成员变量正确的被外接访问,我们需要设置set和get方法。 用property就会自动生成get,set方法的过程中处理好retain,copy,release的关系,而且还可以在main中调用时使用

  1. set 函数 set 函数,对成员变量赋值。 Set函数的一般写法: -(void)setAge:(int)newage; 2.Get函数 getter函数,对成员变量取值。Get函数一般写法 -(int)age;
    点运算符在oc的类对象中不能访问成员变量,如dog.age=5,这里不是使用成员变量age,而是调用成员方法setAge,相当于给dog.age赋值,调用方法[dog setAge:5];

" . " 不能调用成员变量,只是调用set函数和get函数的一种简写。

Blocks理解
Blocks可以访问局部变量,但是不能修改如果修改局部变量,需要加__block

__block int multiplier = 7; int (^myBlock)(int) = ^(int num) { multiplier ++;//这样就可以了 return num * multiplier; }; __weak __typeof(*self)weakSelf =self; 等同于 __weak UIViewController *weakSelf =self; 为什么不用__block 是因为通过引用来访问self的实例变量 ,self被retain,block也是一个强引用,引起循环引用,用__week是弱引用,当self释放时,weakSelf已经等于nil。 在引用计数的环境里面,默认情况下当你在block里面引用一个Objective-C对象的时候,该对象会被retain。当你简单的引用了一个对象的实例变量时,它同样被retain。但是被__block存储类型修饰符标记的对象变量不会被retain

推荐阅读更多精彩内容