iOS深入学习:手动内存管理怎样编码
iOS深入学习:手动内存管理怎样编码
召唤攻城狮 发表于4年前
iOS深入学习:手动内存管理怎样编码
  • 发表于 4年前
  • 阅读 329
  • 收藏 1
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

摘要: 我们知道了内存管理的原则,这篇博客主要记录内存管理时候的一些编码规范

我建了一个iOS开发QQ交流群:188647173,大家可以一起来相互学习。

还有一个群里面大神的个人站点www.mylonly.com,大家有不会的可以向他请教。


iOS的对象都继承自NSObject,NSOjbect对象有一个方法retainCount,用以获取对象的内存引用计数。一般情况下,一下几种情况会使对象的引用计数发生改变,

(1)alloc(或new) 对象分配后,引用计数为1

(2)retain 调用retain方法(或者说向对象发送retain消息),对象的引用计数增加1

(3)copy 调用copy方法(或者说向对象发送copy消息),创建一个新的对象,其引用计数为1;原来对象的引用计数不变。

(4)release 向对象发送release消息,对象内存引用计数较少1,如果引用计数为0,那么就释放对象所占有的内存

(5)autorelease 想对象发送autorelease消息,对象引用计数较少1,如果引用计数为0,不会马上释放对象内存,等到最近一个自动释放池autoreleasepool时候释放。


那么我们以代码添加一个UIImageView为例,看看内存管理的代码应该怎么写,

第一种情况,局部变量的内存管理

- (void)viewDidLoad

{

    [super viewDidload];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:...];//通过alloc/init创建对象,retainCount=1

    [self.view addSubview:imageView];//将其添加到superView,引用计数加1,retainCount=2

    [imageView release];//向对象发送release消息,引用计数减1,retainCount=1

}

上面的代码[self.view addSubview:imageView];造成imageView引用计数增加1,可能让人不明白,所以这里需要说明一下,在子视图添加到父视图的时候,子视图的引用计数会自动的增加1,当父视图被release的时候,该父视图上面的所有子视图的引用计数都会被release一次,使子视图的引用计数减1,这样一增一减保持了retain/release的平衡。

这段话很重要,请仔细理解:关于上面的UIImageView对象imageView的引用计数变化和生命周期是这样的,当使用alloc/init创建imageView对象的时候,它的引用计数retainCount=1;将它将入到父视图的时候,引用计数增加1,retainCount=2;向其发送release消息时候,引用计数减少1,retainCount=1;最后,在ViewController的dealloc方法中,self.view被释放,self.view就是imageView的父视图,这时候self.view上面的所有子视图都会收到release消息,当imageView收到release消息的时候,引用计数减少1, retainCount=0。这时候imageView对象内存被安全释放,该对象的生命周期到此结束。

好了,上面的情况讲述的就是在一对括号内{}创建局部变量时候遵循的手动管理内存的规则,那就是你通过alloc/init创建了对象,那你就需要在使用结束以后向其发送release消息


第二种情况,成员变量的内存管理

什么是成员变量,以一个ViewController1举例说明,

@interface ViewController1:NSObject

{

    //括号内的变量就是成员变量

    Person *_person;

    NSString *_name;

    NSString *_schoolName;

}

@end

上面括号内就定义了三个成员变量,分别是_person、_name、_schoolName,成员变量最好以'_'下划线开头,接着我们在viewDidLoad中对三个对象分别初始化,成员如下所示,

- (void)viewDidLoad

{

    [super viewDidLoad];

    //alloc/init创建对象

    _person = [[Person alloc] init];

    _schoolName = [[NSString alloc] initWithFormat:@"yyy"];

    //类方法创建对象

    _name = [NSString stringWithFormat:@"xxx"];    

}

上面用两种不同的方法创建了对象,一是使用alloc/init创建,二是使用类方法创建,因为成员变量我们需要在整个ViewController作用域都需要使用到,所以我们不能立即向其发送release消息,我们应该在dealloc时候向其发送release消息,如下代码,

- (void)dealloc

{

    [_person release];

    [_schoolName release];

    [super dealloc];

}

我们在dealloc中向两个通过alloc/init方式创建的对象_person和_schoolName发送release消息,为什么不向_name发送release消息呢,这是因为_name是通过NSString的类方法stringWithFormat:创建的,而类方法返回的对象已经在方法内部加入了自动释放池,它的引用计数和生命周期已经交给了自动释放池来管理,不需要向其发送release消息了。

上面描述了“成员变量”内存管理的规则,如果是通过alloc/init创建的对象,那么需要在dealloc时候向其发送release消息;如果不是通过alloc/init方法创建的对象,例如通过类方法创建的,那么不需要在dealloc时候向其发送release消息,比如_name = [NSString stringWithFormat:@"xxx"];或者数组_petArray = [NSArray arrayWithObject:@"dog",@"cat",@"duck",nil];。


第三种,属性@property对象的内存管理

上面说的两种对象都是ViewController类内部管理的对象,这些对象不会与其他类有交互,所以说内存管理的规则相对简单。如果类中的变量需要与其他类进行交互,这时候就需要用到@property属性来定义变量。我们一般这样编写@property的代码,

@property (nonatomic, assign) TestObject *testObject;//默认情况是assign

//或者

@property (nonatomic, retain) TestObject *testObject;

//又或者

@property (nonatomic, copy) TestObject *testObject;

我们知道,当我们定义属性的时候,Xcode会根据修饰关键字(例如retain、assign、copy)自动生成getter/setter,不同修饰符的getter方法没什么差别,主要就是setter有很大的不同,那么我们来不同修饰符时候setter方法不同的地方,

//assign修饰符

- (void)setTestObject:(id)newValue

{

    testObject = newValue;

}

assign修饰符,相当于指针赋值。对象的引用计数不发生改变。注意源对象不用了,一定要把这个对象设置为nil

//retain修饰符

- (void)setTestObject:(id)newValue

{

    if(testObject != newValue){

        [testObject release];

        [newValue retain];

        testObject = newValue;

    }

}

retain修饰符的setter方法表示,先向原来的值发送release,然后向新的值newValue发送retain消息,使其引用计数增加1,然后新的值newValue赋值给原来的值。

//copy修饰属性

- (void)setTestObject:(id)newValue

{

    if(testObject != newValue){

        [testObject release];

        testObject = [newValue copy];

    }

}

copy修饰符的setter方法就是,先向原来的值发送release,然后复制一份新的值赋值给testObject,它的引用计数为1。

下面主要说一说retain修饰符修饰的属性,在使用的时候需要注意的地方,我们知道当我们使用self.testObject的时候有两种情况,一种是左值例如self.testObject = [[TestObject alloc] init];,另一种是又值例如TestObject *tempTestObject = self.testObject。

当我们使用self.testObject作为右值的时候,相当于调用了getter方法,这没有什么需要注意的地方;当使用self.testObject作为左值的时候,就有一些注意的地方了,例如下面的语句,

self.testObject = [[TestObject alloc] init];//这是错误的,因为alloc/init创建的对象,在setter内部又被retain了一次,导致引用计数retainCount=2,造成内存泄露

//修改代码如下

TestObject *tempTestObject = [[TestObject alloc] init];

self.testObject = tempTestObject;

[tempTestObject release];

这样就保证了self.testObject引用计数为1,在整个类的作用域都可以放心安全使用,然后在dealloc的时候向该对象再发送release对象,释放其内存空间,

- (void)dealloc

{

    [self.testObject release];

    [super dealloc];

}

还有一种情况,self.testObject是通过TestObject的类方法初始化的,这时候就可以直接初始化,如下代码,

self.testObject = [TestObject testObjectWithClassMehtod:xxx];//这样写没有问题

这样创建的对象是被类方法放入自动释放池来管理其引用计数和生命周期,而不用担心内存泄露的问题。虽然这时候内存不需要我们来手动释放了,但是为了编码规范,还是在dealloc时候向其发送release消息,

- (void)dealloc

{

    [self.testObject release];

    [super dealloc];

}


下面的内容可能比较多,但是我是详细的列出了每一个细节和不同,读者也可以按照这种方式去总结,应该会有帮助,我自己就是这样总结,感觉对于内存管理的了解越来越明确了。

通过实际的例子来说明问题,下面我来描述一个场景:点击ViewController1导航栏上面的rightBarButtonItem,push到ViewController2页面,ViewContorller2需要用到Student类,那么我们在ViewController2内部定义Student时候就有很多细节需要注意了,我会分情况讨论,

(1)Student作为ViewController2的成员变量

前面我说过,为了让自己和别人一眼就看出这是一个成员变量,最好在成员变量的前面加上'_'下划线,所以这时候我们这样在ViewController2中使用Student作为成员变量,

ViewController2.h file

@interface ViewController2:UIViewController

{

    Student *_student;

}

@end

ViewController2.m file

@implementation ViewController2

- (void)dealloc

{

    [_student release];

    [super dealloc];

}

- (void)viewDidLoad

{

    [super viewDidLoad];

    _student = [[Student alloc] init];//引用计数为1,即retainCount=1

}

@end

(2)不定义Student成员变量_student,定义Student属性,不写@synthesie代码

ViewController2.h file

@interface ViewController:UIViewController

{

    //这里不定义成员变量_student

}

@property (nonatomic, retain) Student *student;

@end

ViewController2.m file

@implementation ViewController2

//没有写@synthesize

- (void)viewDidLoad

{

    [super viewDidLoad];   

    //第一种实例化方法

    Student *tempStudent = [[Student alloc] init];//tempStudent引用计数为1

    self.student = tempStudent;//tempStudent引用计数为2,self.student引用计数为2

    [tempStudent release];//tempStudent引用计数为1,self.student引用计数为1

    //这时候定义的self.student引用计数为1,可以在ViewController2整个作用域范围内使用,要在dealloc中向其发送release消息


    //第二种实例化方法

    _student = [[Student alloc] init];

    //需要解释一下这个代码,我们这时候没有定义成员变量,为什么还可以使用_student呢,我也不明白为什么,应该是属性property自动帮助我们生成了对应的成员变量,我之前还有所疑惑,后来查看了(注释1)_student和self.student的内存地址,发现两者的内存地址相同。所以,我们定义了属性@property (nonatomic, retain) Student *student;,就可以直接使用_student成员变量。

    //那么接着解释一下此时的内存吧,这时候_student的引用计数为1,self.student引用计数为1

}

- (void)dealloc

{

    //dealloc方法怎么写?

    //如果你是使用第一种方式实例化self.student,为了对应,那么就用这种方式释放对象

    [self.student release];

    //如果你使用第二种方式实例化,那么就用下面的方式发送release消息

    [_student release];

    //需要说明的是,其实上面两种方式,释放对象都是可以的,这里只是为了与实例化方法对应。

    [super dealloc];

}

@end

(3)不定义Student成员变量_student,定义Student属性,写@synthesize sutdent;,而不写@synthesize sutdent = _student;

ViewController2.h file

@interface ViewController:UIViewController

{

    //不定义成员变量_student

}

@property (nonatomic, retain) Student *student;

@end

ViewController2.m file

@implementation ViewController2

//注意这里不是@synthesize student = _student;这是下面会说到的

@synthesize student;

- (void)viewDidLoad

{

    [super viewDidLoad];

    //这时候有两种实例化方法

    //第一种实例化方法

    Student *tempStudent = [[Student allloc] init];//引用计数retainCount=1

    self.student = tempStudent;//引用计数self.student's retainCount=2,tempStudent's retainCount=2

    [tempStudent release];//引用计数tempStudent's retainCount=1,self.student's retainCount=1

    //第二种实例化方法

    student = [[Student alloc] init];

}

@end

- (void)dealloc

{

    //释放对象,对应第一种实例化方法

    [self.student release];

    //释放对象,对应第二种实例化方法

    [super dealloc];

    //当然,这两种释放对象的方式其实都可以,只是为了对应实例化方法

}

需要特别注意的是,我们写了@synthesize student;而不是@synthesize student = _student;这时候,就不可以在ViewController2作用域范围内使用_student这个成员变量了。

(4)不定义Student成员变量,定义Student属性,写@synthesize student = _student,而不是@synthesize sutdent;

ViewController2.h file

@interface ViewController2:UIViewControler

{

    //不定义_student成员变量

}

@property (nonatomic, retain) Student *student;

@end

ViewController2.m file

@implementation ViewContorller2

//这里不是@synthesize sutdent;

@synthesze student = _student;

- (void)viewDidLoad

{

    [super viewDidLoad];

    //第一种实例化方法,不在解释引用计数

    Student *tempStudent = [[Student alloc] alloc];

    self.student = tempStudent;

    [tempStudent release];

    //第二种实例化方法

    _student = [[Student alloc] release];

}

- (void)dealloc

{

    //释放对象,对应第一种实例化方法

    [self.student release];

    //释放对象,对应第二种实例化方法

    [_student release];

    [super dealloc];

}

@end

(5)定义成员变量_student,定义student属性,不写@synthesize

ViewController2.h

@interface ViewController2:UIViewContorller

{

    Student *_student;

}

@property (nonatomic, retain) Student *student

@end

ViewContorller2.m

@implementation ViewContorller2

//不写@synthesize

- (void)viewDidLoad

{

    [super viewDidLoad];

    //第一种实例化方法

    Student *tempStudent = [[Student alloc] init];

    self.student = tempStudent;

    [tempStudent release];

    //第二种实例化方法

    _student = [[Student alloc] init];

}

- (void)deallc

{

    //释放对象,对应第一种实例化方法

    [self.student release];

    //释放对象,对应第二种实例化方法

    [_student release];

    [super dealloc];

}

@end

(6)定义Student成员属性_student,定义Student属性,写@synthesize student;,而不写@syntheise student = _student;

ViewController.h

@interface ViewContorller:UIViewContorller

{

    Student *_sutdent;

}

@property (nonatomic, retain) Student *student

@end

ViewController2.m file

@implementation ViewController2

//这里不是@synthesize student = _student;

@synthesieze student;

- (void)viewDidLoad

{

    [super viewDidLoad];

    //第一种实例化方法

    Student *tempStudent = [[Student alloc] init];

    self.student = tempStudent;

    [tempStudent release];

    //第二种实例化方法

    student = [[Student alloc] init];

}

- (void)dealloc

{

    //释放对象,对应第一种实例化方法

    [self.student release];

    //释放对象,对应第二种实例化方法

    [student release];

    [super dealloc];

}

@end

(7)定义Student成员属性_student,定义Student属性,写@synthesize student = _student;,而不写@syntheise student;

ViewController.h

@interface ViewController2:UIViewContorller

{

    Student *_student;

}

@property (nonatomic, retain) Student *student;

@end

ViewController2.m file

@implementation ViewController2

@synthesize student = _student;

- (void)viewDidLoad

{

    [super viewDidLoad];

    //第一种实例化方法

    Student *tempStudent = [[Student alloc] init];

    self.student = tempStudent;

    [tempStudent release];

    //第二种实例化方法

    _student = [[Student alloc] init];

}

- (void)dealloc

{

    //释放对象,对应第一种实例化方法

    [self.student release];

    //释放对象,对应第二种实例化方法

    [_student release];

    [super dealloc];

}

@end

上面我自己罗列了我所知道的定义成员变量和属性时候用到的所有方式,如果有遗漏还请读者指出,这里面的代码我都验证了有效性,可能在写博客的时候会有一些书写方面的疏漏,有错误也请读者指正。

总结:

如果定义了属性student,则自动生成_student成员变量;如果写了@synthesize student,此时只能在ViewController2作用域范围内使用student和self.student,而不能使用_student;如果写了@synthesize student = _student,则在ViewController2作用域范围内只能使用_student和self.studetn,而不能使用student,也就是说_student和student不能共存,只能使用其一。

注释1:怎样查看一个对象的内存呢?在使用该对象的地方打一个断点,当运行到断点时候,在控制台使用po命令工具,例如我想知道self.student内存地址,那么我可以在断点时候在控制台中这样做,po self.student,如下图,


标签: ios 内存管理
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 183
博文 53
码字总数 49671
×
召唤攻城狮
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: