知识导图 知识导图
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
  • OC知识笔记

    • iOS开发-上篇

    • iOS开发-中篇

    • iOS开发-下篇

      • iOS基础概念
      • 头文件
      • 栈
      • 内存泄露
      • 签名
      • 测试
      • 静态库
      • 动态库
      • iOS静、动态库
      • iOS编译流程
      • 启动优化
      • 包体积优化
      • 卡顿优化
      • 架构方案
      • 组件化方案
      • 内存泄露场景
      • iOS开发-三方库

      • iOS开发-工具

      • iOS开发-杂谈

    • iOS开发
    • OC知识笔记
    • iOS开发-下篇
    2023-03-16
    目录

    内存泄露场景

    # 1、解决循环引用的两种宏

    方式一:

    /// 引用
    #define WEAK_SELF __weak __typeof(&*self)weakSelf = self;
    #define STRONG_SELF  __strong __typeof(&*weakSelf) strongSelf = weakSelf;
    
    /// 检查是否为空
    #define CHECK_WEAK_SELF if (weakSelf == nil) { return; }
    #define CHECK_STRONG_SELF if (strongSelf == nil) { return; }
    

    使用示例:

    
    WEAK_SELF
    [self.view1 xxActionWithBlock:^(NSObject * _Nonnull obj) {
        STRONG_SELF
        CHECK_STRONG_SELF
    
        // 逻辑代码
        [strongSelf.view1 configData];
    }];
    

    方式二:

    参考 RAC 或者 SDWebImage 等库的实现,选择一个就好,不要重复

    以 RAC 宏为例:

    /// 定义
    #define weakify(...) \
        rac_keywordify \
        metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
    
    #define strongify(...) \
        rac_keywordify \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Wshadow\"") \
        metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
        _Pragma("clang diagnostic pop")
        
    

    使用示例:

    id foo = [[NSObject alloc] init];
        id bar = [[NSObject alloc] init];
    
        @weakify(self, foo, bar);
        // this block will not keep 'foo' or 'bar' alive
        BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
            // but now, upon entry, 'foo' and 'bar' will stay alive until the block has
            // finished executing
            @strongify(self, foo, bar);
    
           // 逻辑代码 (直接使用self)
       		 [self.view1 configData];
            return [foo isEqual:obj] || [bar isEqual:obj];
        };
    

    ⚠️ Block 内部重新声明的 self 和 block 外部的不是一个对象,只是名称相同,可以理解为block 内部定义了一个局部变量名为self的对象,而恰巧这个场景是允许的,优先取block内部的此对象。所以在内部并不会导致循环引用。

    # 2、常规使用Block场景

    // 使用场景
    @weakify(self);
    [self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
       @strongify(self);  
      
        // self 
        !self.didClickMoreBlock ?: self.didClickMoreBlock(YES);
    }];
    
    // 使用场景
    WEAK_SELF
    [self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
        STRONG_SELF
        !strongSelf.didClickMoreBlock ?: strongSelf.didClickMoreBlock(YES);
    }];
    

    # 3、Block内使用指针

    // 声明变量方式
    @interface xxxVC ()<UITableViewDelegate, UITableViewDataSource>
    {
        NSMutableArray *_dataArray;
        NSInteger _pageNum;
        NSInteger _pageSize;
    }
    
    // 错误使用场景
    WEAK_SELF
    [self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
        STRONG_SELF
        ...
        	[strongSelf->_dataArray xxx];
        ...
    }];
    
    =======================================
    // 原因: 因为非@property 声明的属性,只能通过 strongSelf->_dataArray 的方式,而 -> 是直接通过内存地址访问实例变量的值,而不是点语法的通过属性访问器方法,这样在block延迟调用而self已经释放的情况下可能会导致直接访问内存地址异常
    
    // 修改后
    @property (nonatomic, strong) NSMutableArray *dataArray;
    
    // 使用属性访问器方法
    [self.moreButton addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
        STRONG_SELF
        ...
        	[strongSelf.dataArray xxx];
        ...
    }];
    
    

    # 4、super 导致的内存泄露

    部分场景block中使用super也是会内存泄露的,所以尽量避免

    
    [self.viasInputView setBlock_viasInputViewEditedCompleted:^(AMapPOI * _Nonnull startPoi, AMapPOI * _Nonnull endPoi, NSArray * _Nonnull viaPois) {
        STRONG_SELF
        ...
        // 直接使用了super
        [super sc_routerEventWithName:@"xxxx" userInfo:nil];
    }];
    
    // 上述场景是实际项目中的内存泄露代码,
    
    [self.viasInputView setBlock_viasInputViewEditedCompleted:^(AMapPOI * _Nonnull startPoi, AMapPOI * _Nonnull endPoi, NSArray * _Nonnull viaPois) {
        STRONG_SELF
        ...
        // 改用strongSelf 方法调用
        [strongSelf setupRouterEventWithName:@"ViaEditedSuccessShowDesignView" userInfo:nil];
    }];
    
    - (void)setupRouterEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
        [super sc_routerEventWithName:eventName userInfo:userInfo];
    }
    

    # 5、addSubview 子view在block中调用了自己

    UIButton *leftBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 6, 64, 28)];
    leftBtn.layer.masksToBounds = YES;
    leftBtn.layer.cornerRadius = 14;
    WEAK_SELF
    [leftBtn addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
        leftBtn.selected = YES;
        weakSelf.carSwitchIndex = 1;
    }];
    [self.view addSubview:leftBtn];
    
    
    // 上述场景 中addSubview 的对象在 block 中调用了自己,这种场景也是可能导致内存泄露的
    
    // 修改: 使用 weak_leftBtn
    
    @weakify(self, leftBtn);
    [leftBtn addTapActionWithBlock:^(UIGestureRecognizer * _Nonnull gestureRecoginzer) {
        @strongify(self, leftBtn);
      
        leftBtn.selected = YES;
        self.carSwitchIndex = 1;
    }];
    [self.view addSubview:leftBtn];
    

    # 6、 数组中包含对象使用block

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      UITableViewXXCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
      ...
      
      // cell 数组中包含对象使用block 
      cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:UIColor.redColor callback:^BOOL(MGSwipeTableCell * _Nonnull cell) {
        if (self.dataArray.count < 1) {
            self.tableView.mj_footer.hidden = YES;
            self.emptyView.hidden = NO;
        }
    
      }]];
      
     ...
      
      // 上述场景虽然不是属性直接使用,但是由于其数组依然是强引用,所以也是导致内存泄露的一个原因
      // 修改: 使用弱引用
      @weakify(self);
    	cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:UIColor.redColor callback:^BOOL(MGSwipeTableCell * _Nonnull cell) {
        @strongify(self);
        if (self.dataArray.count < 1) {
            self.tableView.mj_footer.hidden = YES;
            self.emptyView.hidden = NO;
        }
    
      }]];
    

    # 7、Block中使用含 self 的宏导致内存泄露

    (这个肉眼比较难定位)

    // 以下代码存在内存泄露
    ...
    [self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
    	WEAK_SELF // 这里在block内部
      
    	[weakSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
    		STRONG_SELF
    		[strongSelf.tableHeadView needHidenSwitchButton:YES];
    	}];
    	...
    }];
    
    
    // 原因:嵌套block中 WEAK_SELF 宏本身置于block中了,而WEAK_SELF 的 本质是包含self 的代码块,编译插入等于block中使用了self。从而导致内存泄露
    // #define WEAK_SELF __weak __typeof(&*self)weakSelf = self;
    
    // 修改:
    
    ...
    WEAK_SELF // 应该放在最外层
    [self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
    	STRONG_SELF
    	[strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
    		STRONG_SELF
    		[strongSelf.tableHeadView needHidenSwitchButton:YES];
    	}];
    	...
    }];
    

    # 8、嵌套block使用

    
    ...
    WEAK_SELF 
    // 第一层block
    [self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
    	STRONG_SELF
    	...
      
      // 第二层block
      [strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
    		... // 其它业务
        
    		[strongSelf.tableHeadView needHidenSwitchButton:YES];
    	}];
    	...
    }];
    
    // 原因:
    // 上面的示例。 第二层block使用的是第一层 STRONG_SELF 宏 。这个情况什么时候会内存泄露呢
    // 正常是没问题的,但是第二层回调的时候strongSelf 不一定在,这个时候业务逻辑就不对了
    // 如果不确认是否存在耗时方法等业务逻辑,尽量嵌套block 都加上吧
    
    // 修改:
    
    WEAK_SELF 
    // 第一层block
    [self.myFamilyVM queryRelationTagListCompleteBlock:^(BOOL result) {
    	STRONG_SELF // 加上1
    	...
      
      // 第二层block
      [strongSelf.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
        STRONG_SELF // 加上2
        
    		... // 其它业务
        
    		[strongSelf.tableHeadView needHidenSwitchButton:YES];
    	}];
    	...
    }];
    

    # 9、Block中使用 下划线变量

    
    [self.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
      [_tableHeadView needHidenSwitchButton:YES];
    }];
    
    // 原因:
    // 上面的示例。 _tableHeadView 使用下划线,本质上还是self,所以要使用 weakSelf.tableHeadView  
    
    // 修改:
    
    WEAK_SELF
    [self.myFamilyVM familyAddRelationTag:tagStr CompleteBlock:^(BOOL result) {
      [weakSelf.tableHeadView needHidenSwitchButton:YES];
    }];
    
    

    # 10、Target代理强持有导致循环引用

    NSTimer 为例:

    self.scrollTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval
                                                            target:self
                                                          selector:@selector(scrollTimerDidFire:)
                                                          userInfo:nil
                                                           repeats:NO];
                                                           
    [[NSRunLoop currentRunLoop] addTimer:self.scrollTimer forMode:NSRunLoopCommonModes];  
    
    
    // 原因:
    // 上面的示例。iOS10.0以下系统时使用系统默认scheduledTimer方法都会被循环引用,就算是 target:传入 weakSelf 也是一样的
    // 用户可以在DidDisaper  等方法进行关闭定时器,但是不可以在 dealloc 方法中销毁timer,时机太晚了,dealloc不会被调用。
    
    // 修改:
    
    // 对于 10 以上系统可以使用 block的方法
    [NSTimer scheduledTimerWithTimeInterval:1 block:^(NSTimer * _Nonnull timer) {
            
     } repeats:YES];
    
    
    // 兼容iOS10.0以下系统时,要自己处理,方式有很多种
    
    // 1、进行一个 block 的 copy
    #pragma mark - NSTimer(private) 兼容iOS10.0以下系统时使用
    + (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(void))block {
        return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(handle:) userInfo:[block copy] repeats:repeats];
    }
    
    + (void)handle:(NSTimer *)timer {
        void(^block)(void) = timer.userInfo;
        if (block) {
            block();
        }
    }
    
    
    ⚠️ 另外:
    1、兼容iOS10.0以下系统时使用,项目最低系统升级10以上时请使用系统方法
    2、scrollView中启动
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    3、请在dealloc中 invalidate 并置nil
    if (_timer) {
       [_timer invalidate];
       _timer = nil;
    }
    
    // 2、 使用weak delegate 方式,
    // NSProxy 做中间代理等
    
    // NSProxy 方式是 通过 invocation 消息转发方式处理
    
    

    # 11、 单例方法导致内存泄露

    
    1、不是所有的单例都不释放的,部分情况的单例在短时间内持有了大量内存, 在不使用的时候建议适当清理
    2、单例不要持有UI对象,避免无法释放
    
    

    # 12 、MRC与ARC混编

    
    // 如果文件是指定  --fno-objc-arc 的 手动管理模式,那么代码就需要手动对对象进行 release。 不然就会内存泄露
    
    // 这个场景现在比较少,一些老的项目中存在,MRC 文件代码往往因为ARC 已成习惯忘了手动进行是否,也就是手动调用 release
    [xxx release];
    
    // 还有就是使用 autorelease
    [[[xxx aloc] initWith xxx] autorelease];
    
    
    

    # 13 、CoreFoundation与Foundation的桥接

    // 桥接其实也是 混编 场景,也是容易导致内存泄露的一种方式,因为使用习惯的问题
    
    1、ARC: 针对的是 Foundation (OC 语法)代码,进行了一个引用计数管理
    2、CF:而Core Foundation(CF)中的对象是用C语言实现的,分配给CF对象的内存需要手动释放,否则会造成内存泄漏
    3、__bridge 桥接:只做类型转换,不会改变对象的内存管理权逻辑
    			// 3.1 被桥接的对象是OC对象,那么桥接以后不需要手动释放
          NSString *aNSStr = [[NSString alloc] initWithFormat:@"test"];
          CFStringRef aCFStr = (__bridge CFStringRef)aNSStr;
    			// 3.2 被桥接的对象是CF对象,那么桥接以后需要手动释放
    			CFStringRef aCFStr = CFStringCreateWithCString(NULL, "test", kCFStringEncodingUTF8);
          NSString *aNSStr = (__bridge NSString *)aCFStr;
          CFRelease(aCFStr); // 这里必须释放CF对象
    
    4、__bridge_transfer 桥接:除了类型转换,还会把CF对象的所有权移交到OC对象
    
    			// 使用__bridge_transfer 桥接以后不需要手动释放
    			CFStringRef aCFStr = CFStringCreateWithCString(NULL, "test", kCFStringEncodingUTF8);
          4.1)NSString *aNSStr = (__bridge_transfer NSString *)aCFStr;
    			// __bridge_transfer,等价于 CFBridgingRelease()
          4.2)NSString *aNSStr = (NSString *)CFBridgingRelease(aCFStr);
    
    5、使用了 __bridge_retained 将 OC 桥接成CF (__bridge_retained,等价于CFBridgingRetain())
    			// __bridge_retained除了类型转换,还会把OC对象的所有权抢过来给CF对象。因此需要手动释放了
          NSString *aNSStr = [[NSString alloc] initWithFormat:@"test"];  
          5.1)CFStringRef aCFStr = (__bridge_retained CFStringRef) aNSStr;  
          5.2)CFStringRef aCFStr = (CFStringRef)CFBridgingRetain(aNSStr);
          CFRelease(aCFStr); // 这里必须释放CF对象
    
    6、其他:
    在将CoreFoundation对象进行计数减少后,为避免再次访问该对象可能造成野指针访问,建议及时将对象置为NULL。
    对于CoreFoundation框架对象来说,不可重复使用CFRelease(未检测NULL)函数进行计数减少,过度释放时会发生崩溃。
    对于CGImageRef对象可以使用CGImageRelease函数(未检测NULL)
    对于CGFontRef对象可以使用CGFontRelease函数(会检测NULL)
    
    
    

    # 14 、malloc申请内存未 free导致内存泄漏

    很显然, malloc 也是C 方法,需要手动管理内存
    
    

    # 15 、NSURLSessionTask 使用

    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
    
    生成NSURLSessionTask及其子类对象时,该对象会处于挂起状态,此时该对象会一直常驻内存
    需要为该对象调用cancel或resume方法 进行释放
    
    

    # 16 、分类使用 dealloc 打印日志

    ⚠️ ⚠️⚠️⚠️ 分类里不要使用dealloc方法,不然可能会出现各种异常,,,,,
    
    

    # 17 、VC 中 dealloc 释放一些对象导致异常

    有时候我们会遇到在dealloc 进行一些对象释放的代码,大部分是能正常运行,但是,有些就是可以的
    
    本人遇到一些,比如在 dealloc 中 使用 self 释放 一些 self.delegate 导致异常
    
    // 修复:
    
    不要使用self ,改使用 下划线 _
    
    

    # 18、类方法导致的内存泄露

    正常使用类方法+block 不会导致循环引用,但是使用了静态变量或全局变量,就有可能会 发生内存泄漏。

    
    在iOS Objective-C中,类对象Block(Class Object Block)也称为静态Block,指的是在类
    方法中使用的Block。
    
    类对象Block的内存管理方式与实例对象Block不同,因为类对象Block不会引用实例变量,也
    就不会对self进行强引用。但是,如果在Block内部使用了静态变量或全局变量,就有可能会
    发生内存泄漏。
    
    具体来说,如果在类对象Block中使用了静态变量或全局变量,并且这些变量被Block所引
    用,那么就有可能会导致内存泄漏。这是因为,静态变量或全局变量的生命周期与应用程序
    的生命周期相同,如果Block被长期持有,就可能导致这些变量一直无法释放,
    为了避免这种情况发生,可以使用block修饰符来修饰静态变量或全局变量。_block修饰
    符可以使得Block内部对这些变量的引用变成弱引用,这样即使Block被长期持有,也不会导
    致这些变量无法释放。
    
    需要注意的是,在使用类对象Blockl时,一定要注意Block中对静态变量或全局变量的引用关
    系,避免出现内存泄漏问题。同时,在Block中使用block修饰符时,需要注意变量所在的
    作用域和Block所在的作用域,以免出现访问无效指针的情况。
    
    
    组件化方案
    AFNetworking

    ← 组件化方案 AFNetworking→

    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式