iOS 内存管理
Posted 北渚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 内存管理相关的知识,希望对你有一定的参考价值。
一、Objective-C 使用引用计数来管理内存。
每个对象都有一个计数器,来表示引用该对象的个数;每次引用就加1,用完就减1;当计数为0时表示不再使用该对象,于是就销毁该对象。
多个对象之间的引用形成闭环会导致循环引用,从而不能够相互释放,造成内存泄漏。
二、ARC 自动引用计数
ARC 自动引用计数,把内存管理事宜交由编译器来处理。
使用 ARC 时,实际上引用计数还是在执行的,只不过保留操作和释放操作由 ARC 在编译时自动为你添加。
ARC 环境下特殊情况处理。
- (void)dealloc {
// 移除通知中心的监听者
// 移除 KVO 监听者
// 关闭 NSTimer,并将定时器置空(nil)
// 释放非 Objective-C 对象的内存,如 CFRelease(...), free(...)
}
三、自动释放池
@autoreleasepool {} 自动释放池。
当向一个对象发送autorelease消息时,系统会将该对象放入到最新的自动释放池。
在自动释放池的作用域内,池子内的对象依然可以正常使用。
当自动释放池的作用域结束时,再释放池子内的对象———每个对象收到几个autorelease就进行几次release操作。
常见用途:循环内部创建大量临时对象时,没有及时释放导致内存使用急剧增加,可以使用自动释放池在每次循环结束时释放对象。
for (NSInteger i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *str = @"Hello World";
str = [str stringByAppendingFormat:@"- %ld", i];
str = [str uppercaseString];
}
}
四、@property 属性修饰符
原子性(多线程管理):atomic、nonatomic
读写属性:readwrite、readonly
setter 语意:assign、retain、copy
强弱引用:strong、weak
1、NSNumber、NSString、Block 使用 copy。
2、IBOutlet 属性、引用“引用对象”的属性(控制器引用view的子视图)、代理属性使用 weak。
3、Class、id 类型的属性使用 retain。
4、NSString 属性使用 retain 时,可能赋值一个 NSMutableString,导致值为 NSMutableString 类型。
5、NSMutableString 属性使用 copy 时,导致属性值为 NSString 类型,有可能出现未识别方法调用的异常。
atomic 只是保证了读写的线程安全,但是在多线程环境访问对象属性时,访问结果不一定符合我们的预期。
- (void)setAtomicObj:(NSObject *)atomicObj{
@synchronized(self) {
if (_atomicObj != atomicObj) {
[_atomicObj release];
_atomicObj = [atomicObj retain];
}
}
}
- (NSObject *)atomicObj{
@synchronized(self) {
return _atomicObj;
}
}
ARC下同时重写getter、setter方法时,需使用关键字@synthesize 声明变量和属性的关系。
@synthesize name = _name;
注意:@property = ivar + getter + setter;
五、定时器使用时的内存管理
通常控制器会拥有一个定时器,如果把控制器设置为定时器的 target,定时器会引用控制器,导致定时器不关闭销毁时不能释放控制器。因此,想办法不要定时器直接或者间接引用控制器即可。
定时器解决循环引用思路:
1、ios10 以后系统提供了block 方法执行定时器操作。
[NSTimer timerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
}];
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval) repeats:(BOOL) block:^(NSTimer * _Nonnull timer) {
}];
2、给 NSTimer 提供一个分类,实现自定义的 block 方法。
@interface NSTimer (BBBlocksSupport)
+ (NSTimer *)bb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (BBBlocksSupport)
+ (NSTimer *)bb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(bb_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)bb_blockInvoke:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
3、中间件模式。
设计思路:给中间对象动态添加定时器需要执行的方法。
#import <Foundation/Foundation.h>
@interface BBTimerManager : NSObject
@property (weak, nonatomic) NSTimer *bb_timer;
+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
#import "BBTimerManager.h"
#import <objc/runtime.h>
@implementation BBTimerManager
+ (instancetype)bb_timerManagerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {
Method targetMethod = class_getInstanceMethod([aTarget class], aSelector);
if (!class_addMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod))) {
class_replaceMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
}
BBTimerManager *manager = [BBTimerManager new];
manager.bb_timer = [NSTimer scheduledTimerWithTimeInterval:interval target:manager selector:aSelector userInfo:userInfo repeats:repeats];
return manager;
}
@end
- (void)dealloc {
[_timerManager.bb_timer invalidate];
_timerManager.bb_timer = nil;
}
六、优雅使用 KVO
在需要处理属性变化事件的对象中添加观察者,并在该对象销毁时移除观察者。
@interface RootViewController ()
@property (retain, nonatomic) Focus *focus;
@end
@implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.focus = [[Focus alloc] init];
[self.focus addObserver:self
forKeyPath:@"number"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
self.focus.number = @2;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"number"]) {
NSLog(@"%@", change);
}
}
- (void)dealloc {
[self.focus removeObserver:self forKeyPath:@"number"];
}
七、copy 操作
对于不可变对象 NSString、NSArray、NSDictionary 表示引用加1。
对于可变对象 NSMutableString、NSMutableArray、NSMutableDictionary 表示复制对象,返回不可变对象。
mutableCopy 操作:
对于不可变对象、可变对象都是表示复制对象,返回可变对象。
自定义对象使用 copy 和 mutableCopy 需要遵守 NSCopying 和 NSMutableCopying 协议。
- (id)copyWithZone:(nullable NSZone *)zone;
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
八、weak 引用原理
runtime 维护了一个 weak 表,weak 表其实是一个 hash(哈希)表,其中 Key 是所指对象的地址,Value 是 weak 指针的地址数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
以上是关于iOS 内存管理的主要内容,如果未能解决你的问题,请参考以下文章