内存管理原则
- 原则一:
谁创建,谁release
- 原则二:
谁retain,谁release
- 下面通过代码来演示set方法内部的内存管理,本篇文章是在MRC环境进行测试的;
- 定义两个类YYPerson与YYBook,其中YYPerson拥有YYBook的成员变量;
#import
#import "YYBook.h"
@interface YYPerson : NSObject
{
YYBook *_book;
int _age;
}
- (void)setBook:(YYBook *)book;
- (YYBook *)book;
- (void)setAge:(int)age;
- (int)age;
@end
#import "YYPerson.h"
@implementation YYPerson
- (void)setBook:(YYBook *)book{
_book = book;
}
- (YYBook *)book{
return _book;
}
- (void)setAge:(int)age{
_age = age;
}
- (int)age{
return _age;
}
- (void)dealloc{
[super dealloc];
NSLog(@"%s",__func__);
}
@end
#import
@interface YYBook : NSObject
{
int _price;
}
- (void)setPrice:(int)price;
- (int)price;
@end
#import "YYBook.h"
@implementation YYBook
- (void)setPrice:(int)price{
_price = price;
}
- (int)price{
return _price;
}
- (void)dealloc{
[super dealloc];
NSLog(@"%s",__func__);
}
@end
#import
#import "YYPerson.h"
#import "YYBook.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYBook *book1 = [[YYBook alloc]init];
YYPerson *person1 = [[YYPerson alloc]init];
person1.book = book1;
book1.price = 10;
[book1 release];
[person1 release];
}
return 0;
}
- 首先alloc了一个person对象和一个book对象,有alloc则必然有release,即遵循内存管理原则一,否则会造成内存泄漏;
- person1.book = book1这行代码表明person占有book对象,所以book1对象被引用其内存计数器+1,那么set方法内部应该对book进行retain操作,代码修改如下:
- (void)setBook:(YYBook *)book{
_book = [book retain];
}
- 修改之后发现book1对象内存泄漏了,原因是在person的setBook方法中对book1做了retain操作,没有做相应的release操作,违反了内存管理原则二,从而造成内存造成泄漏;需要在person对象释放之前将book1成员释放掉,所以在person的dealloc方法中对book1成员进行release操作,代码修改如下:
- (void)dealloc{
//人在挂掉之前 需要对book进行release操作,否则会造成book的内存泄漏
[_book release];
[super dealloc];
NSLog(@"%s",__func__);
}
- 再创建一本书对象book2,然后person对象的book成员换成book2,代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYBook *book1 = [[YYBook alloc]init];
YYPerson *person1 = [[YYPerson alloc]init];
person1.age = 20;
//person1想占用book1这本书 需要对book1进行retain操作
person1.book = book1;
book1.price = 100;
YYBook *book2 = [[YYBook alloc]init];
book2.price = 200;
//将原来的书换掉,换成了新书book2
person1.book = book2;
[book1 release];
[person1 release];
[book2 release];
}
return 0;
}
- 发现原来的book1存在内存泄漏,问题出在换书的时候,没有对原来的book1进行release操作,新书book2有retain操作,在person的dealloc中对book2有release操作,所以book2没有内存泄漏;
代码块- (void)setBook:(YYBook *)book{
//换书只有当旧书与新书不同时,对旧书release,对新书retain
if (_book != book) {
[_book release];
_book = [book retain];
}
}
- 上面在YYPerson类中每定义一个成员变量,都要去实现其setter/getter方法,如果YYPerson有100个成员变量,那么会写100个setter/getter方法,这样会很麻烦,而且代码没有技术含量,所以引出了属性@property这项技术来帮助我们自动实现setter/getter方法的以及内存管理的相关操作;
- 再新建一个类YYCar,然后在YYPerson类中引入YYCar类型的一个属性car,代码如下:
#import
#import "YYBook.h"
#import "YYCar.h"
@interface YYPerson : NSObject
{
YYBook *_book;
int _age;
}
@property(retain)YYCar *car;
- (void)setBook:(YYBook *)book;
- (YYBook *)book;
- (void)setAge:(int)age;
- (int)age;
@end
- @property(retain)YYCar *car 这行代码帮助我们定义了一个成员变量_car以及成员变量的setter/getter方法,retain关键字做了内存管理方面的操作,即release旧值,retain新值,外界调用代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person1 = [[YYPerson alloc]init];
person1.age = 20;
YYCar *car = [[YYCar alloc]init];
person1.car = car;
[car release];
[person1 release];
}
return 0;
}
- 发现car存在内存泄漏,原因在于@property(retain)YYCar *car中帮助我们生成setter/getter方法,成员变量_car以及retain关键字做了内存管理操作,但是在setter方法中对car进行了retain操作,却没有对应的release操作,所以我们必须在person对象的dealloc中对car进行release操作;
- (void)dealloc{
//人在挂掉之前 需要对book进行release操作,否则会造成book的内存泄漏
[_book release];
[_car release];
[super dealloc];
NSLog(@"%d岁的人 -- %s",_age,__func__);
}
- 可以看到在YYPerson每添加一个属性,都在dealloc方法中对所加属性进行一次release操作,这样也很麻烦,这就引出了ARC技术,帮助我们自动生成release操作的代码;
MRC环境property中的参数
- retain:release旧值,retain新值(适用于OC对象类型)
- assign:直接赋值(默认,适用于非OC对象类型)
- copy:release旧值,copy新值
- readwrite:同时生成setter和getter的声明,实现
- readonly:只会生成getter的声明,实现
- nonatomic:性能高,非线程安全
- atomic:性能低,线程安全
- setter=method:决定了set方法的名称,一定要有个冒号
- getter=method:决定了get方法的名称(一般用于BOOL类型属性)
ARC技术
- 是一种编译器特性;
- 只要没有强指针指向对象,就会释放对象;
- 不允许调用retain, release,retainCount;
- 允许重写dealloc,但是不允许调用[super dealloc];
- @property的参数
- strong:成员变量是强指针,适用于OC对象类型;
- weak:成员变量是弱指针,适用于OC对象类型;
- assign:适用于非OC对象类型。
set方法的源码分析
- 实例对象在设置属性时,
objc4-781
底层源码会调用objc_setProperty
,实现如下:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
- 内部调用
reallySetProperty
,实现如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
//设置isa,因为isa是结构体的首位offset=0
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
//获取当前需要设置值的成员的offset
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);//retain新值
}
//nonatomic非线程安全
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {//atomic线程安全
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);//release旧值
}
- 可以看到设置属性时,
retain新值
,release旧值
- 进入objc_retain,objc_release源码:
__attribute__((aligned(16), flatten, noinline))
id objc_retain(id obj){
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
__attribute__((aligned(16), flatten, noinline))
void objc_release(id obj){
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
- 可以看到objc_retain与objc_release都会对对象进行判断,
isTaggedPointer
,如果对象是TaggedPointer小对象,不会进行内存管理(引用计数)操作,直接返回,如果不是TaggedPointer对象,则进行内存管理操作即执行obj->retain()
和obj->release()
函数;
- 详细的源码分析见iOS内存管理07 -- 源码分析