天天看點

NSCache記憶體淘汰政策分析

NSCache記憶體淘汰政策分析

      • 1 NSCache功能
      • 2 分析前
      • 3 分析後
      • 4 參考
        • (1) NSCache的swift版源碼
        • (2) NSCache的OC版逆向

1 NSCache功能

  • 儲存key-value對,可增删查;
  • 記憶體吃緊時,可自動移除部分key-value對;

2 分析前

蘋果文檔裡說:

The NSCache class incorporates various auto-eviction policies, which ensure that a cache 
doesn’t use too much of the system’s memory. If memory is needed by other applications, 
these policies remove some items from the cache, minimizing its memory footprint.
           

NSCache本來很簡單的類,就這裡記憶體吃緊時的淘汰政策有些疑問。網上有很多文章說記憶體不夠時,自動淘汰,貌似會收到系統記憶體warning通知,然後自動怎麼怎麼。。。感覺很智能。。。

3 分析後

但是分析後發現:

1 淘汰政策隻是NSCache自身邏輯,和記憶體warning之類無關;

2 淘汰政策依賴totalCostLimit和countLimit成員變量的設定,預設沒有開啟;

4 參考

(1) NSCache的swift版源碼

https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/NSCache.swift

(2) NSCache的OC版逆向

/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation庫逆向後,NSCache類隻是很薄的封裝,裡面都是調用的C實作,也沒有處理記憶體warning什麼的。

void * -[NSCache init](void * self, void * _cmd) {
    rbx = self;
    var_60 = 0x2;
    intrinsic_movdqu(var_40, intrinsic_punpcklqdq(zero_extend_64(___NSCacheKeyRelease), zero_extend_64(___NSCacheValueRelease)));
    var_10 = 0x0;
    if (cache_create("", &var_60, &var_10) != 0x0) {
            [rbx release];
            rbx = 0x0;
    }
    else {
            *(rbx + *ivar_offset(_private) + 0x8) = var_10;
            rbx->_private = 0x0;
            *(rbx + *ivar_offset(_private) + 0x10) = 0x1;
    }
    rax = rbx;
    return rax;
}
           
void -[NSCache setObject:forKey:cost:](void * self, void * _cmd, void * arg2, void * arg3, unsigned long long arg4) {
    r12 = arg4;
    r14 = arg3;
    r15 = arg2;
    rbx = _cmd;
    r13 = self;
    ___NSCheckReentrancy(self, _cmd);
    if (r14 != 0x0) {
            if (r15 != 0x0) {
                    rbx = *ivar_offset(_private);
                    COND = (*(int8_t *)(r13 + rbx + 0x10) & 0x1) != 0x0;
                    rax = *(r13 + rbx + 0x20);
                    if (!COND) {
                            if (rax == 0xffffffffffffffff) {
                                    *(r13 + rbx + 0x20) = 0x2;
                            }
                    }
                    else {
                            if (rax != 0xffffffffffffffff) {
                                    if (rax != 0x1) {
                                            if (rax == 0x0) {
                                                    rax = [r15 conformsToProtocol:@protocol(NSDiscardableContent)];
                                                    CMP(rax, 0x1);
                                                    *(r13 + rbx + 0x20) = !(rax - rax + CARRY(RFLAGS(cf))) | 0x1;
                                            }
                                    }
                                    else {
                                            if ([r15 conformsToProtocol:@protocol(NSDiscardableContent)] != 0x0) {
                                                    *(r13 + rbx + 0x20) = 0x2;
                                            }
                                    }
                            }
                            else {
                                    if ([r15 conformsToProtocol:@protocol(NSDiscardableContent)] == 0x0) {
                                            *(r13 + rbx + 0x20) = 0x2;
                                    }
                            }
                    }
                    if (cache_set_and_retain(*(r13 + rbx + 0x8), r14, r15, r12) == 0x0) {
                            cache_release_value(*(r13 + rbx + 0x8), r15);
                    }
            }
            else {
                    ___CFExceptionProem(r13, rbx);
                    _CFStringCreateWithFormat(*_kCFAllocatorSystemDefault, 0x0, @"%@: attempt to insert nil value (key: %@)");
                    objc_exception_throw([NSException exceptionWithName:*_NSInvalidArgumentException reason:__CFAutoreleasePoolAddObject() userInfo:0x0]);
            }
    }
    return;
}
           
void -[NSCache removeObjectForKey:](void * self, void * _cmd, void * arg2) {
    r14 = arg2;
    rbx = self;
    ___NSCheckReentrancy(self, _cmd);
    if (r14 != 0x0) {
            cache_remove(*(rbx + *ivar_offset(_private) + 0x8), r14);
    }
    return;
}
           
void -[NSCache setCountLimit:](void * self, void * _cmd, unsigned long long arg2) {
    ___NSCheckReentrancy(self, _cmd);
    cache_set_count_hint(*(self + *ivar_offset(_private) + 0x8), arg2);
    return;
}
           
void -[NSCache setTotalCostLimit:](void * self, void * _cmd, unsigned long long arg2) {
    ___NSCheckReentrancy(self, _cmd);
    cache_set_cost_hint(*(self + *ivar_offset(_private) + 0x8), arg2);
    return;
}