IOS之循环引用

1. 内存分配区

  • 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。

  • 栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  • 堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

2.循环引用

2.1 block

@interface CeshiViewController ()
//声明一个block
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, strong) NSString *str;

@end

@implementation CeshiViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
  // 这里存在一个循环引用 
 	self.str = @"dealloc";
 	//解决方法 __weak typeof(self) weakSelf = self;  将下列代码中weakSelf 替换成 self
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"=======%@",self.str);
        });
        
    };
    
    self.block();
}


- (void)dealloc {
    NSLog(@"CeshiViewController====dealloc");
    
} 

输出结果:
在这里插入图片描述
解决了循环引用,但是我们在block中做的 事情没有实现,解决这个问题只需要在block的内部使用__strong来修饰weakSelf 增加医用计数器1 即可

self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"=======%@",strongSelf.str);
        });
        
    };

输出结果变成:
在这里插入图片描述

  • block实质上是一个匿名函数,我们在函数内部声明一个属性 == __strong typeof(self) strongSelf = weakSelf;==
    当block(函数)执行完毕的时候后,函数内声明的变量就会被回收。 即强引用会消失。然后通知deallo()方法。

2.2 NSTimer

- (void)viewDidLoad {
    [super viewDidLoad];
   
    [self TimerTest];
}

- (void)TimerTest {
    [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(fire) userInfo:nil repeats:YES];
}

- (void)fire {
    NSLog(@"fire=============");
}
  • 当控制器pop回去的时候,timer还在执行,当前控制器并没有销毁,不是我们希望的
//上面上传timer的方法--》相当于下面这两句
self.timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(fire) userInfo:nil repeats:YES]
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
  • RunLoop是一个死循环,只要循环不结束,timer就一直在,而timer又强引用了,self(VC),所有timer不消失,那self就一直没有办法被回收
- (void)dealloc {
    NSLog(@"CeshiViewController====dealloc");
    
    //这两句代码并没有任何作用,因为根本不会走这个dealloc()这个析构函数方法,所有没有任何作用
    //但是当我们在- (void)viewWillDisappear:(BOOL)animated方法内执行这两句代码,是可以起到作用的,可以关闭定时器,但是我们跳转下级页面时,定时器也停止,这符合我们的需求。
    [self.timer invalidate];
    self.timer = nil;
    
}
  • 解决办法:
    – 第一种解决方法
//在这个方法中 来执行这两句代码
- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
    //parent 就是当前控制器
        [self.timer invalidate];
        self.timer = nil;
    }
}

- 第二种解决方法
不让定时器强引用我们当前的VC, 我们可不可使用消息转发机制来实现
@interface CeshiViewController ()
@property (nonatomic, strong) NSTimer *timer;

@property (nonatomic, strong) id target;
@end

@implementation CeshiViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
    [self TimerTest];
}

- (void)TimerTest {
    
    //创建target对象
    _target = [NSObject new];
    
    class_addMethod([_target class], @selector(fire), (IMP)fireImp, "v@:");
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:_target selector:@selector(fire) userInfo:nil repeats:YES];
}

//MARK: -- 第二种方法


void fireImp(id self, SEL _cmd) {
    NSLog(@"fireImp====fire=============");
}
- (void)fire {
    NSLog(@"fire=============");
}

实现原理(不让定时器强引用self): 当前的self是持有 target 和 self.Timer对象的, 而Timer本身出于一个大的循环中,Timer是持有targe 并不持有self,所有当我们pop VC的时候 当前控制器可以正常的执行 析构函数dealloc(), 正常释放,而我们在析构函数中,销毁Timer
在这里插入图片描述

-- 第三种解决方法:
是利用NSProxy来做中间桥梁和利用消息转发的机制 来实现
#import "CeshiViewController.h"
#import "objc/runtime.h"
#import "GYProxy.h"

@interface CeshiViewController ()
@property (nonatomic, strong) GYProxy * gyProxy;
@end

@implementation CeshiViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self thridTest];
    
}


#pragma mark -- 第三种方法
- (void)thridTest {
    //只有alloc方法
    self.gyProxy = [GYProxy alloc];
    self.gyProxy.target = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self.gyProxy selector:@selector(fire) userInfo:nil repeats:YES];
}
// 中间桥梁的实现代码
@interface GYProxy : NSProxy

/// 声明一个target的对象
@property (weak, nonatomic) id target;
@end


#import "GYProxy.h"
#import <objc/runtime.h>

@implementation GYProxy

/// 消息转发机制-》  方法签名
/// @param sel <#sel description#>
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

/// 返回处理方法的对象
/// @param invocation <#invocation description#>
- (void)forwardInvocation:(NSInvocation *)invocation {
    
    [invocation invokeWithTarget:self.target];
}

输出结果:
在这里插入图片描述

3.内存检测

  • 静态检测
  • 动态检测(Instrument/MLeakFinder)
  • 析构方法打印

4.实现例子

  • 需求分析: UIViewController 是否释放, pop/push
  • 思路分析:
    • ViewWillAppear / didDisAppear
    • 确定对象是否存活

在这里插入图片描述
NSObject+LGSwizzing

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (LGSwizzing)

- (void)willDealloc;

+ (void)swizzleSEL:(SEL)oringSEL withSEL:(SEL)swizzlingSEL;
@end

NS_ASSUME_NONNULL_END



#import "NSObject+LGSwizzing.h"
#import <objc/runtime.h>

@implementation NSObject (LGSwizzing)
+ (void)swizzleSEL:(SEL)oringSEL withSEL:(SEL)swizzlingSEL {
    //1.获取到当前的Class
    Class class = [self class];
    
    //2.得到原始方法 和需要交换的方法
    Method originMethod = class_getInstanceMethod(class, oringSEL);
    Method currentMethod = class_getInstanceMethod(class, swizzlingSEL);
    
    //3.判断当前类中需要替换的的方法有没有在当前类中实现
    /**
     class_addMethod是runtime中的动态添加方法,如果能够添加成功说明本类并没有实现这个要添加的方法,如果不能添加成功说明本类实现这个方法。实现方法包括新增的方法和重写父类方法。
    */
    BOOL didAddMethod = class_addMethod(class, oringSEL, method_getImplementation(currentMethod), method_getTypeEncoding(currentMethod));
    if (didAddMethod) {
        //代表当前类中被替换的方法已经实现, 那就开始替换
        //调用了class_replaceMethod会把原来方法的IMP指向另一个方法的实现
        class_replaceMethod(class, swizzlingSEL, method_getImplementation(currentMethod), method_getTypeEncoding(currentMethod));
    } else {
        //如果没有实现 则替换方法
        method_exchangeImplementations(originMethod, currentMethod);
    }
}

- (void)willDealloc {
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //在block中引用
        __strong typeof(self) strongSelf = weakSelf;
        NSLog(@"lg_WillDealloc===============%@",strongSelf);
    });
}
@end

UINavigationController+LGLeaksTest

#import "UINavigationController+LGLeaksTest.h"
#import "NSObject+LGSwizzing.h"
#import <objc/runtime.h>


@implementation UINavigationController (LGLeaksTest)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //交换方法--》 pop方法
        [self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(lg_popViewControllerAnimated:)];
    });
}

- (UIViewController *)lg_popViewControllerAnimated:(BOOL)animated {
    UIViewController *popVC = [self lg_popViewControllerAnimated:animated];//pop的出去的VC
    
    //设置popvc出去的属性状态
    extern const char * LGVCFLAT;//声明
    objc_setAssociatedObject(popVC, LGVCFLAT, @(YES), OBJC_ASSOCIATION_ASSIGN);
    
    return popVC;
}

UIViewController+LGLeakTest

#import "UIViewController+LGLeakTest.h"
#import <objc/runtime.h>
#import "NSObject+LGSwizzing.h"

const char * LGVCFLAT = "LGVCFLAT";

@implementation UIViewController (LGLeakTest)
/**
 1.交换方法
 */



+ (void)load {
    //为了防止手动调用,出现又把方法交换回来的结果
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //交换两个方法
        [self swizzleSEL:@selector(viewWillAppear:) withSEL:@selector(lg_viewWillAppear:)];
        [self swizzleSEL:@selector(viewDidDisappear:) withSEL:@selector(lg_viewWillDisAppear:)];
        
    });
}

- (void)lg_viewWillAppear:(BOOL)animate {
    [self lg_viewWillAppear:animate];
    
    //怎么知道VC是pop状态还是push状态, 可以使用runtime添加一个属性来记录
    objc_setAssociatedObject(self, LGVCFLAT, @(NO), OBJC_ASSOCIATION_ASSIGN);
    
}

- (void)lg_viewWillDisAppear:(BOOL)animate {
    [self lg_viewWillDisAppear:animate];
    if ([objc_getAssociatedObject(self, LGVCFLAT) boolValue]) {
        // 如果为YES则表示 pop的状态--》 怎么知道vc的pop的状态
        [self willDealloc];
    }
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值