1. 程式人生 > 其它 >iOS 記憶體管理

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表中刪除,最後清理物件的記錄。