iOS --关于循环引用

iOS之循环引用问题

  • 循环引用的产生
  • 内存分布区域
  • 内存管理机制
  • 循环引用常见问题

循环引用的产生

内存中和变量有关的分区:堆、栈、静态区。其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生,也和其息息相关,即循环引用会导致堆里的内存无法正常回收,从而导致内存泄漏。

内存分布区域

这里写图片描述

 

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。分配内存的时候,栈区的地址是 从上往下分配(越分配地址越小 i 地址比j大) 
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。堆区的地址是 由下往上的 
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 
4、文字常量区— 常量字符串就是放在这里的。 程序结束后由系统释放 
5、程序代码区— 存放函数体的二进制代码。

内存管理模型

在iOS中有2套内存管理机制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源于iOS 4.3,在那之前,苹果开发者只能手动使用retain和release来进行内存管理。这样做的问题很明显,如果你在开发过程中,一个不小心没有retain和release成对出现,那么很容易使得内存没有释放,最后导致程序内存不足而导致闪退。

MRC 
在你创建一个对象的时候,他会需要进行retain,然后在你不在持有他的时候进行release,所以每个对象都有一个retain count来进行计数。 
内存管理规则如下: 
1> 当调用alloc、new、copy(mutableCopy)方法产生一个新对象的时候,就必须在最后调用一次release或者autorelease 
2> 当调用retain方法让对象的计数器+1,就必须在最后调用一次release或者autorelease

2.集合的内存管理细节 
1> 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1 
2> 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1 
3> 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1

3.普遍规律 
1> 如果方法名是add\insert开头,那么被添加的对象,计数器会+1 
2> 如果方法名是remove\delete开头,那么被移除的对象,计数器-1

ARC 
iOS 5.0之后引用自动管理机制——自动引用计数(ARC),管理机制与手动机制一样,只是不再需要调用retain、release、autorelease;它编译时的特性,当你使用ARC时,在适当位置插入release和autorelease;它引用strong和weak关键字,strong修饰的指针变量指向对象时,当指针指向新值或者指针不复存在,相关联的对象就会自动释放,而weak修饰的指针变量指向对象,当对象的拥有者指向新值或者不存在时weak修饰的指针会自动置为nil。

循环引用常见问题

类相互引用
- (void)viewDidLoad {
    [super viewDidLoad];
    ClassA *a = [[ClassA alloc] init];
    ClassB *b = [[ClassB alloc] init];
    a.classB = b;
    b.classA = a;
}
两个类 A 和 B A强引用B B强引用A 所以下面的代码 dealloc 不会走 发生循环引用 
解决方法 让其中任意一个类 变成弱引用即可 即 @property (nonatomic, weak) ClassA *classA; 
运行结果如下: 
这里写图片描述

2.Block问题

 - (void)blockFunc {
    self.block = ^{
        self.str = @"hello";//发生循环引用
        NSLog(@"%@", self.str);
    };
    self.block();
}

可以发现发生循环引用 解决办法为 用weak 进行弱引用

- (void)blockFunc {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        weakSelf.str = @"hello";
        NSLog(@"%@", weakSelf.str);
    };
    self.block();
}

注:此处为自己的原创:

==========

/** 

第二种方式  此种方法需要确保再self释放之后不会再有调用当前类的其他方法。

**/

        self.nameStr = @"sssss";

        __block ViewController2 *bSelf = self;

        self.pushBlock = ^{

           NSLog(@"name的值是:%@",bSelf.nameStr);

            bSelf = nil;

        };

        self.pushBlock();

/**

  第三种方式 

将当前对象作为block的入参传入到Block内部去

**/

self.nameStr = @"ssss"

    self.pushBlock = ^(ViewController2 *obj) {

        NSLog(@"name的值是:%@",obj.nameStr);

    };

    self.pushBlock(self);

=========

原创结束

当在block中添加延迟函数时 下面函数就会出现问题

- (void)blockFunc {
    self.str = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf.str);
        });
    };
    self.block();
}

1 打印null 5秒之内VC返回 此时VC 页面消失 会走dealloc函数 故里面的weakSelf会为空 
2 打印hello 5秒之后VC返回 延迟函数已执行完 打印完整的hello VC 消失 走dealloc 
解决办法为 用strong 进行 强引用

- (void)blockFunc {
    self.str = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf.str);
        });
    };
    self.block();
}

 

首先 weakSelf 就是弱引用 防止里面循环引用 其次 内部的strongSelf是个 局部变量 仅存在于栈中 当strongSelf.str 执行完 strongSelf就会回收 不会造成循环引用 strongSelf 会使vc引用计数器加1 当其返回时 并不会立刻走dealloc函数 而是等strongSelf.str它执行完 引用计数为0 才会走dealloc 函数 所以不论什么时候返回 strongSelf.str都是有值的 这就是为什么__weak 和 __strong 成对出现的原因
 

3.NSTimer

 - (void)viewDidLoad {
    [super viewDidLoad];
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    // 等同于 下面 RunLoop -> timer -> self
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

- (void)timerClicked {
    NSLog(@"timerClicked ----");

这段代码发生内存泄露 页面消失 timerClicked函数一直执行 
解决办法为 didMoveToParentViewController函数 将timer 置为空 或在合适位置将其timer取消

- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (nil == parent) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章