美文网首页iOS知识点面试内存管理iOS底层技术
IOS循环引用、内存泄漏、野指针

IOS循环引用、内存泄漏、野指针

作者: wg刚 | 来源:发表于2018-09-19 17:15 被阅读0次

一、循环引用和内存泄漏

1、block循环引用
typedef void(^BlockTest)(void);

@interface WGBlockTestViewController ()

@property (nonatomic, copy) BlockTest blockTest;

@end
- (void)blockTest04{
    self.blockTest = ^{
        [self doSomething];
    };
}
- (void)doSomething{
    WGLog(@"测试循环引用调用方法");
}
-(void)dealloc{
    WGLog(@"没有循环引用");
}
image.png

分析:
因为WGBlockTestViewController强引用blockTest,而在blockTest04方法blockTest里对self强引用,造成两者相互持有。

解决:加上 __weak typeof(self) weakSelf = self;打破这个环

- (void)blockTest05{
    __weak typeof(self) weakSelf = self;
    self.blockTest = ^{
        [weakSelf doSomething];
    };
}

这里再抛出一个问题,看代码:

- (void)blockTest07{
    __weak typeof(self) weakSelf = self;
    self.blockTest = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf doSomething];
        });
    };
}

如果我在5秒内就让WGBlockTestViewController出栈

image.png

这时候 [weakSelf doSomething];就不会执行了。
如果要执行怎么办呢?

解决:
- (void)blockTest07{
    __weak typeof(self) weakSelf = self;
    self.blockTest = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [strongSelf doSomething];
        });
    };
}
image.png
分析:

strongSelf会使WGBlockTestViewController引用计数+1;
即使WGBlockTestViewController被pod,也不会回收(因为strongSelf是持有WGBlockTestViewController的);
等到[strongSelf doSomething]执行完,strongSelf销毁后;WGBlockTestViewController才会销毁。

注意:

这里既有 __weak typeof(self) weakSelf = self;
又有 __strong typeof(weakSelf) strongSelf = weakSelf;
为什么不会循环引用呢?
因为strongSelf是局部变量,在栈中,当block执行完就销毁了,所以不会循环引用

2、NSTimer
#import "FirstViewController.h"
#import "SecondViewController.h"

@interface FirstViewController ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation FirstViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"FirstViewController";
    
    [self nstimer];

}

- (void)nstimer{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
}

- (void)timerMethod{
    NSLog(@"还在执行");
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    SecondViewController *secondC = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:secondC animated:YES];
}

- (void)dealloc{
    NSLog(@"FirstViewController没有内存泄露");
}

@end

执行结果:
如图当FirstViewController执行pod后依然没有被销毁,timer还在执行,出现内存泄漏;

333.gif

分析:
NSTimer是在runloop中的,而NSTimer强引用FirstViewController导致控制器不会被销毁,出现内存泄漏。

解决方案:

方案1:
-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    
    [_timer invalidate];
    _timer = nil;
}

结果:
可以达到销毁的效果,但是存在一个问题:当push一个页面再回来也会导致_timer被销毁了,这时候如果还需要执行_timer怎么办呢?重新创建?

方案2:
-(void)didMoveToParentViewController:(UIViewController *)parent{
    if (nil == parent) {
        [_timer invalidate];
        _timer = nil;
    }
}

当调用willMoveToParentViewController方法或didMoveToParentViewController方法时,要注意他们的参数使用:
当某个子视图控制器将从父视图控制器中删除时,parent参数为nil。
即:[将被删除的子试图控制器 willMoveToParentViewController:nil];
当某个子试图控制器将加入到父视图控制器时,parent参数为父视图控制器。
即:[将被加入的子视图控制器 didMoveToParentViewController:父视图控制器];

方案3:runtime动态添加类和方法
@interface FirstViewController ()

@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) id object;

@end

@implementation FirstViewController

static const void *key = @"key";

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"FirstViewController";
    
    [self nstimer];

}

- (void)nstimer{
    //新建一个类
    _object = [[NSObject alloc] init];
    //给类动态添加方法
    class_addMethod([_object class], @selector(timerMethod), (IMP)objMethod, "v@:");
    //target对象改为_object
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_object selector:@selector(timerMethod) userInfo:nil repeats:YES];
    //关联属性
    objc_setAssociatedObject(_object, key, self, OBJC_ASSOCIATION_ASSIGN);
}

void objMethod(id self, SEL _cmd){
//注意:这里的self不是控制器,是_object,获取到的obj才是控制器
    id obj = objc_getAssociatedObject(self, key);
    [obj timerMethod];
}

- (void)timerMethod{
    NSLog(@"还在执行");
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    SecondViewController *secondC = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:secondC animated:YES];
}

- (void)dealloc{
    [self.timer invalidate];
    _timer = nil;
    NSLog(@"FirstViewController没有内存泄露");
}

@end

结果:达到了要求

方案4:消息转发
.h
#import <Foundation/Foundation.h>

@interface WGProxy : NSProxy

@property (nonatomic, weak) id objct;

@end
.m
#import "WGProxy.h"

@implementation WGProxy

//消息转发机制
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.objct methodSignatureForSelector:sel];
}

//转发给了self.objct
-(void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.objct];
}
@end
#pragma mark -方案4
- (void)nstimer01{
    
    self.proxy = [WGProxy alloc];
    self.proxy.objct = self;
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.proxy selector:@selector(timerMethod) userInfo:nil repeats:YES];
}

结果:和方案3一样达到效果,其实和方案3思路是一样的

3、野指针(EXC_BAD_INSTRUCTION)
#import "SecondViewController.h"

@interface SecondViewController ()

@property (nonatomic, assign) UIView *subView;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"SecondViewController";
    
    [self.view addSubview:self.subView];

}

-(UIView *)subView{
    if (!_subView) {
        _subView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
        _subView.backgroundColor = [UIColor yellowColor];
    }
    return _subView;
}

@end

分析:
WGPerformanceTest[8085:597448] *** -[UIView setBackgroundColor:]: message sent to deallocated instance 0x7f9996c179a0
向一个已经dealloc的对象发送消息
原因是因为属性声明用的assign;

@property (nonatomic, assign) UIView *subView;

PS:这里就涉及到assign和weak的区别:

还是上一份代码,看下subView分别用assign和weak修饰执行的结果

assign:修饰的对象释放后,指针不会自动清空,依然指向销毁的对象,这就造成了野指针

weak:修饰的对象释放后,指针会被自动清空(nil)

相关文章

  • IOS循环引用、内存泄漏、野指针

    一、循环引用和内存泄漏 1、block循环引用 分析:因为WGBlockTestViewController强引用...

  • 链接收藏

    iOS面试题 野指针 使用了释放的内存 内存泄漏 内存没有释放

  • 内存泄漏

    什么是内存泄漏引起内存泄漏的原因野指针,空指针,僵尸对象 1.什么是内存泄漏 内存泄漏(Memory Leak)是...

  • 内存管理解析

    目录 1.内存区域解析2.什么是引用计数(retainCount)3.什么是指针和地址4.内存泄漏、野指针、空指针...

  • 循环引用

    由于iOS采用的引用计数,来管理内存,如果相互强引用持有,则无法释放,从而造成内存泄漏。 避免循环引用的规则 1、...

  • ARC模式下的循环引用引起内存泄漏

    ARC模式下的循环引用引起内存泄漏 ​ 自从iOS 5时代自动引用计数(Automatic Reference...

  • iOS内存优化

    引起内存泄漏的原因 引起内存泄漏的原因主要有三类,如下 循环引用 强引用 非OC对象 1、循环引用。最简单的循环引...

  • NSTimer,NSRunLoop,autoreleasepoo

    引言 NSTimer内存泄漏真的是因为vc与timer循环引用吗?不是! 小伙伴们都知道,循环引用会造成内存泄漏,...

  • Swift中Weak References弱引用和Unowned

    循环引用 循环引用在iOS开发中是需要非常重视的一个问题,不合理的循环引用会导致内存的泄漏,这在开发中是非常危险的...

  • 循环引用导致内存泄漏

    前言 由循环引用导致的内存泄漏是常出现的一个原因。一般都是weak给弱化一方的指针,打破循环引用。一些隐藏的循环引...

网友评论

    本文标题:IOS循环引用、内存泄漏、野指针

    本文链接:https://www.haomeiwen.com/subject/sskkmftx.html