ios内存管理

ios内存管理方案

  ios的内存布局如下图所示。
在这里插入图片描述

  • demo1
int a = 1;
int b;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    static int c = 2;
    static int d;
    int e = 3;
    int f;
    NSString *str = @"abc";
    NSObject *obj = [NSObject new];
    NSNumber *num1 = @(123);
    NSNumber *num2 = @(9456789987654378998);
    NSNumber *num3 = [[NSNumber alloc] initWithLong:123];
    NSString *str2 = @"abiduehwoidjewidjowejdioewjdiewfdjewkdew";
    NSString *str3 = [[NSString alloc] initWithFormat:@"%@", @"abiduehwoidjewidjowejdioewjdiewfdjewkdew"];
    NSString *str4 = [[NSString alloc] initWithFormat:@"%@", @"abc"];
    NSString *str5 = [NSString stringWithFormat:@"%@", @"abiduehwoidjewidjowejdioewjdiewfdjewkdew"];
    NSString *str6 = [NSString stringWithFormat:@"%@", @"abc"];
    NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\n&str=%p\n&obj=%p\n&num1=%p\n&num2=%p\n&num3=%p\n&str2=%p\n&str3=%p\nstr4=%p\nstr5=%p\nstr6=%p\n",
          &a, &b, &c, &d, &e, &f, str, obj, num1, num2, num3, str2, str3, str4, str5, str6);
}
@end

  打印结果如下图。可以看出a、b、c、d、str、str2地址相近,说明都存储在“数据段”区。e、f地址相近,且f地址比e小,说明e、f都在栈区。obj、num2、str3、str5地址相近,说明都在堆里面。num1、num3地址相近,说明都是TaggedPointer。str4、str6地址相近,说明都是TaggedPointer。
在这里插入图片描述

  • isa
      下图是isa类型(即isa_t)的定义,可以看出,isa_t是一个uion类型,大小是64bit。
    在这里插入图片描述
      上图代码等价于下图。
    在这里插入图片描述
  • 引用计数的存储
      在64bit中,引用计数可以直接存储在优化过的isa指针中,如果引用计数太大而导致isa装不下时,就会把引用计数存储在SideTable类中
    在这里插入图片描述

TaggedPointer

  从64位开始,ios引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString这些小对象的存储。
  在没有使用TaggedPointer之前,NSNumber等对象都需要动态分配内存和维护其引用计数(堆的管理),NSNumber指针存储的只是NSNumber对象的地址。
  而在使用TaggedPointer之后,NSNumber指针里面存储的数据变成了:Tag+Data,即把数据直接存储在了指针里面。当对象指针不够存储数据时,才会使用动态分配内存的方式来存储数据。objc_msgSend方法能识别TaggedPointer,比如NSNumber的intValue方法,直接从指针提取数据,从而节省了从堆中获取数据的开销。

demo1:非TaggedPointer类型的NSString(即存储了很长的字符串的NSString)在多线程环境下会导致crash

  代码如下:

@interface ViewController ()
@property(nonatomic, copy)NSString *str;
@end

@implementation ViewController

//setStr方法的本质
//- (void)setStr:(NSString *)str {
//    if (_str != str) {
//        [_str release];
//        [str retain];
//    }
//}

- (void)viewDidLoad {
    [super viewDidLoad];
    //方式1:不会导致crash。因为[NSString stringWithFormat:@"abc"]返回的是TaggedPoint类型的NSString,并且objc_msgSend方法能识别出TaggedPointer类型的变量,该方法对于TaggedPointer类型的变量的处理操作是直接复制,而不会调用对象的release方法和retain方法。
//    for (int i = 0; i < 1000; ++i) {
//        dispatch_async(dispatch_get_global_queue(0, 0), ^{
//            self.str = [NSString stringWithFormat:@"abc"];
//        });
//    }
    
    //方式2:会导致crash
    for (int i = 0; i < 1000; ++i) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.str = [NSString stringWithFormat:@"jfewjiodjeiowjoidjwoijdiejwoidnkjwed"];
        });
    }
}

  运行结果如下:
在这里插入图片描述

demo2:怎么知道NSString、NSNumber等的实例对象是否是TaggedPointer类型呢?

  下图是runtime源码,可以看出ios平台判断一个对象是否是TaggedPointer类型的方式是:判断该对象的地址的最高位是否是1。而mac平台判断一个对象是否是TaggedPointer类型的方式是:判断该对象的地址的最低位是否是1。
在这里插入图片描述

  因此,我们可以通过以下代码来判断一个对象是否是TaggedPointer类型。

//ios平台下的判断是否为TaggedPointer的指针位置
#define _OBJC_TAG_MASK (1UL<<63)
//mac平台下的判断是否为TaggedPointer的指针位置
#define MAC_OBJC_TAG_MASK 1UL

BOOL isTaggedPointer(id pointer) {
    return ((uintptr_t)pointer & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *str1 = [NSString stringWithFormat:@"abc"];
    NSString *str2 = [NSString stringWithFormat:@"jfewjiodjeiowjoidjwoijdiejwoidnkjwed"];
    NSLog(@"\nstr1=%@, str2=%@\n str1=%p, str2=%p\n, str1 taggedPointer=%d, str2 taggedPointer=%d, ", [str1 class], [str2 class], str1, str2, isTaggedPointer(str1), isTaggedPointer(str2));
}

  运行结果如下:
在这里插入图片描述

NONPointer_ISA

  ios中使用的是引用计数来管理oc对象的内存。一个新创建的oc对象的引用计数默认是1,调用retain方法会让oc对象的引用计数+1,而调用release方法则会让oc对象的引用计数-1。当oc对象的引用计数减为0时,oc对象就会被销毁,并且是否其所占用的内存空间。

MRC

demo1: retainCount方法、release方法、autorelease方法
  1. 先把工程的ARC开关关掉,如下图。
    在这里插入图片描述
  2. 代码如下
#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        NSLog(@"count=%zd", p.retainCount);
        NSLog(@"Hello, World!");
    }
    return 0;
}
  1. 打印结果如下,可以看出, Person的dealloc方法即使在程序运行结束时都没有被调用。。
    在这里插入图片描述

  2. 在上面代码基础上,新增release方法的调用,代码合打印结果如下。说明只有调用release方法以便让实例对象p的引用计数变为0之后,该实例对象的dealloc方法才会被调用。

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        NSLog(@"count=%zd", p.retainCount);
        //新增代码-begin
        [p release];
        NSLog(@"after release, count=%zd", p.retainCount);
        //新增代码-end
        NSLog(@"Hello, World!");
    }
    return 0;
}

在这里插入图片描述
5. 我们再来看看autorelease方法的使用,代码和运行结果如下

#import <Foundation/Foundation.h>

@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        NSLog(@"count=%zd", p.retainCount);
        [p autorelease];
        NSLog(@"after autorelease, count=%zd", p.retainCount);
        NSLog(@"11111");
    }
    NSLog(@"22222");
    return 0;
}

在这里插入图片描述

retainCount源码解析

  内部只是调用了rootRetainCount方法。
在这里插入图片描述
  rootRetainCount方法如下图所示,如果是对象是TaggedPointer,直接返回对象地址,否则:如果isa是经过优化的,就返回extra_rc的值加上sideTable中该对象的引用计数值(如果有的话),否则只返回sideTable中该对象的引用计数值。
在这里插入图片描述
  sidetable_getExtraRC_nolock函数的代码如下
在这里插入图片描述
  sidetable_retainCount函数的代码如下图。
在这里插入图片描述

release源码解析

  release内部只是调用了_objc_rootRelease函数。
在这里插入图片描述
  _objc_rootRelease函数调用了对象的rootRelease函数。
在这里插入图片描述
  rootRelease函数先判断对象是否是TaggedPointer类型,是的话就直接返回。否则会调用sidetable_release函数。
在这里插入图片描述
  rootRelease函数内部对引用计数表里面的该对象对应的引用计数器执行-1操作,如果引用计数器为0,就调用该对象的dealloc方法。
在这里插入图片描述

retain源码解析

  retain内部只是调用了rootRetain函数。
在这里插入图片描述
  rootRetain函数对引用计数进行+1。

在这里插入图片描述
sidetable_retain函数对引用计数表里面的该对象对应的引用计数器执行+1操作
在这里插入图片描述

调用alloc、new、copy、mutaleCopy方法所返回的对象,在不需要该对象的时候,都要调用release或者autorelease方法来释放它。
demo2:copy 和mutableCopy方法。这两个方法的目的都是拷贝,即创建对象的副本
  1. 如下图,NSString的copy方法是浅拷贝,即不会创建新的NSString对象(很好理解,既然都是不可变对象,那就不需要创建新的对象了,进而节省内存),所以下图的s1和s2指向同一个对象。NSString的mutableCopy方法是深拷贝,即会创建新的对象。
    在这里插入图片描述
  2. 如下图,NSMutableString的copy方法是深拷贝,即会创建新的NSString对象(很好理解,你不可能为了节省内存而把一个不可变对象变成可变对象吧,这样源对象的语义就已经变了,所以要创建一个新的NSString对象),所以下图的s1和s2指向不同的对象。NSMutableString的mutableCopy方法是深拷贝,即会创建新的对象(也很好理解,拷贝的目的是备份,但你如果不创建新的对象而让两个指针指向同一个对象,其中的一个指针对对象作出的修改都会影响到另一个指针,这样就违背了备份的目的,因而mutableCopy是深拷贝),所以下图的s1和s3指向的是不同的对象。

在这里插入图片描述

demo3:NSString的copy和mutableCopy方法。TaggedPointer类型的对象的retainCount是-1,即使copy后也是-1

代码及其运行结果如下。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *s1 = [[NSString alloc] initWithFormat:@"abc"];
        NSLog(@"\ntaggedPointer的retainCount居然是-1. s1.count=%zd", s1.retainCount);
        NSString *s2 = [s1 copy];
        NSLog(@"s1.count=%zd, s2.count=%zd", s1.retainCount, s2.retainCount);
        NSMutableString *s3 = [s1 mutableCopy];
        NSLog(@"s1.count=%zd, s3.count=%zd", s1.retainCount, s3.retainCount);
        [s1 release];
        NSLog(@"s1.count=%zd, s2.count=%zd, s3.count=%zd", s1.retainCount, s2.retainCount, s3.retainCount);
        [s2 release];
        NSLog(@"s1.count=%zd, s2.count=%zd, s3.count=%zd", s1.retainCount, s2.retainCount, s3.retainCount);
        [s3 release];
        NSLog(@"s1.count=%zd, s2.count=%zd, s3.count=%zd", s1.retainCount, s2.retainCount, s3.retainCount);
        NSLog(@"\n----------------\n");
        NSString *s4 = [[NSString alloc] initWithFormat:@"abcfkewljdlejwjdiewjodjewoi"];
        NSLog(@"s4.count=%zd", s4.retainCount);
        NSString *s5 = [s4 copy];
        NSLog(@"s4.count=%zd, s5.count=%zd", s4.retainCount, s5.retainCount);
        NSMutableString *s6 = [s4 mutableCopy];
        NSLog(@"s4.count=%zd, s6.count=%zd", s4.retainCount, s6.retainCount);
        [s4 release];
        NSLog(@"s4.count=%zd, s5.count=%zd, s6.count=%zd", s4.retainCount, s5.retainCount, s6.retainCount);
        [s5 release];
        NSLog(@"s4.count=%zd, s5.count=%zd, s6.count=%zd", s4.retainCount, s5.retainCount, s6.retainCount);
        [s6 release];
        NSLog(@"s4.count=%zd, s5.count=%zd, s6.count=%zd", s4.retainCount, s5.retainCount, s6.retainCount);
    }
    return 0;
}

在这里插入图片描述

demo4:NSArray和NSMutableArray的copy和mutableCopy方法。NSArray的copy是浅拷贝,即不会创建新的NSArray对象。NSArray的mutableCopy是深拷贝,即会创建新的NSMutableArray对象,但数组里面的对象并不会被重新创建。NSMutableArray的copy和mutableCopy方法都是深拷贝。

代码及其运行结果如下。

@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *arr1 = [[NSArray alloc] initWithObjects:[Person new], [Person new], nil];
        NSLog(@"\narr1.count=%zd", arr1.retainCount);
        NSArray *arr2 = [arr1 copy];
        NSLog(@"arr1.count=%zd, arr2.count=%zd", arr1.retainCount, arr2.retainCount);
        NSMutableArray *arr3 = [arr1 mutableCopy];
        NSLog(@"arr1=%p, arr2=%p, arr3=%p", arr1, arr2, arr3);
        NSLog(@"arr1=%@, arr2=%@, arr3=%@", arr1, arr2, arr3);
        NSLog(@"arr1.count=%zd, arr3.count=%zd", arr1.retainCount, arr3.retainCount);
        [arr1 release];
        NSLog(@"arr1.count=%zd, arr2.count=%zd, arr3.count=%zd", arr1.retainCount, arr2.retainCount, arr3.retainCount);
        [arr2 release];
        NSLog(@"arr1.count=%zd, arr2.count=%zd, arr3.count=%zd", arr1.retainCount, arr2.retainCount, arr3.retainCount);
        [arr3 release];
//        NSLog(@"arr1.count=%zd, arr2.count=%zd, arr3.count=%zd", arr1.retainCount, arr2.retainCount, arr3.retainCount); //该行会导致野指针
        NSLog(@"\n----------------\n");
        NSMutableArray *arr4 = [NSMutableArray arrayWithObjects:[Person new], [Person new], nil];
        NSLog(@"arr4.count=%zd", arr4.retainCount);
        NSArray *arr5 = [arr4 copy];
        NSLog(@"arr4.count=%zd, arr5.count=%zd", arr4.retainCount, arr5.retainCount);
        NSMutableArray *arr6 = [arr4 mutableCopy];
        NSLog(@"arr4=%p, arr5=%p, arr6=%p", arr4, arr5, arr6);
        NSLog(@"arr4=%@, arr5=%@, arr6=%@", arr4, arr5, arr6);
        NSLog(@"arr4.count=%zd, arr6.count=%zd", arr4.retainCount, arr6.retainCount);
        [arr4 release];
        NSLog(@"arr4.count=%zd, arr5.count=%zd, arr6.count=%zd", arr4.retainCount, arr5.retainCount, arr6.retainCount);
        [arr5 release];
//        NSLog(@"arr4.count=%zd, arr5.count=%zd, arr6.count=%zd", arr4.retainCount, arr5.retainCount, arr6.retainCount);//该行会导致野指针
        [arr6 release];
        NSLog(@"arr4.count=%zd, arr5.count=%zd, arr6.count=%zd", arr4.retainCount, arr5.retainCount, arr6.retainCount);
    }
    return 0;
}


在这里插入图片描述

demo5:NSDictionary和NSMutableDictionary的copy和mutableCopy方法。NSDictionary的copy是浅拷贝,即不会创建新的NSDictionary对象。NSDictionary的mutableCopy是深拷贝,即会创建新的NSMutableDictionary对象,但字典里面的对象并不会被重新创建。NSMutableDictionary的copy和mutableCopy方法都是深拷贝。

@interface P1 : NSObject<NSCopying>
@end

@implementation P1

- (id)copyWithZone:(struct _NSZone *)zone {
    return [P1 new];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end

@interface P2 : NSObject<NSCopying>
@end

@implementation P2

- (id)copyWithZone:(struct _NSZone *)zone {
    return [P1 new];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDictionary *dict1 = @{
            [P1 new] : [P2 new],
            [P1 new] : [P2 new]
        };
        NSLog(@"\ndict1.count=%zd", dict1.retainCount);
        NSDictionary *dict2 = [dict1 copy];
        NSLog(@"dict1.count=%zd, arr2.count=%zd", dict1.retainCount, dict2.retainCount);
        NSMutableDictionary *dict3 = [dict1 mutableCopy];
        NSLog(@"dict1=%p, dict2=%p, dict3=%p", dict1, dict2, dict3);
        NSLog(@"dict1=%@, dict2=%@, dict3=%@", dict1, dict2, dict3);
        NSLog(@"dict1.count=%zd, dict3.count=%zd", dict1.retainCount, dict3.retainCount);
        [dict1 release];
        NSLog(@"dict1.count=%zd, dict2.count=%zd, dict3.count=%zd", dict1.retainCount, dict2.retainCount, dict3.retainCount);
        [dict2 release];
        NSLog(@"dict1.count=%zd, dict2.count=%zd, dict3.count=%zd", dict1.retainCount, dict2.retainCount, dict3.retainCount);
        [dict3 release];
        NSLog(@"----------------\n");
        NSMutableDictionary *dict4 = @{
            [P1 new] : [P2 new],
            [P1 new] : [P2 new]
        }.mutableCopy;
        NSLog(@"dict4.count=%zd", dict4.retainCount);
        NSDictionary *dict5 = [dict4 copy];
        NSLog(@"dict4.count=%zd, dict5.count=%zd", dict4.retainCount, dict5.retainCount);
        NSMutableDictionary *dict6 = [dict4 mutableCopy];
        NSLog(@"dict4=%p, dict5=%p, dict6=%p", dict4, dict5, dict6);
        NSLog(@"dict4=%@, dict5=%@, dict6=%@", dict4, dict5, dict6);
        NSLog(@"dict4.count=%zd, dict6.count=%zd", dict4.retainCount, dict6.retainCount);
        [dict4 release];
        NSLog(@"dict4.count=%zd, dict6.count=%zd, dict6.count=%zd", dict4.retainCount, dict5.retainCount, dict6.retainCount);
        [dict5 release];
        [dict6 release];
//        NSLog(@"dict4.count=%zd, dict6.count=%zd, dict6.count=%zd", dict4.retainCount, dict5.retainCount, dict6.retainCount); //这行会导致野指针
    }
    return 0;
}

在这里插入图片描述

demo6: NSString、NSArray、NSDictionary的拷贝总结

在这里插入图片描述

ARC

  ARC利用了LLVM和Runtime机制来帮我们管理内存,即ARC是LLVM编译器和Runtime机制相互作用的结果。其中,利用LLVM编译器自动为我们所写的代码加上retain、release、autoreleases、dealloc方法。利用Runtime机制来帮我们在对象销毁时自动把所有指向该对象的弱引用指针置为nil。

weak指针的实现原理

  1. 先看下面的简单demo。运行结果说明局部变量在离开所属生命周期区域时就会被销毁。
@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1");
    {
        Person *p = [Person new];
        NSLog(@"2");
    }
    NSLog(@"3");
}
@end

在这里插入图片描述
2. 再看下面的demo。当实例对象被销毁时,weakP会自动被置为nil。

@interface Person : NSObject
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak Person *weakP;
    NSLog(@"1");
    {
        Person *p = [Person new];
        weakP = p;
        NSLog(@"2,weakP=%@", weakP);
    }
    NSLog(@"3,weakP=%@", weakP);
}

@end

在这里插入图片描述

  1. 实现原理如下

  当一个对象要释放时,会自动调用dealloc方法,该方法的源码如下,内部只是调用了_objc_rootDealloc函数。
在这里插入图片描述
  _objc_rootDealloc函数也只是调用了对象的rootDealloc方法
在这里插入图片描述
  rootDealloc方法的执行逻辑如下。
在这里插入图片描述
  object_dispose方法的执行逻辑如下。
在这里插入图片描述
  objc_destructInstance函数的执行逻辑如下。
在这里插入图片描述
clearDeallocating函数的调用如下。
在这里插入图片描述
clearDeallocating_slow函数的调用如下。
在这里插入图片描述

weak_clear_no_lock函数的逻辑如下。先调用weak_entry_for_referent函数(该函数内部通过哈希表的开放地址法【不是拉链地址法】找出实例对象对应的entry对象)来获取entry对象,然后调用weak_entry_remove来把该所有指向该实例对象的所有弱引用指针置为nil
在这里插入图片描述
weak_entry_for_referent函数通过哈希表的开放地址法【不是拉链地址法】找出实例对象对应的entry对象来获取entry对象。
在这里插入图片描述

weak_entry_remove函数的处理逻辑如下
在这里插入图片描述

autorelease原理

MRC环境下,才能手动调用retain、release、autorelease方法。所以接下来为了便于理解autorelease方法, 下面的所有demo都是运行在MRC环境下

demo1:如果你不调用release或者autorelease方法,对象就算进程结束了,其dealloc方法都不会被调用

@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
    NSLog(@"%s, %p", __func__, self);
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        NSLog(@"1 p=%p",p);
        @autoreleasepool {
            Person *p2 = [Person new];
            NSLog(@"2 p2=%p",p2);
        }
        NSLog(@"3 p=%p",p);
    }
    NSLog(@"4");
    return 0;
}

在这里插入图片描述

demo2:autorelease用法
@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
    NSLog(@"%s, %p", __func__, self);
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person new] autorelease];
        NSLog(@"1 p=%p",p);
        @autoreleasepool {
            Person *p2 = [[Person new] autorelease];
            NSLog(@"2 p2=%p",p2);
        }
        NSLog(@"3 p=%p",p);
    }
    NSLog(@"4");
    return 0;
}

在这里插入图片描述

demo3:把调用autorelease方法所在的oc文件转成c++
@interface Person : NSObject
@end

@implementation Person
- (void)dealloc
{
    NSLog(@"%s, %p", __func__, self);
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person new] autorelease];
    }
    return 0;
}

  把上面的oc代码所在的文件(假设文件名为main.m)通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转成c++代码文件,该c++文件如下图。可以看出@autoreleasepool{}被转成__AtAutoreleasePool __autoreleasepool;可以看出,__autoreleasepool局部变量在执行完第34263行时会被销毁。
在这里插入图片描述
  __AtAutoreleasePool结构体的定义如下图。当定义上图中的__autoreleasepool局部变量时,下图的构造方法会被调用,当执行完上图的第34263行时,__autoreleasepool结构体变量会被销毁,进而下图的析构函数会被调用。
在这里插入图片描述
  所以@autoreleasepool { Person *p = [[Person new] autorelease]; }等价于atautoreleasepoolobj = objc_autoreleasePoolPush(); Person *p = [[Person new] autorelease]; objc_autoreleasePoolPop(atautoreleasepoolobj);

objc_autoreleasePoolPush函数

  objc_autoreleasePoolPush函数只是调用了AutoreleasePoolPage类的push函数
在这里插入图片描述

  为了便于理解,我们先来看看AutoreleasePoolPage的结构体定义和使用方式,如下图。在这里插入图片描述

  AutoreleasePoolPage::push函数实现如下,该函数会调用autoreleaseFast(POOL_BOUNDARY)以便压入一个名为POOL_BOUNDARY的元素到“栈”里面
在这里插入图片描述
  autoreleaseFast函数实现如下。

static inline id *autoreleaseFast(id obj) {
        AutoreleasePoolPage *page = hotPage();//获取尾结点(由AutoreleasePoolPage组成的双向链表的最后一个节点)
        if (page && !page->full()) {//尾结点的“栈”没有满
            return page->add(obj);
        } else if (page) {//尾结点的“栈”已经满了
            return autoreleaseFullPage(obj, page);//创建一个AutoreleasePoolPage实例并将其添加到双向链表的尾部
        } else {
            return autoreleaseNoPage(obj);//创建一个AutoreleasePoolPage实例并将其作为头结点
        }
    }

	id *add(id obj) {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//压栈
        protect();
        return ret;
    }

    static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

    static __attribute__((noinline)) id *autoreleaseNoPage(id obj) {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }
autorelease方法

  autorelease方法调用rootAutorelease2()以便把当前实例对象压入到“栈”顶
在这里插入图片描述
  rootAutorelease2函数调用了AutoreleasePoolPage::autorelease函数,this指的就是你调用了autorelease方法的那个实例对象。
在这里插入图片描述
  AutoreleasePoolPage::autorelease函数调用了autoreleaseFast函数以便把实例对象压入栈顶。

	static inline id autorelease(id obj) {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }
    static inline id *autoreleaseFast(id obj) {
        AutoreleasePoolPage *page = hotPage();//获取尾结点(由AutoreleasePoolPage组成的双向链表的最后一个节点)
        if (page && !page->full()) {//尾结点的“栈”没有满
            return page->add(obj);
        } else if (page) {//尾结点的“栈”已经满了
            return autoreleaseFullPage(obj, page);//创建一个AutoreleasePoolPage实例并将其添加到双向链表的尾部
        } else {
            return autoreleaseNoPage(obj);
        }
    }
objc_autoreleasePoolPop函数

  objc_autoreleasePoolPop函数只是调用AutoreleasePoolPage::pop函数,传入的参数是前面调用objc_autoreleasePoolPush函数函数时所返回的POOL_BOUNDARY元素。

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage::pop函数如下。

static inline void pop(void *token) {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);//一直弹栈,直到把stop变量(即token)弹出

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
}

void releaseUntil(id *stop) {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {//一直弹栈,直到栈顶元素等于stop
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);//调用实例对象(调用了autorelease方法的对象)的release方法以便释放实例对象
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            assert(page->empty());
        }
#endif
    }

散列表

  如下图所示,每个oc实例对象都对应一个SideTable实例对象,SideTable有3个成员:①自旋锁;②对象对应的引用计数表;③对象的弱引用表。
在这里插入图片描述

内存泄漏常见坑

NSTimer或者CADisplayLink对target是强引用关系

  如下图所示,NSTimer或者CADisplayLink会强引用你传入的target,所以如果你的target又持有对NSTimer或者CADisplayLink的强引用(通常是作为属性)的话,就会导致循环引用,进而导致内存泄漏。
在这里插入图片描述
解决方案有两种:1 使用NSTimer或者CADisplayLink的block回调方法而不是addTarget方法;2 通过一个中间对象(也称代理对象),该中间对象弱引用self。

  • 方案1
      代码如下
@interface ViewController ()
@property(nonatomic, strong)NSTimer *timer;
@end

@implementation ViewController

- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)fun {
    NSLog(@"timer 回调方法");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf fun];
    }];
}
  • 方案2

  解决方案的类图如下:
在这里插入图片描述

  代码如下

//继承NSProxy而不是NSObject的好处是:NSProxy不会有方法查找、动态方法解析这两步的耗时
@interface MyTimerProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end

@interface MyTimerProxy ()
@property(nonatomic, weak)id target;
@end

@implementation MyTimerProxy

+ (instancetype)proxyWithTarget:(id)target {
    MyTimerProxy *proxy = [MyTimerProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

@end

@interface ViewController ()
@property(nonatomic, strong)NSTimer *timer;
@end

@implementation ViewController

- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"%s", __func__);
}

- (void)fun {
    NSLog(@"timer 回调方法");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MyTimerProxy proxyWithTarget:self] selector:@selector(fun) userInfo:nil repeats:YES];
}

NSTimer和CADisplayLink都依赖于RunLoop实现定时功能,所以可能不准时,此时可以使用GCD的timer来让定时功能更加准时,因为GCD的timer不依赖于RunLoop,但如果GCD的timer绑定的是主队列,如果主队列耗时较高的话,GCD的timer也不是准时的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值