Memory leak analysis ios Development Series (under)

Continued articles herein, this mainly on notice and does not remove KVO observer, block circular reference, together with the use of a memory leak caused NSThread and RunLoop.

1, notice due to a memory leak

1.1, after ios9, general notifications, no longer need to manually remove the observer, the system will automatically call the dealloc at the time of [[NSNotificationCenter defaultCenter] removeObserver: self]. Ios9 be removed manually before required.

The reason is: when ios9 previously registered observer, the observer will notice the center of the object do not retain the operation, but were unsafe_unretained references, so when the viewer is recycled, if not inform manually removed, then the pointer to the recovered memory area will become a field guide, the next time you send a notification, it will cause the program to crash.

ios9 center will start notification weak weak reference for the observer, even without notification time manually removed from the pointer automatically after blanking the observer is recovered, then the retransmission notification message is sent to the null pointer is not there will be problems.

1.2, using block mode notification listening, still need to be processed, because the use of this API will lead to the observer by the system retain.

Consider the following code:

[[NSNotificationCenter defaultCenter] addObserverForName:@"notiMemoryLeak" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
  NSLog(@"11111");
}];
//发个通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"notiMemoryLeak" object:nil];


The first print comes in first, second came in printed twice, the third time to print three times. You can try the demo, demo address see the bottom of the article.

The solution is to record the notification of the recipient, and the recipient is removed inside dealloc like:

@property(nonatomic, strong) id observer;
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"notiMemoryLeak" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
  NSLog(@"11111");
}];
//发个通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"notiMemoryLeak" object:nil];
- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"notiMemoryLeak" object:nil];
  NSLog(@"hi,我 dealloc 了啊");
}

 

2, KVO caused by a memory leak

2.1, is now generally use KVO, even if the observer is not removed, there will not be a problem
Consider the following code:

- (void) {kvoMemoryLeak 
    MFMemoryLeakView View * = [[MFMemoryLeakView the alloc] the initWithFrame: self.view.bounds]; 
    [self.view addSubview: View]; 
  [View the addObserver: Self forKeyPath: @ "Frame" Options: NSKeyValueObservingOptionNew context: nil ]; 
  // take the initiative to call these two principles have inspired kvo specific late in kvo Detailed explanation 
  [View willChangeValueForKey: @ "Frame"]; 
  [View didChangeValueForKey: @ "Frame"]; 
} 

- (void) observeValueForKeyPath :( NSString *) keyPath ofObject: (ID) Object Change: (NSDictionary <NSKeyValueChangeKey, ID> *) Change context: (void *) context { 
  IF ([keyPath as isEqualToString: @ "Frame"]) { 
    NSLog (@ "View =% @ ", Object); 
  } 
}

This situation is not removed there will not be a problem, I guess because the view is destroyed when the controller is also destroyed, so the view of the frame will not change, do not remove the observer would have no problem, so I did a suspect, if the observed object is one that will not destroy what happens? When the observers have been destroyed, the object being observed also changed, there will be a problem?

2.2 observe the object will not be destroyed, do not remove the observer, uncertain collapse will occur.

Then the above speculation, first create a singleton object MFMemoryLeakObject, there is a property title:

@interface MFMemoryLeakObject : NSObject
@property (nonatomic, copy) NSString *title;
+ (MFMemoryLeakObject *)sharedInstance;
@end

#import "MFMemoryLeakObject.h"
@implementation MFMemoryLeakObject
+ (MFMemoryLeakObject *)sharedInstance {
  static MFMemoryLeakObject *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
    sharedInstance.title = @"1";
  });
  return sharedInstance;
}
@end

Then listen for the title attribute MFMemoryLeakObject in MFMemoryLeakView:

#import "MFMemoryLeakView.h"
#import "MFMemoryLeakObject.h"

@implementation MFMemoryLeakView
- (instancetype)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.backgroundColor = [UIColor whiteColor];
    [self viewKvoMemoryLeak];
  }
  return self;
}

#pragma mark - 6.KVO造成的内存泄漏
- (void)viewKvoMemoryLeak {
  [[MFMemoryLeakObject sharedInstance] addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  if ([keyPath isEqualToString:@"title"]) {
    NSLog(@"[MFMemoryLeakObject sharedInstance].title = %@",[MFMemoryLeakObject sharedInstance].title);
  }
}

The last change in the value of title in the controller, front view changed once destroyed, after the destruction of a change:

//6.1、在MFMemoryLeakView监听一个单例对象
MFMemoryLeakView *view = [[MFMemoryLeakView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:view];
[MFMemoryLeakObject sharedInstance].title = @"2";

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  [view removeFromSuperview];
  [MFMemoryLeakObject sharedInstance].title = @"3";
});

After attempts, for the first time no problem, the second on the collapse occurred, error field guide specific we can do the test with a demo, demo address see the bottom.

The solution is simple, remove the observer's view dealloc method like:

- (void)dealloc {
  [[MFMemoryLeakObject sharedInstance] removeObserver:self forKeyPath:@"title"];
  NSLog(@"hi,我MFMemoryLeakView dealloc 了啊");
}

 

In general, write code or specifications that have observed there should be removed, otherwise the project was likely to produce a variety of bug Yuxianyusi. KVO There is also a duplicate removing cause a crash, please refer to this article: https://www.cnblogs.com/wengzilin/p/4346775.html.

3, block caused by a memory leak

block caused memory leaks are generally circular references that block the owner within the scope and cites a block, thus leading to block the owner never release memory.

This article will explain the scene block memory leak analysis and solutions, the principle of other block will be explained in a single chapter after the block's.

3.1, block as an attribute, called internally the self or member variables causing a circular reference.

Consider the following code, define a block properties:

typedef void (^BlockType)(void);

@interface MFMemoryLeakViewController ()
@property (nonatomic, copy) BlockType block;
@property (nonatomic, assign) NSInteger timerCount;
@end

Then call:

#pragma mark - 7.block memory leak caused 
- (void) {blockMemoryLeak 
  // 7.1 normal circular reference block 
  self.block = ^ () { 
    NSLog (@ "% @ = MFMemoryLeakViewController", Self); 
    NSLog (@ "MFMemoryLeakViewController % ZD = ", _ timerCount); 
  }; 
  self.block (); 
}


This creates a cycle block and controller references, the solution is simple, use __block at MRC, use __weak closed off at ARC, member variables -> The access can be resolved.

It should be noted that only the modified objects __weak, if released, the object becomes nil during block execution, which may cause some problems, such as arrays and dictionaries of insertion.

It is proposed to __weak the modified target once again strong references within the block, so that the process Block execution, the object is not set to nil, while in the Block finished, ARC this target will be automatic release, will not cause a circular reference:

typeof __weak (Self) weakSelf = Self; 
self.block = ^ () { 
  // proposes to add about strong references, avoid weakSelf be freed 
  __strong typeof (weakSelf) strongSelf = weakSelf; 
  NSLog (@ "MFMemoryLeakViewController =% @", strongSelf ); 
  NSLog (@ "% MFMemoryLeakViewController = ZD", strongSelf -> _ timerCount); 
}; 
self.block ();

 

3.2, NSTimer created using a block of time, pay attention to the circular reference

Look at this code:

[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
  NSLog(@"MFMemoryLeakViewController = %@",self);
}];

From the block's point of view, there is no circular reference, in fact, within this class method, there is a strong reference to self timer, and so they will have to use a closed-loop __weak cut, in addition, timer created in this way, repeats when YES is also need to invalidate process, or a timer or a stop.

@property(nonatomic,strong) NSTimer *timer;
__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
  NSLog(@"MFMemoryLeakViewController = %@",weakSelf);
}];
- (void) the dealloc { 
  [_timer the invalidate]; 
  NSLog (@ "Hi, I MFMemoryLeakViewController dealloc, ah"); 
}

 

4, NSThread caused by a memory leak

NSThread and RunLoop used in conjunction, pay attention to the circular reference problem, consider the following codes:

- (void)threadMemoryLeak {
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
  [thread start];
}

- (void)threadRun {
  [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
  [[NSRunLoop currentRunLoop] run];
}

Cause of the problem is that "[[NSRunLoop currentRunLoop] run];" This line of code. The reason is that the run method NSRunLoop is unstoppable, it is designed to open a thread never destroyed, but also when the threads created on the current of the current controller (self) were strong references, so causing a circular reference.

The solution is created using the block when creating ways:

- (void)threadMemoryLeak {
  NSThread *thread = [[NSThread alloc] initWithBlock:^{
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
  }];
  [thread start];
}

So that the controller can be released, but in fact this thread is not destroyed, even if the call "CFRunLoopStop (CFRunLoopGetCurrent ());" can not stop this thread because this can only stop this time RunLoop, the next cycle can still continue proceed. Specific solutions I will explain in a single chapter RunLoop's.

The memory leak analysis, I write to you, because I am limited level, many places still could not speak enough depth to welcome you to be correct.

demo Institute Add: https://github.com/zmfflying/ZMFBlogProject.git

 

Guess you like

Origin www.cnblogs.com/zmfflying/p/11110752.html