ios内存管理

本文详细介绍了autorelease的工作机制,包括RunLoop中的释放时机以及自动释放池的数据结构。同时,文章探讨了copy的目的和不同类型的拷贝(深拷贝、浅拷贝),特别是针对NSString和NSMutableString的拷贝行为。此外,还提到了引用计数的存储方式和weak引用的底层实现,包括dealloc过程中的弱引用处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、内存管理-----autorelease原理

1.autorelease在什么时候会被释放(autorelease对象在什么时机会调用release)

1)如果直接被@autoreleasepool包住,release的时机就是大括号结束的时候,也就是调用pop方法的时候,

2)在项目里面,ios在主线程的RunLoop里面注册了两个Observer,第一个Observer监听了KCFRunLoopEntry事件,会调用objc_autoreleasePoolPush(),第二个Observer监听了KCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(),监听了KCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

​​​​​​​​​​​​​//这个Person什么时候调用release,是有RunLoop来控制的

    //可能是在所属的某次RunLoop循环中,RunLoop休眠前调用了release

//        MJPerson *person = [[[MJPerson alloc]init] autorelease];

第一次创建是在启动RunLoop的时候,最后一次销毁是在RunLoop退出的时候,其他时候的创建和销毁:当RunLoop即将休眠时销毁之前的释放池,重新创建一个新的

1)autorelease是用来使对象的引用计数减1的,也可以使用release来减少他的引用计数,但是这样的话所有的代码都需要写到release之前

            MJPerson *person = [[[MJPerson alloc]init] autorelease];
            person.age = 10;
            NSLog(@"age的值是:%d",person.age);

MJPerson *person1 = [[MJPerson alloc]init];
        [person1 release];

 使用release的话,person1的所有代码都需要写到release的前面,但是使用autorelease的话,后面仍然可以继续调用,直到autoreleasepool}结束

2)autorelease的本质如下

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}


struct __AtAutoreleasePool {
    //构造函数,在生成结构体变量的时候调用
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    //析构函数,在结构体销毁的时候调用
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

@autoreleasepool的本质就是一个结构体__AtAutoreleasePool

每次我们使用一次@autoreleasepool,就相当于调用一个这个结构体的构造函数和析构函数

     @autoreleasepool {
            MJPerson *person = [[[MJPerson alloc]init] autorelease];
      }

他的本质就等价于

 {
 objc_autoreleasePoolPush()
 MJPerson *person1 = [[[MJPerson alloc]init] autorelease] ;
 objc_autoreleasePoolPop(atautoreleasepoolobj)
 }

3)自动释放池的主要的底层数据结构是:__AtAutoreleasePool,AutoreleasePoolPage

4)AutoreleasePoolPage的结构

a.每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址

b.所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起

 child指针会存储下一个AutoreleasePoolPage的地址,parent指针会存储上一个AutoreleasePoolPage的地址,最后一个AutoreleasePoolPage的child指针值为nil,第一个parent的指针值为nil

5)objc_autoreleasePoolPush()会把调用了autorelease方法的对象的内存地址会存放到AutoreleasePoolPage的这个内存里面去

调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址,然后调用autorelease方法,把对象的内存地址存放到AutoreleasePoolPage的这个内存里面,然后调用pop方法,pop方法会一直调用release方法,直到释放遇到到POOL_BOUNDARY这个地址的时候,就release结束

6)next指针会指向下一个能存放autorelease对象地址的一个位置

7)下面的代码的底层逻辑

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        r1 = push()
        MJPerson *person1 = [[[MJPerson alloc]init] autorelease];
        MJPerson *person2 = [[[MJPerson alloc]init]autorelease];
        @autoreleasepool {
            //r2 = push();
            MJPerson *person3 = [[[MJPerson alloc]init] autorelease];
            MJPerson *person4 = [[[MJPerson alloc]init]autorelease];
            
            @autoreleasepool {
                //r3 = push();
                MJPerson *person5 = [[[MJPerson alloc]init] autorelease];
                MJPerson *person6 = [[[MJPerson alloc]init]autorelease];
                //pop(r3);
            }
         //pop(r2)
        }
        
        //pop(r1)
}

 8)可以通过以下私有函数来查看自动释放池的情况

extern void _objc_autoreleasePoolPrint(void)

hot修饰的page表示是当前页

#import <Foundation/Foundation.h>
#import "MJPerson.h"

extern void _objc_autoreleasePoolPrint(void);

在C语言中系统的私有函数或者是自定义的私有函数没有进行暴露的话,可以在其他类中使用extern修饰词来修饰这个函数,就可以在当前函数进行调用

9)如果在viewDidLoad里面的对象调用了autorelease,那这个对象释放是在viewDidLoad加载完毕和viewWillAppear执行完毕才会释放

2.方法里面有局部对象,出了方法会被立即释放掉吗?

如果这个局部对象他最终是通过autorelease的形式来去释放的话,意味着不会马上就释放,而是等他所处的那一次runLoop休眠前进行release,如果ARC生成release方法的话,会立即释放。

二、内存管理----- copy

1.copy的目的

拷贝的目的:产生一个副本对象,跟源对象互不影响,修改了源对象,不会影响副本对象,修改了副本对象,不会影响源对象

2.本身含有copy的类

NSString、NSArray、NSDictionary

    NSString *str ;
    NSString *str1 = [str copy];

直接可以调用copy来拷贝,copy是产生一个副本,如果对str1进行修改不会修改到str的内容,对str进行修改,不会修改到str1的内容

ios提供了两个拷贝方法,一个是copy不可变拷贝,产生不可变副本,一个是mutableCopy,可变拷贝,产生可变副本

 NSString *str1  = [NSString stringWithFormat:@"test"];
 NSString *str2 = [str1 copy];
 NSString *str3 = [str1 mutableCopy];
 NSLog(@"%@",str1);
 NSLog(@"%@",str2);
 NSLog(@"%@",str3);

copy返回的是NSString,mutableCopy返回的是   NSMutableString 

无论是copy还是mutableCopy都是产生副本,对原字符串修改不会改变掉他复制完以后的值,对复制完以后的值进行修改,也不会改变掉他原来的值。copy始终复制出来的是不可变副本,mutableCopy始终复制出来的是可变副本,无论是对NSString类型的进行拷贝,还是对NSMutableString类型的进行拷贝。如果使用mutableCopy复制完以后,用NSString来接收的话接收到的就是不可变的字符串,得用NSMutableCopy来接收。appendString是可变字符串的方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSString *str1  = [NSString stringWithFormat:@"test"];
        NSString *str2 = [str1 copy];
        NSMutableString *str3 = [str1 mutableCopy];
        NSLog(@"str1的值是:%@",str1);
        NSLog(@"str2的值是:%@",str2);
        NSLog(@"str3的值是:%@",str3);
        [str3 appendString:@"123"];
        NSLog(@"str3的值是:%@",str3);
        NSLog(@"str1的值是:%@",str1);
        MJPerson *person;
        MJPerson *person1 = [person copy];
        NSMutableString *str4 = [NSMutableString stringWithFormat:@"test"];
        NSString *str5 = [str4 copy];
        NSMutableString *str6 = [str4 mutableCopy];
        NSLog(@"str4的值是:%@",str4);
        NSLog(@"str5的值是:%@",str5);
        NSLog(@"str6的值是:%@",str6);
        [str4 appendString:@"456"];
        [str5 stringByAppendingString:@"123"];
        [str6 appendString:@"123"];
        NSLog(@"str4的值是:%@",str4);
        NSLog(@"str5的值是:%@",str5);
        NSLog(@"str6的值是:%@",str6);
    }
    return 0;
}

 3.当调用alloc、new、copy、mutableCopy返回了一个对象,在不需要这个对象的时候,要调用release或者autorelease来释放他

NSString如果使用stringWithFormat初始化的话内部是调用了一次autorelease的,不需要在使用autorelease来进行释放,如果使用initWithFormat初始化的话就需要调用autorelease或者是release来释放

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //如果使用stringWithFormat调用的话内部是调用了一次autorelease的
        NSString *str1 = [NSString stringWithFormat:@"test"];
        NSString *str2 = [str1 copy];
        NSMutableString *str3 = [str1 mutableCopy];
        NSLog(@"%p ,%p ,%p",str1,str2,str3);
        NSLog(@"%@, %@, %@",str1,str2,str3);
        [str2 release];
        [str3 release];
        //如果使用initWithFormat的话得需要调用一次autorelease
//        NSString *str1 = [[[NSString alloc]initWithFormat:@"test"] autorelease];
    }
    return 0;
}

可以看出copy出来的和原对象是一个地址,指向的是同一个对象,原因就是copy出来的是不可变的副本,NSString本身的内容也是不可变的,所以为了避免占用更多的内存,使用一个就可以,mutableCopy出来的是一个新的地址对象。

但是如果是对NSMutableString的对象进行拷贝的话,就是一个新的内容,因为原字符串是会变的

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //如果使用stringWithFormat调用的话内部是调用了一次autorelease的
        NSString *str1 = [NSString stringWithFormat:@"test"];
        NSString *str2 = [str1 copy];
        NSMutableString *str3 = [str1 mutableCopy];
        NSLog(@"%p ,%p ,%p",str1,str2,str3);
        NSLog(@"%@, %@, %@",str1,str2,str3);
        [str2 release];
        [str3 release];
        NSMutableString *str4 = [NSMutableString stringWithFormat:@"test"];
        NSString *str5 = [str4 copy];
        NSLog(@"str4的地址%p ,str5的地址%p",str4,str5);
        //如果使用initWithFormat的话得需要调用一次autorelease
//        NSString *str1 = [[[NSString alloc]initWithFormat:@"test"] autorelease];
    }
    return 0;
}

4.深拷贝和浅拷贝 

深拷贝:内容拷贝,产生新的对象

浅拷贝:指针拷贝,没有产生新的对象

对NSMutableString对象进行拷贝,无论是使用copy还是mutableCopy都是深拷贝,对NSString对象进行拷贝,使用copy出来的是浅拷贝,使用mutableCopy出来的是深拷贝

5.对NSString对象进行拷贝,引用计数的计算

如果对NSString使用copy,相当于使用了一次retain,拷贝出来的是同一个对象,所以str1的引用计数为2 ,str2的引用计数为2,无论是对str1进行计数减1,还是对str2进行计数减1,都是对同一个对象进行计数减1 。

6.总结


​​​​​​​

7.copy作为属性修饰符

1)使用copy修饰NSArray

@property (copy, nonatomic) NSArray *data;

- (void)setData:(NSArray *)data {
    if(_data !=data) {
        [_data release];
        _data = [data copy];
    }
}

属性修饰符的不同是底层set方法的不同,assign方法就是直接赋值,retain操作就是把以前的值release掉,新传进来的值进行一次retain操作,copy作为属性修饰符的底层实现是如果传进来的值和原来的值不相等,就把原来的值release掉,然后对新传进来的值进行一次copy操作

2)使用copy修饰NSMutableArray

@property (copy, nonatomic) NSMutableArray *mutableArray;
- (void)setMutableArray:(NSMutableArray *)mutableArray {
    if(_mutableArray != mutableArray) {
        [_mutableArray release];
        _mutableArray = [mutableArray copy];
    }
}
 person.mutableArray = [NSMutableArray array];
 [person.mutableArray addObject:@"rose"];

如果NSMutableArray使用copy来进行修饰,底层是对传进来的新值进行了copy操作,最后变成了不可变的数组,所以不能使用可变数组的方法

 8.给自己的类使用copy修饰符

1)让自己的类遵守<NSCopying>协议

2)实现- (id)copyWithZone:(struct _NSZone *)zone{}方法

类方法中是不能使用self.的

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        MJPerson *person1 = [[MJPerson alloc]init];
        person1.age = 20;
        person1.weight = 50;
        MJPerson *person2 = [person1 copy];
        person2.age = 30;
        NSLog(@"%@",person1);
        NSLog(@"%@",person2);
    }
    return 0;
}

MJPeron.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MJPerson : NSObject<NSCopying>

@property (nonatomic, assign) int age;

@property (nonatomic, assign) int weight;

@end

MJPerson.m文件

- (id)copyWithZone:(struct _NSZone *)zone {
    MJPerson *person = [[MJPerson allocWithZone:zone]init];
    person.age = self.age;
    person.weight = self.weight;
    return person;
}

- (NSString *)description {
    
    return [NSString stringWithFormat:@"age = %d , weight = %d",self.age,self.weight];
}

copyWithZone方法中创建一个新的对象,让本身的属性值赋值给新的对象的属性,将对象进行返回,此时copy完之后的这个对象和copyWithZone这个是同一个对象,因为他是将对象进行了赋值,但是赋值前的对象和赋值以后的对象不是一个对象,属性可以进行部分属性值的修改。

​​​​​​​

三、内存管理----引用计数的存储

1.extra_rc:里面存储的值是引用计数器减1

2.has_sidetable_rc:引用计数器是否过大无法存储在isa指针中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

3.在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable中

struct SideTable {
  spinlock_t stock;
  RefcountMap regents;
  weak_table_t weak_table;
}

regents是一个存放着对象引用计数的散列表

objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    //优化过的isa
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            //引用计数不是存储在isa中,而是存储在SideTable中
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

四、内存管理------weak的底层原理实现

1.Dealloc的实现机制

1)在ARC的情况下不需要调用父类的dealloc方法,当对象销毁的时候就会自动调用dealloc方法

 NSLog(@"111");
 {
  MJPerson *person = [[MJPerson alloc]init];

 }
 NSLog(@"222");

person出了{}这个作用域就销毁了,所以会调用他的dealloc方法

2)当调用dealloc的时候首先会调用_objc_rootDealloc方法

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

3)接下来会调用rootDealloc()

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

 4)此时会根据以下5个标准去判断是否可以被释放

isa.nonpointer 是否是优化过的isa指针

weakly_referenced是否有弱引用指向

has_assoc 是否有关联对象

has_cxx_dtor 是否有析构函数

has_sidetable_rc是否有sidetable另外一个数据结构来存储引用计数

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

5)如果是isTaggerPointer,直接return,如果是优化过的指针并且没有关联对象,没有弱指针引用,没有析构函数,没有sidetable另外一个数据结构来存储引用计数的话就free,否则的话就调用object_dispose()方法,做下一步的处理

6)object_dispose方法,直接调用objc_destructInstance(obj)方法,然后free掉

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

7)objc_destructInstance方法会判断是否有析构函数,如果有C++内容,调用object_cxxDestruct(obj),清除掉成员变量,清除掉C++的内容,然后判断是否有关联对象,如果有关联对象,则销毁掉关联对象的一系列操作,然后调用clearDeallocating(),在clearDeallocating()方法里面将当前对象指向的弱引用指针置为nil

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

8)如果是没有优化过的指针,就执行 sidetable_clearDeallocating(),优化过的指针调用clearDeallocating_slow()

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

9)然后执行weak_clear_no_lock(&table.weak_table, (id)this)在这一个步骤中,会将指向该对象的弱引用指针置为nil,然后table.refcnts.erase(this),从引用计数表中擦除掉该对象的引用计数,dealloc结束

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

​​​​​​​

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

2.weak的底层原理实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值