Welcome to my blog post
A memory leak is a situation in which dynamically allocated heap memory (space managed by the programmer himself) in a program fails to be released or cannot be released for some reason, resulting in a waste of system memory, slowing down the program and even crashing the system.
Memory leak scenarios encountered in iOS development fall into several categories:
A circular reference
Circular references occur when object A strongly references object B and object B strongly references object A, or when multiple objects strongly reference each other and form A closed loop.
Block
Blocks have strong references to objects inside them, so they need to be used to ensure that they do not form circular references.
As an example, look at this code:
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @".self.name);
});
};
self.block();
Copy the code
A block is an attribute of self, so self strongly references the block, and inside the block calls self, so the block also strongly references self. There are two ways to solve this circular reference problem.
Use the Weak – Strong Dance
First use __weak to set self as weak reference to break the “cycle” relationship, but weakSelf may be released in advance in the block, so it is also necessary to use __strong to strongreference weakSelf inside the block. This ensures that the strongSelf will not be released until the block ends.
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @", strongSelf.name);
});
};
self.block();
Copy the code
Disconnection of holding
Use the __block keyword to set a pointer vc to self, re-forming a loop of self → block → vc → self. Setting vc to nil at the end of the call breaks the loop holding chain and causes self to be released properly.
__block UIViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @", vc.name);
vc = nil;
});
};
self.block();
Copy the code
Here’s another question: why is vc decorated with __block?
First, the block itself does not allow you to change the value of an external variable. But variables decorated with __block are stored in the structure of a stack as structure Pointers. When the object is held by the block, the memory address of the “external variable” in the stack is placed in the heap, and the value of the external variable can be changed within the block.
There is another way to disconnect the holding relationship. Self is passed inside the block as a parameter, so that self is not held by the block, so that there is no loop holding chain.
self.block = ^(UIViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @", vc.name);
});
};
self.block(self);
Copy the code
NSTimer
We know that NSTimer objects are created in target-action mode. Usually target is the class itself, and we often declare NSTimer as a property for convenience, like this:
// For the first method, timer is added to runloop by default
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
// Add timer to runloop manually
self.timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code
This forms a loop of self → timer → self(Target). As long as self is not released, dealloc is not executed, timer cannot be destroyed in dealloc, self is always strongly referenced, never released, and the loop is contradictory, eventually causing a memory leak.
What if timer is only a local variable, not a property?
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timeFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code
Self can’t release either.
Because timer is strongly referenced in the addition of a runloop, this creates a chain of runloop → timer → self(target) holders. The timer, as a local variable, cannot perform invalidate, so self will not be released until the timer is destroyed.
So any time you apply a timer, add a runloop, and the target is self, even if it’s not a circular reference, it’s going to leak because self has no time to release.
There are several ways to solve this problem, and developers can choose among them.
Destroy NSTimer when appropriate
When NSTimer is initialized, adding runloop results in being strongly referenced by the current page, so dealloc is not executed. So you need to destroy _timer at the right time and break the strong reference relationship between _timer, runloop and the current page.
[_timer invalidate];
_timer = nil;
Copy the code
The timing in the viewcontrollers can choose didMoveToParentViewController, viewDidDisappear, can choose removeFromSuperview etc., in the View but this plan and must be feasible.
If _timer is destroyed in viewDidDisappear, the countdown will be destroyed when the user clicks to jump to the user protocol page. This is not logical. Therefore, it needs to be considered according to the requirements of specific business scenarios.
Use the GCD timer
GCD is not runloop-based, and you can use GCD’s timer instead of NSTimer for timing tasks. However, it is important to note that circular references in GCD internal blocks still need to be addressed.
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC.0);
dispatch_source_set_event_handler(_timer, ^{
[weakSelf timeFire];
});
// Start the timer
dispatch_resume(_timer);
// Destroy the timer
// dispatch_source_cancel(_timer);
Copy the code
Destruction through intermediaries
The mediator is the one that replaces self in target with another object, the mediator binds the selector, and then releases the timer in dealloc.
There are two types of intermediaries, an NSObject object and a subclass of NSProxy. They exist to break a strong reference to self so that it can be released.
Take an NSObject object as the mediator
Create a new NSObject object, _target, and dynamically add a method to it with an address pointing to the IMP of timeFire in the self method list. There is no direct reference between _target and self, but the method in self can be referenced, so there is no circular reference.
_target = [NSObject new];
class_addMethod([_target class].@selector(timeFire), class_getMethodImplementation([self class].@selector(timeFire)), "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_target selector:@selector(timeFire) userInfo:nil repeats:YES];
Copy the code
Use a subclass of NSProxy as the mediator
Create a subclass of WeakProxy that inherits from NSProxy, set the target of timer as WeakProxy instance, implement the timing method in self by using the complete message forwarding mechanism, and solve the circular reference.
// WeakProxy.h
@property (nonatomic.weak.readonly) id weakTarget;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
// WeakProxy.m
@implementation WeakProxy
+ (instancetype)proxyWithTarget:(id)target {
return [[self alloc] initWithTarget:target];
}
- (instancetype)initWithTarget:(id)target {
_weakTarget = target;
return self;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = [invocation selector];
if ([self.weakTarget respondsToSelector:sel]) {
[invocation invokeWithTarget:self.weakTarget]; }} - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.weakTarget methodSignatureForSelector:sel];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.weakTarget respondsToSelector:aSelector];
}
@end
Copy the code
Then create a timer like this:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:[WeakProxy proxyWithTarget:self] selector:@selector(timeFire) userInfo:nil repeats:YES];
Copy the code
The circular holding chain looks like this:
Due to the weak reference relationship between WeakProxy and self, self can be destroyed eventually.
The timer with a block
After iOS 10, Apple provided a block approach to solve the problem of circular references.
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^) (NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
Copy the code
In order to be compatible with the methods before iOS 10, it can be written in the form of NSTimer classification. Block is passed into the initialization method as SEL, and the callback is processed in the form of block.
// NSTimer+WeakTimer.m
#import "NSTimer+WeakTimer.h"
@implementation NSTimer (WeakTimer)
+ (NSTimer *)ht_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void(^) (void))block {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(ht_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)ht_blockInvoke:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if(block) { block(); }}@end
Copy the code
Then create the Timer in the required class.
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer ht_scheduledTimerWithTimeInterval:1.0f repeats:YES block:^{
[weakSelf timeFire];
}];
Copy the code
Delegate pattern
Delegation pattern is a design pattern for communication between objects. The main idea of this pattern is to define a set of interfaces that an object must comply with in order to become its “delegate object” if it wants to accept delegates from another object.
The delegate of UITableView
The kind of tableView and ViewController that we use is the proxy and proxy relationship.
When we need to add a list to a controller, we usually set the tableView as a child view of the View in the ViewController. The UIViewController source code defines the view like this:
@property(null_resettable.nonatomic.strong) UIView *view;
Copy the code
So the ViewController strongly references the tableView. And the tableView delegates to the ViewController to implement several proxy methods and data source methods for it. If the dataSource and delegate properties are represented with strong, then the UITableView and ViewController are strongly referenced to each other, creating a circular reference.
If we look at the UITableView implementation source code, we can see that the dataSource and delegate attributes are defined as weak.
@property (nonatomic.weak.nullable) id <UITableViewDataSource> dataSource;
@property (nonatomic.weak.nullable) id <UITableViewDelegate> delegate;
Copy the code
So the dataSource and delegate of the tableView are just weak Pointers to the ViewController, and the relationship between them looks like this:
This avoids circular references.
The delegate NSURLSession
Does the delegate have to be weak?
Not necessarily. It depends on the situation. For example, the delegate in the NSURLSession class is decorated with retain.
@property (nullable.readonly.retain) id <NSURLSessionDelegate> delegate;
Copy the code
It does this to ensure that the delegate is not released until the network request is called back.
This also indirectly led to circular references in AFNetworking. The session declared in the AFURLSessionManager class is of type strong.
/** The managed session. */
@property (readonly.nonatomic.strong) NSURLSession *session;
Copy the code
When constructing the session object, we also set the delegate to self, which is the AFURLSessionManager class.
- (NSURLSession *)session {
@synchronized (self) {
if(! _session) { _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; }}return _session;
}
Copy the code
So the three formed such a circular holding relationship.
To solve this problem, there are two approaches:
Method 1: willAFHTTPSessionManager
Object is set to a singleton
For clients, most of the time it is the same backend service, so you can set the AFHTTPSessionManager object as a singleton to handle it.
- (AFHTTPSessionManager *)sharedManager {
static dispatch_once_t onceToken;
static AFHTTPSessionManager *_manager = nil;
dispatch_once(&onceToken, ^{
_manager = [AFHTTPSessionManager manager];
_manager.requestSerializer = [AFHTTPRequestSerializer serializer];
_manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json".@"text/html".@"text/json".@"text/plain".@"text/javascript".@"text/xml".nil];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
return _manager;
}
Copy the code
If you want to set a fixed request header, add this key-value to dispatch_once.
[_manager.requestSerializer setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
Copy the code
Disadvantages: Because the request header is by AFHTTPSessionManager requestSerializer mutableHTTPRequestHeaders dictionary holding, so the singleton pattern can lead to a global Shared a header, It can become cumbersome to handle requests for different custom headers.
Method 2: Manually destroy at the end of the requestsession
object
Because the Session object is strongly held to the delegate, to break the circular reference, you need to manually call the AFHTTPSessionManager object destruction method after the request ends.
- (AFHTTPSessionManager *)getSessionManager{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json".@"text/html".@"text/json".@"text/plain".@"text/javascript".@"text/xml".nil];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
return manager;
}
- (void)sendRequest{
AFHTTPSessionManager *manager = [self getSessionManager];
__weak typeof(manager)weakManager = manager;
[manager GET:@"https://blog.fiteen.top" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
__strong typeof (weakManager)strongManager = weakManager;
NSLog(@ "success callback." ");
[strongManager invalidateSessionCancelingTasks:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
__strong typeof (weakManager)strongManager = weakManager;
NSLog(@ "error correction");
[strongManager invalidateSessionCancelingTasks:YES];
}];
}
Copy the code
Non-oc object memory processing
Although the ARC mode is now common, it only does automatic memory management for OC objects. For non-OC objects, such as CI, CG, CF and other objects starting with the CoreFoundation framework, we still need to release them manually after using them.
For example, here is the code to get the UUID:
CFUUIDRef puuid = CFUUIDCreate( kCFAllocatorDefault );
CFStringRef uuidString = CFUUIDCreateString( kCFAllocatorDefault, puuid );
NSString *uuid = [(NSString *)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString)) uppercaseString];
// Release puuid and uuidString objects after use
CFRelease(puuid);
CFRelease(uuidString);
Copy the code
In C language, if malloc is used to dynamically allocate memory, you need to use free to release the memory, otherwise there will be a memory leak. Such as:
person *p = (person *)malloc(sizeof(person));
strcpy(p->name,"fiteen");
p->age = 18;
// Free the memory
free(p);
// Prevent wild Pointers
p = NULL;
Copy the code
Memory spikes are caused by cyclic loading
It doesn’t look like there is a memory leak, but in real time, the for loop creates a large number of temporary objects, causing a CPU explosion.
for (int i = 0; i < 1000000; i++) {
NSString *str = @"Abc";
str = [str lowercaseString];
str = [str stringByAppendingString:@"xyz"];
NSLog(@ "% @", str);
}
Copy the code
This is because a large number of temporary objects are created within the loop and are not released until the end of the loop, which can lead to memory leaks.
Solution:
Create your own AutoReleasepool in the loop torelease large temporary variables in time to reduce memory spikes.
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *str = @"Abc";
str = [str lowercaseString];
str = [str stringByAppendingString:@"xyz"];
NSLog(@ "% @", str); }}Copy the code
In the absence of a manual autorelease pool, the AutoRelease object is released at the end of the current Runloop iteration because the system destroys and recreates the autorelease pool in each runloop iteration.
As a special example, when using the block version of the container’s enumerator, an automatic release pool is added internally, such as:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// This is surrounded by a local @autoreleasepool
}];
Copy the code
Wild Pointers and zombie objects
A pointer to an object that has been freed/reclaimed is called a wild pointer. The freed object is the zombie object.
If you use wild Pointers to access zombie objects, or send messages to wild Pointers, EXC_BAD_ACCESS crashes and memory leaks occur.
/ / MRC
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
[stu setAge:18];
[stu release]; // after release, memory space is freed and reclaimed, and stu becomes wild pointer
// [stu setAge:20]; // Set will crash when setAge is called
}
return 0;
}
Copy the code
Solution: When an object is freed, it should be set to nil.
Memory leak checking tool
Instruments
Instruments is a collection of tools that come with Xcode, providing developers with powerful application performance analysis and testing capabilities.
It opens as Xcode → Open Developer Tool → Instruments. The Allocations, Leaks, and Zombies functions help with memory leak checks.
-
Leaks: Dynamically check leaked memory. If a Red Cross appears during the check, it indicates that there is a memory leak, and you can locate the leak to solve the problem. In addition, Xcode also provides a static monitoring method called Analyze, which can be opened directly through Product → Analyze and displays a “blue branch icon” if a leak occurs.
-
Allocations: Checks memory usage/allocation. For example, the situation of “memory peak caused by cyclic loading” can be checked out by this tool.
-
Zombies: Checks whether a zombie object is accessed.
The use of Instruments is relatively complex, and you can also detect it by introducing some third-party frameworks into your project.
MLeaksFinder
MLeaksFinder is the WeRead team’s open source iOS memory leak detection tool.
It is very simple to use, as long as the framework is introduced in the project, you can detect the memory leak object during the App running and immediately alert. MLeaksFinder is also non-invasive and does not need to be removed from the Release version when used, as it only works in the Debug version.
However, MLeaksFinder can only locate leaky objects if you want to check if the object has a circular reference. It is used together with FBRetainCycleDetector.
FBRetainCycleDetector
FBRetainCycleDetector is an open source circular reference detector tool from Facebook. It recursively traverses all strongly referenced objects of the OC object passed into memory, detecting for circular references in the strong reference tree with the object as the root.