iOS内存管理02 -- set方法的内存管理

内存管理原则

  • 原则一:谁创建,谁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 -- 源码分析

你可能感兴趣的:(iOS内存管理02 -- set方法的内存管理)