iOS常见内存问题分析

2020/6/9 23:26:44

本文主要是介绍iOS常见内存问题分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

引用计数

引自维基百科 引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。 当创建一个对象的实例并在堆上申请内存时,对象的引用计数就为1,在其他对象中需要持有这个对象时,就需要把该对象的引用计数加1,需要释放一个对象时,就将该对象的引用计数减1,直至对象的引用计数为0,对象的内存会被立刻释放。 使用这种方式进行内存管理的语言:Objective-C

iOS是使用引用计数管理内存,非常需要注意的一个点就是持有关系。持有关系就是A_View持有B_View, [B_View removeFromSuperview]释放A_View对B_View的持有,B_View才会释放。 如果B_View没有调用[B_View removeFromSuperview],即使B_View=nil,也不会释放。因为A_View依然在持有A_View。

所以在iOS里面想要obj释放,不要使用obj=nil,如果持有关系没解除,释放不掉的。

内存问题-定时器

定时器在持有关系上比较特殊,生成一个定时器并开启,RunLoop会持有Timer,Timer会持有Target,Timer不关闭,Target和Timer都不会释放。

释放方式一【手动停止定时器】

- (void)invalidate;
复制代码

@interface B_View : UIView

/** timer */
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation B_View

- (void)stopTimer {
    [_timer invalidate];
}
复制代码

B_View手动停止定时器,这样Timer释放了对B_View的持有,B_View就可以dealloc了。

释放方式二【Timer持有中间对象】

中间对象作为Timer的Target,每次触发定时器的判断B_View是否被释放了,释放了就停止定时器。这样B_View在使用定时器的时候,不需要再操心定时器的释放了。

weakTarget实现如下

@interface IKWeakTimerTarget : NSObject

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;

@end

@implementation IKWeakTimerTarget

- (void)fire:(NSTimer *)timer {
    if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
    } else {
        [self.timer invalidate];
    }
}

@end

@implementation IKWeakTimer

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     target:(id)aTarget
                                   selector:(SEL)aSelector
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    IKWeakTimerTarget *timerTarget = [[IKWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(IKTimerHandler)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
    if (userInfo != nil) {
        [userInfoArray addObject:userInfo];
    }
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(timerBlockInvoke:)
                                       userInfo:[userInfoArray copy]
                                        repeats:repeats];
}

+ (void)timerBlockInvoke:(NSArray*)userInfo {
    IKTimerHandler block = userInfo[0];
    id info = nil;
    if (userInfo.count == 2) {
        info = userInfo[1];
    }
    
    if (block) {
        block(info);
    }
}

@end
复制代码

平时使用定时器还需要注意一点,就是在starTimer之前,最好先stopTimer一下。有可能starTimer多次,生成了多个Timer对象,造成一堆的Timer在跑,没释放。

内存问题-延迟执行

dispatch延迟3秒执行block,要在block里面使用weak_self,如果3秒内weak_self释放了,weak_self为nil。

weakify(self)
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
    if (!weak_self) {
        return;
    }
    [weak_self xxx];
    [weak_self aaaaa:@"3"];
    [[CJK_FloatHandle sharedInstance] callingEnd];
});
复制代码

3秒内weak_self释放了,后面的代码就不会执行了。

注意,要在block里面进行weak_self为nil的判断,如果不做这个判断,后面的单例依然会执行!!!

内存问题-网络请求

网络请求的问题和上面延迟执行的问题类似,由于网络是异步加载的,在网络环境很差的时候,如果页面退出了,由于网络请求的block还在持有self页面,导致页面不能释放,直到网络请求返回执行完block才释放self。

注意,如果block一直没有回调,self就一直不释放!!!

- (void)getBannerData {
    weakify(self)
    [CJActivityBannerService reqBannerList:@{@"type":@(_type)} complete:^(NSArray * _Nonnull arr) {
        strongify(self)
        if (!self) {
            return;
        }
        
        [self _initView];
        [self.bannerView configBannerCellWithModel:arr];
    }];
}
复制代码

这里对临时变量self做了为nil的判断,虽然不做判断也没问题,因为block里面不存在单例、after等场景,最好是养成习惯,对self或weak_self做一次nil判断,以后即使增加代码,也不会有隐藏风险。

内存问题-代理

其实代理就是一个对象持有另外一个对象,然后执行另外一个对象的方法。如果A持有B,B的delegate刚好是A,那B的delegate要用weak修饰了。

@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;
复制代码

内存问题-单例

很简单的道理,因为单例贯穿整个APP的生命周期的,单例不会释放。如果单例持有了一个外部传过来的view,这个view需要用weak修饰,不然view就一直被单例持有,不会释放。

@interface GiftComboAnimaOperationManager : NSObject

/// 父视图weak
@property (nonatomic, weak) UIView *parentView;

/// 单例
+ (instancetype)sharedManager;

@end
复制代码

内存问题-动画

CAAnimation的delegate为strong,如果CAAnimation不释放,我们的self也不会释放。

/* The delegate of the animation. This object is retained for the
 * lifetime of the animation object. Defaults to nil. See below for the
 * supported delegate methods. */

@property(nullable, strong) id <CAAnimationDelegate> delegate;

/* When true, the animation is removed from the render tree once its
 * active duration has passed. Defaults to YES. */

@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

复制代码

如果removedOnCompletion设置为NO,CAAnimation执行完动画并不会主动释放。 这就需要手动释放CAAnimation。

[CAAnimation removeAnimationForKey:@"key"];
复制代码

总结

对比以上场景可以发现,对象没有释放的根本原因是被持有了,这也是引用计数的原理。在代码中,建议规范使用block,养成习惯在block里面对self判断nil。如下。

weakify(self)
^() {//block example
	strongify(self)
	if (!self) {
	    return;
	}

	[self xxxx];
}
复制代码


这篇关于iOS常见内存问题分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程