wordpress站内链接跳转,新闻门户网站什么意思,区域推广网站,网站怎么运营推广引言
在iOS应用开发中#xff0c;内存泄漏是一个常见而严重的问题。本文将探讨一些iOS应用中常见的内存泄漏原因#xff0c;并提供一些最佳实践#xff0c;帮助开发者避免这些问题#xff0c;提高应用性能。
什么是内存泄漏
内存泄漏是指在程序运行时#xff0c;由于错…引言
在iOS应用开发中内存泄漏是一个常见而严重的问题。本文将探讨一些iOS应用中常见的内存泄漏原因并提供一些最佳实践帮助开发者避免这些问题提高应用性能。
什么是内存泄漏
内存泄漏是指在程序运行时由于错误的内存管理分配的内存空间无法被正常释放导致系统中的可用内存逐渐减少最终可能导致应用程序性能下降甚至崩溃的问题。iOS中的内存管理机制是依赖引用计数进行自动管理而引用计数的最大缺陷就在于它不能处理环状的引用关系。
常见的iOS内存泄漏场景
1.子对象持有它的父对象
interface LMAlbum : NSObjectproperty(nonatomic, copy)NSString * title;
property(nonatomic, copy)NSArray * photos;end
interface LMPhoto : NSObjectproperty(nonatomic, copy)NSString * name;
property(nonatomic, strong)HPAlbum * album;//LMPhoto通过强引用指向它所属的相册end
当我们创建一个相册album对象相册中包含一个有许多照片LMPhoto对象的数组
照片LMPhoto对象又包含一个所属相册的属性。
照片LMPhoto对象在album的photos中有强引用引用计数为1。
album对象又在照片LMPhoto对象的album中有强引用引用计数为1。所以当这些对象不再被使用的时候它们的内存也不会被释放因为它们的引用计数不会被降为0。
解决方案
我们可以通过子对象用weak引用指向它的父对象的方式解决该问题。
interface LMPhoto : NSObjectproperty(nonatomic, copy)NSString * name;
property(nonatomic, weak)LMAlbum * album;//LMPhoto通过弱引用指向它所属的相册end
2.代理
protocol LMRequestManagerDelegate NSObject- (void)finish;endinterface LMRequestManager : NSObjectproperty(nonatomic,strong)idLMRequestManagerDelegate delegate;- (void)requstData;end
interface ViewController ()LMRequestManagerDelegateproperty(nonatomic,strong)LMRequestManager * requestManager;endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.requestManager [[LMRequestManager alloc] init];self.requestManager.delegate self;[self.requestManager requstData];
}- (void)finish {}
上述案例中ViewController通过属性强持有requestManager。
而self.requestManager.delegate self;
此句代码使得LMRequestManager强持有了self这就是产生循环引用的地方。
解决方案
我们需要保持对回调代理的弱引用或者不需要将LMRequestManager设置为属性。本质上这里和上一个例子是相同的。
protocol LMRequestManagerDelegate NSObject- (void)finish;endinterface LMRequestManager : NSObjectproperty(nonatomic,weak)idLMRequestManagerDelegate delegate;- (void)requstData;end
3.block
- (void)method{self.name Joyme;self.block ^{NSLog(%,self.name);};self.block();
}
这也将产生循环引用因为self持有了block然后在block中捕获了self。
解决方案
我们可以使用 __weak typeof(self) weakSelf self;的方式进行解决。
- (void)method{self.name “Joyme”;__weak typeof(self) weakSelf self;self.block ^{NSLog(%,weakSelf);};self.block();
}
4.计时器
implementation LMNewsFeedViewController- (void)startCountdown{self.timer [NSTimer scheduledTimerWithTimeInterval:120 target:self selector:selector(updateFeed:) userInfo:nil repeats:YES];
}- (void)dealloc{[self.timer invalidate];
}end
上述代码中有非常明显的循环引用对象持有了计时器同时计时器也持有了对象。此时我们不能通过不设置为属性并且我们也不可以使用weak来修饰timer。相反我们需要持有timer属性以便可以在后续被销毁。
这种情况我们不能指望dealloc能够清理这些对象因为建立了循环引用dealloc方法永远都不会被调用计时器也永远都不会执行invalidated。
要解决这个问题有两个方案
主动调用invalidate将代码分离到多个类中
第一个方案可以写在当视图控制器退出时
- (void)didMoveToParentViewController:(UIViewController *)parent{if(parent nil){[self cleanup];}
}- (void)cleanup{[self.timer invalidate];
}
或者通过拦截返回按钮的响应
- (id)init{if(self [super init]){self.navigationItem.backBarButtonItem.target self;self.navigationItem.backBarButtonItem.action selector(backButtonPreDetected);}return self;
}- (void)backButtonPressDetected:(id)sender{[self cleanup];[self.navigationController popViewControllerAnimated:TRUE];
}- (void)cleanup{[self.timer invalidate];
}
另一个方案更优雅一些是将持有关系分散到多个类中。
interface LMNewFeedUpdateTaskproperty(nonatomic,weak)id target;//target属性是弱引用。target会在这里实例化任务并持有它。
property(nonatomic,assign)SEL selector;
property(nonatomic,strong)NSTimer * timer;endimplementation LMNewFeedUpdateTask- (void)initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector{if(self [super init]){self.target target;self.selector selector;self.timer [NSTimer scheduledTimerWithInterval:interval target:self selector:selector(fetchAndUpdate:) userInfo:nil repeats:YES];}return self;
}- (void)fetchAndUpdate:(NSTimer*)timer{//fetchAndUpdate:方法会周期性地执行__weak typeof(self)weakSelf self;dispatch_async(dispatch_get_main_queue(),^{__strong typeof(self) sself weakSelf;if(!sself){return;}if(sself.target nil){return;}id target sself.target;SEL selector sself.selector;if([target respondsToSelector:selector]){[target performSelector:selector withObject:];}});
}- (void)shutdown{//shutdown方法对计时器调用invalidate。运行循环会终止对计时器的调用于是计时器成为任务对象持有的唯一引用。[self.timer invalidate];
}end
implement LMNewsFeedViewController- (void)viewDidLoad{//对任务对象进行初始化其内部会触发计时器。self.updateTask [LMNewsFeedUpdateTask initWithTimeInterval:120 target:self selector:selector(updateUsingFeed:)];
}- (void)updateUsingFeed:(id)obj{//更新UI
}- (void)dealloc{//负责调用任务对象的shutdown方法其内部会销毁计时器。注意dealloc在此处是明确可用的因为该对象没有被其他的地方所引用。[self.updateTask shutdown];
}
5.延迟执行
#import LMDataListViewController.himplementation LMDataListViewController.h- (void)viewDidLoad {[super viewDidLoad];[self performSelector:selector(run) withObject:nil afterDelay:30];
}- (void)run{
}
上述案例当LMDataListViewController退出的时候会出现延迟释放的情况
当执行[self performSelector:selector(run) withObject:nil afterDelay:30];代码的时候会对self进行一个捕获当前self的引用计数进行1直到延迟方法执行后才会进行-1操作。
所以self在延迟调用的方法执行之前会始终得不到释放。
解决方案
其一还是显示的调用下面方法。
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector(run) object:nil];
其二参考定时器的解决方案我们可以设计出相似类来解决这个问题。
#import LMAfterTask.hinterface LMAfterTask ()property(nonatomic,weak)id target;//target属性是弱引用。target会在这里实例化任务并持有它。
property(nonatomic,assign)SEL selector;endimplementation LMAfterTask- (id)initWithAfterInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector{if (self [super init]) {self.target target;self.selector selector;[self performSelector:selector(performMethod) withObject:nil afterDelay:interval];}return self;
}- (void)performMethod{if(self.target nil){return;}if([self.target respondsToSelector:self.selector]){[self.target performSelector:self.selector withObject:nil];}
}- (void)cancel{[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector(performMethod) object:nil];
}
#import LMNewFeedUpdateTask.h
#import LMAfterTask.hinterface LMDataListViewController ()property(nonatomic,strong)LMAfterTask * afterTask;endimplementation LMDataListViewController.h- (void)viewDidLoad {[super viewDidLoad];self.afterTask [[LMAfterTask alloc] initWithAfterInterval:15 target:self selector:selector(run)];
}- (void)run{NSLog(跑起来);
}- (void)dealloc{[self.afterTask cancel];
}end
使用GCD的延迟执行也会有同样的问题。
#import LMDataListViewController.himplementation LMDataListViewController.h- (void)viewDidLoad {[super viewDidLoad];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[self run];});
}- (void)run{}
但我们可以通过使用__weak来进行解决此时虽然self会得到正常的释放但是延迟的的代码块还是会执行的。操作不当还是会出现其它不可预知的情况所以我们还需要显示的取消该任务块。
#import LMDataListViewController.hinterface LMDataListViewController.h ()
{dispatch_block_t _taskBlock;
}endimplementation LMDataListViewController.h- (void)viewDidLoad {[super viewDidLoad];__weak LMDataListViewController.h * weakSelf self;_taskBlock dispatch_block_create(DISPATCH_BLOCK_BARRIER, ^{[weakSelf run];});dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), _taskBlock);
}- (void)run{NSLog(跑起来);
}- (void)dealloc{if (_taskBlock) {dispatch_block_cancel(_taskBlock);}
}end
最佳实践
我们可以遵循以下最佳实践避免内存泄漏
对象不该持有它的父对象应该用weak引用指向它的父对象。连接对象不应该持有它们的目标对象目标对象角色是持有者。连接对象包括使用代理的对象观察者。定时器需要显式的进行销毁。延迟执行代码需要显式的进行取消
结尾
内存泄漏对于我们开发者而言可能是一生之敌。上面只是简单的列举一些开发过程中比较常见的场景希望能够帮助到大家避免这些问题提高应用性能。