Since the introduction of ARC in iOS, general memory management has been taken off our hands, but some operations can still cause memory leaks if not carefully done.
This article mainly introduces the principle of memory leakage, common detection methods, common scenarios and modification methods.
1. Principle of memory leakage
The explanation of memory leak on Baidu is “the heap memory that has been dynamically allocated in the program is not released or cannot be released due to some reason, resulting in the waste of system memory, resulting in the program running speed slowing down or even system crash and other serious consequences”.
In my understanding, the company allocated a position to an employee who entered the company, but after the employee quit, the position could not be allocated to the next employee, resulting in a large amount of resource waste.
2. Conventional detection methods
2.1. Static analysis (Command + shift + B)
2.2. Dynamic analysis method (Instrument tool library Leaks, product-> Profile -> Leaks opens the main window of the tool, and the specific usage method can be referred to this article.
3. Memory leak scenario and analysis:
3.1. The following code shows the memory leak caused by the attribute keyword of the proxy being set to strong:
@protocol MFMemoryLeakViewDelegate <NSObject>
@end
@interface MFMemoryLeakView : UIView
@property (nonatomic, strong) id<MFMemoryLeakViewDelegate> delegate;
@end
Copy the code
MFMemoryLeakView *view = [[MFMemoryLeakView alloc] initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
Copy the code
As a result, the controller cannot be released, because the controller strongly references the view, and the view strongly references the view. As a result, the controller and the view are repeatedly referenced. Weak: weak: strong: weak: weak: strong: weak
@property (nonatomic, weak) id<MFMemoryLeakViewDelegate> delegate;
Copy the code
3.2 memory requested in CoreGraphics framework forgot to release
Take a look at the following code:
- (UIImage *)coreGraphicsMemoryLeak{
CGRect myImageRect = self.view.bounds;
CGImageRef imageRef = [UIImage imageNamed:@"MemoryLeakTip.jpeg"].CGImage;
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, myImageRect);
UIGraphicsBeginImageContext(myImageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, myImageRect, subImageRef);
UIImage *newImage = [UIImage imageWithCGImage:subImageRef];
CGImageRelease(subImageRef);
// CGImageRelease(imageRef);
UIGraphicsEndImageContext();
return newImage;
}
Copy the code
If the “CGImageRelease(subImageRef)” line is missing, it causes a memory leak that can be easily detected using static analysis.
Note: Release CGImageRef manually only if it uses create or retain. If it does not use retain, release CGImageRef manually. This is the case with the imageRef object above, which causes an indeterminate crash if manually released.
Why the collapse of uncertainty? One of the theories THAT I’m currently supporting is this: CFRelease cannot be NULL. If it is NULL, it will cause runtime errors and crash. The imageRef manager would have called release at some point, but because it has already been released, it is NULL. So when this call period comes, it crashes.
For this problem, you can use my demo to try, open the annotated code in the later picture and run, first enter the page of memory leak, then return to the higher level, enter this page again, the program crashes, the demo address see the bottom.
3.3. Memory allocated in CoreFoundation framework was forgotten to be released
Take a look at the following code:
- (NSString *)coreFoundationMemoryLeak{
CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
// NSString *uuid = (__bridge NSString *)uuid_string_ref;
NSString *uuid = (__bridge_transfer NSString *)uuid_string_ref;
CFRelease(uuid_ref);
// CFRelease(uuid_string_ref);
return uuid;
}
Copy the code
If the “CFRelease(uuid_ref)” line is missing, it will cause a memory leak that can be easily detected using static analysis.
Note that “__bridge” gives the CoreFoundation framework object ownership to the Foundation framework, but objects in the Foundation framework do not manage the object’s memory. “__bridge_Transfer” is to give The CoreFoundation framework object ownership to Foundation to manage. If the objects in Foundation are destroyed, then our previous objects (CoreFoundation) are destroyed together.
Therefore, __bridge_transfer will no longer have to manage memory manually. If “CFRelease(uuid_string_ref)” is commented in the code above, the UUID will be destroyed and the program will crash when it runs to reURN.
3.4 Memory leakage caused by incorrect use of NSTimer
3.4.1 Memory leaks will not occur when NSTimer is repeatedly set to NO
3.4.2 If NSTimer is repeatedly set to YES, memory leaks will not occur if invalidate is executed and memory leaks will occur if invalidate is not executed. Invalidate can also be invoked in the timer execution method.
3.4.3 Intermediate target: the controller cannot be released because the timer strongly references the controller. The timer created using the class method is added with runloop by default. Therefore, as long as the timer does not hold the controller, the controller can be released.
[NSTimer scheduledTimerWithTimeInterval:1 target:[MFTarget target:self] selector:@selector(timerActionOtherTarget:) userInfo:nil repeats:YES];
Copy the code
#import "MFTarget.h" @implementation MFTarget - (instancetype)initWithTarget:(id)target { _target = target; return self; } + (instancetype)target:(id)target { return [[MFTarget alloc] initWithTarget:target]; } / / here will go forward the selector to _target response - (id) forwardingTargetForSelector: (SEL) the selector {the if ([_target respondsToSelector:selector]) { return _target; } return nil; } - (void)forwardInvocation:(NSInvocation *)invocation { void *null = NULL; [invocation setReturnValue:&null]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { return [NSObject instanceMethodSignatureForSelector:@selector(init)]; }Copy the code
This does release the controller, but the timer method will continue to be called, if the performance requirements are not so strict, can use this method, see the code in the demo.
3.4.4 Rewrite NSTimer: Invalidate operation is performed inside timer according to the above middle target idea. Please take a look at the code.
@interface MFTimer : NSObject
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
@end
Copy the code
#import "MFTimer.h" @interface MFTimer () @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL selector; @property (nonatomic, weak) NSTimer *timer; @end @implementation MFTimer + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo { MFTimer *mfTimer = [[MFTimer alloc] init]; mfTimer.timer = [NSTimer timerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo]; mfTimer.target = aTarget; mfTimer.selector = aSelector; return mfTimer.timer; } + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo { MFTimer *mfTimer = [[MFTimer alloc] init]; mfTimer.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo]; mfTimer.target = aTarget; mfTimer.selector = aSelector; return mfTimer.timer; } - (void)timerAction:(NSTimer *)timer { if (self.target) { #pragma clang diagnostic push #pragma clang diagnostic Ignored - Warc - performSelector - "leaks" / / do not determine whether the response, so as not to realize timer method error [self. The target performSelector: self. The selector withObject:timer]; #pragma clang diagnostic pop }else { [self.timer invalidate]; self.timer = nil; } } @endCopy the code
3.4.5 When using blocks to create timers, block should be used correctly and invalidate should be executed, otherwise memory leakage will occur. There are memory leaks involved in blocks, which I’ll cover in the next article.
Other memory leaks such as notifications and memory leaks caused by KVO, block loop references, and NSThreads are described in the next section.
Click here for demo address