MMKV--源碼學(xué)習(xí)

kv數(shù)據(jù)持久化需要的功能

假設(shè)要設(shè)計(jì)一個(gè)kv的存儲(chǔ)功能:

  1. 首先是可靠性,在各種情況下能夠?qū)v保存
  2. 性能的要求旷祸,當(dāng)時(shí)是越快越好,存儲(chǔ)占用的越少越好

MMKV號(hào)稱滿足這些特性:

  1. 可靠,實(shí)時(shí)寫入
  2. 高性能

如果撇去高可靠性疯攒,可以采取內(nèi)存緩沖的模式,例如先存入dic列荔,然后在合適的時(shí)間同步到文件敬尺。這種方式考慮同步的時(shí)機(jī)是一方面,而且在crash時(shí)可能dic未同步到文件贴浙。

如果撇如高性能砂吞,可以采用直接的讀寫文件,例如采用增量式的編碼悬而,將kv寫入文件呜舒,面臨的問題也很明顯,就是頻繁的磁盤io,效率是很低的袭蝗。

MMKV的設(shè)計(jì)

MMKV 設(shè)計(jì)

在內(nèi)存映射后唤殴,操作文件使用指針就可以完成,文件與映射區(qū)的同步由內(nèi)核完成到腥,MMKV維護(hù)著一個(gè)<String, AnyObject>的dic朵逝,在寫時(shí)同時(shí)寫入dic和映射區(qū),也就是同時(shí)寫入dic和文件乡范,所以dic和持久化的數(shù)據(jù)是同步的配名,既然是同步的,所以讀時(shí)直接取dic中的值就好了晋辆。

下面對(duì)基本流程的總結(jié):

  1. 內(nèi)存映射 mmap
  2. crc校驗(yàn)
  3. aes加密
  4. 線程安全
  5. 內(nèi)存警告

mmap

有關(guān)mmap相關(guān)的知識(shí)和使用可以看這里渠脉。對(duì)于常用kv存儲(chǔ)來說,兼顧性能和可靠性

所以由mmap的相關(guān)知識(shí)和MMKV的設(shè)計(jì)可以猜想瓶佳,MMKV使用mmap要做什么事情:

  1. 映射文件到內(nèi)存芋膘,保存映射區(qū)的指針,方便寫操作(定義了MiniCodedOutputData實(shí)現(xiàn)了對(duì)data按字節(jié)拷貝到指定區(qū)域內(nèi)存)
  2. 從映射區(qū)為dic初始化霸饲,方便讀操作

mmap在MMKV中的使用:

//  MMKV.mm

- (void)loadFromFile {
    m_fd = open(m_path.UTF8String, O_RDWR, S_IRWXU);    // open  得到文件描述符m_fd
    if (m_fd < 0) {
        MMKVError(@"fail to open:%@, %s", m_path, strerror(errno));
    } else {
        m_size = 0;
        struct stat st = {};
        if (fstat(m_fd, &st) != -1) {
            m_size = (size_t) st.st_size;   // 獲取文件大小为朋,為按頁對(duì)齊做準(zhǔn)備
        }
        // round up to (n * pagesize)  按頁對(duì)齊
        if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) {
            m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
            if (ftruncate(m_fd, m_size) != 0) { //  按頁對(duì)齊
                MMKVError(@"fail to truncate [%@] to size %zu, %s", m_mmapID, m_size, strerror(errno));
                m_size = (size_t) st.st_size;
                return;
            }
        }
        //  1: 映射內(nèi)存,獲取內(nèi)存中的指針m_ptr
        m_ptr = (char *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);    
        if (m_ptr == MAP_FAILED) {
            MMKVError(@"fail to mmap [%@], %s", m_mmapID, strerror(errno));
        } else {    
            const int offset = pbFixed32Size(0);
            NSData *lenBuffer = [NSData dataWithBytesNoCopy:m_ptr length:offset freeWhenDone:NO];
            @try {
            // 文件中真正使用的空間有多大厚脉,因?yàn)槲募话错搶?duì)齊后习寸,真正使用的空間清楚,所以在文件開始做了記錄
                m_actualSize = MiniCodedInputData(lenBuffer).readFixed32(); 
            } @catch (NSException *exception) {
                MMKVError(@"%@", exception);
            }
            MMKVInfo(@"loading [%@] with %zu size in total, file size is %zu", m_mmapID, m_actualSize, m_size);
            if (m_actualSize > 0) { // 當(dāng)文件中有記錄時(shí)傻工,如果第一次使用或是已經(jīng)清理過霞溪,實(shí)際使用空間將為0
                bool loadFromFile, needFullWriteback = false;
                if (m_actualSize < m_size && m_actualSize + offset <= m_size) { // 檢查文件是否正常
                    if ([self checkFileCRCValid] == YES) {  
                        loadFromFile = true;
                    } else {    // 校驗(yàn)失敗后的行為
                        loadFromFile = false;
                        if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) {
                            auto strategic = [g_callbackHandler onMMKVCRCCheckFail:m_mmapID];
                            if (strategic == MMKVOnErrorRecover) {  // 如果校驗(yàn)失敗后要繼續(xù)使用
                                loadFromFile = true;    
                                needFullWriteback = true;
                            }
                        }
                    }
                } else {    // 根據(jù)文件中記錄,文件不正常
                    MMKVError(@"load [%@] error: %zu size in total, file size is %zu", m_mmapID, m_actualSize, m_size);
                    loadFromFile = false;
                    if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
                        auto strategic = [g_callbackHandler onMMKVFileLengthError:m_mmapID];
                        if (strategic == MMKVOnErrorRecover) {  // 文件不正常后要繼續(xù)使用
                            loadFromFile = true;
                            needFullWriteback = true;
                            [self writeAcutalSize:m_size - offset]; // 重新記錄下文件的相關(guān)信息
                        }
                    }
                }
                if (loadFromFile) { // 假定文件是正常的精钮,從文件中讀取
                    NSData *inputBuffer = [NSData dataWithBytesNoCopy:m_ptr + offset length:m_actualSize freeWhenDone:NO];
                    if (m_cryptor) {
                        inputBuffer = decryptBuffer(*m_cryptor, inputBuffer);
                    }
                    // 2. 初始化m_dic
                    //  如果文件存在錯(cuò)誤(例如crc校驗(yàn)不通過)威鹿,會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)誤或是丟失
                    m_dic = [MiniPBCoder decodeContainerOfClass:NSMutableDictionary.class withValueClass:NSData.class fromData:inputBuffer];
                    //  定位到文件尾部
                    m_output = new MiniCodedOutputData(m_ptr + offset + m_actualSize, m_size - offset - m_actualSize);
                    // 如果文件存在錯(cuò)誤,decode到m_dic過程中可能會(huì)丟棄部分?jǐn)?shù)據(jù)轨香,所以要將m_dic忽你,保證m_dic與文件的同步
                    if (needFullWriteback) {    
                        [self fullWriteback];
                    }
                } else {    // 文件不正常且不打算恢復(fù),需要重建臂容,丟棄原來的數(shù)據(jù)
                    [self writeAcutalSize:0];
                    m_output = new MiniCodedOutputData(m_ptr + offset, m_size - offset);
                    [self recaculateCRCDigest];
                }
            } else {    //  文件中沒有kv科雳,沒有必要讀入dic
                m_output = new MiniCodedOutputData(m_ptr + offset, m_size - offset);
                [self recaculateCRCDigest];
            }
            MMKVInfo(@"loaded [%@] with %zu values", m_mmapID, (unsigned long) m_dic.count);
        }
    }
    if (m_dic == nil) {
        m_dic = [NSMutableDictionary dictionary];
    }

    
    if (![self isFileValid]) { 
        MMKVWarning(@"[%@] file not valid", m_mmapID);
    }

    // 修改文件的屬性
    tryResetFileProtection(m_path);
    tryResetFileProtection(m_crcPath);
    m_needLoadFromFile = NO;
}

寫入

為了保證性能,采用增量寫入的方式脓杉,這需要編解碼支持糟秘,MMKV的實(shí)現(xiàn)了一套增量編解碼方案。增量編碼是基于性能的考慮球散,不用將m_dic中的數(shù)據(jù)全部寫回尿赚。

所以寫入要實(shí)現(xiàn)的功能:

  1. 將kv寫入m_dic
  2. 檢查文件剩余空間是否夠,不夠的話按照一定的策略分配(分配策略會(huì)選擇犧牲少量磁盤空間換取效率,并且會(huì)整理kv防止占用過大存儲(chǔ)空間)凌净,將kv寫入內(nèi)存映射區(qū)域悲龟,保持兩者同步

在iOS的中,當(dāng)app進(jìn)入后臺(tái)后冰寻,內(nèi)存可能會(huì)被swap出须教,提供給活躍的app,這樣會(huì)降低效率(因?yàn)橐贀Q進(jìn)內(nèi)存呀)斩芭,MMKV提供了后臺(tái)寫保護(hù)的功能(基于性能考慮):

// MMKV.mm

/// 提供對(duì)映射內(nèi)存的保護(hù)轻腺,防止被系統(tǒng)交換

- (BOOL)protectFromBackgroundWritting:(size_t)size writeBlock:(void (^)(MiniCodedOutputData *output))block {
    if (m_isInBackground) { // 如果在后臺(tái),鎖定要寫入的內(nèi)存划乖,防止被換出贬养,影響效率
        // 因?yàn)閙lock的offset是以頁為單位的,所以要計(jì)算鎖定的頁偏移
        static const int offset = pbFixed32Size(0);
        static const int pagesize = getpagesize();
        size_t realOffset = offset + m_actualSize - size;
        size_t pageOffset = (realOffset / pagesize) * pagesize;
        size_t pointerOffset = realOffset - pageOffset;
        size_t mmapSize = offset + m_actualSize - pageOffset;
        char *ptr = m_ptr + pageOffset;
        // 鎖定要寫入的內(nèi)存區(qū)域
        if (mlock(ptr, mmapSize) != 0) {
            MMKVError(@"fail to mlock [%@], %s", m_mmapID, strerror(errno));
            // just fail on this condition, otherwise app will crash anyway
            //block(m_output);
            return NO;
        } else {
            @try {
                MiniCodedOutputData output(ptr + pointerOffset, size);
                block(&output);
                m_output->seek(size);
            } @catch (NSException *exception) {
                MMKVError(@"%@", exception);
                return NO;
            } @finally {
                munlock(ptr, mmapSize);
            }
        }
    } else {
        block(m_output);    // 未在后臺(tái)琴庵,不需要鎖定
    }

    return YES;
}
// MMKV.mm 
// 檢查文件剩余空間是否夠煤蚌,不夠的話按照一定的策略分配

- (BOOL)ensureMemorySize:(size_t)newSize {
    [self checkLoadData];

    if (![self isFileValid]) {
        MMKVWarning(@"[%@] file not valid", m_mmapID);
        return NO;
    }

    if (newSize >= m_output->spaceLeft()) {
        // try a full rewrite to make space
        static const int offset = pbFixed32Size(0);
        NSData *data = [MiniPBCoder encodeDataWithObject:m_dic];
        size_t lenNeeded = data.length + offset + newSize;
        size_t avgItemSize = lenNeeded / std::max<size_t>(1, m_dic.count);
        // 將要使用的空間,持續(xù)擴(kuò)容一半直到足夠细卧,并在擴(kuò)容后,重新映射
        size_t futureUsage = avgItemSize * std::max<size_t>(8, m_dic.count / 2);
        // 1. no space for a full rewrite, double it
        // 2. or space is not large enough for future usage, double it to avoid frequently full rewrite
        if (lenNeeded >= m_size || (lenNeeded + futureUsage) >= m_size) {
            size_t oldSize = m_size;
            do {
                m_size *= 2;
            } while (lenNeeded + futureUsage >= m_size);
            MMKVInfo(@"extending [%@] file size from %zu to %zu, incoming size:%zu, futrue usage:%zu",
                     m_mmapID, oldSize, m_size, newSize, futureUsage);

            // if we can't extend size, rollback to old state
            if (ftruncate(m_fd, m_size) != 0) { //  擴(kuò)充文件
                MMKVError(@"fail to truncate [%@] to size %zu, %s", m_mmapID, m_size, strerror(errno));
                m_size = oldSize;
                return NO;
            }
            
            // 文件大小變了筒占,所以要重新映射贪庙,先關(guān)閉原來的
            if (munmap(m_ptr, oldSize) != 0) {
                MMKVError(@"fail to munmap [%@], %s", m_mmapID, strerror(errno));
            }
            
            // 從老位置開始映射,因?yàn)榭赡芟到y(tǒng)沒把這塊內(nèi)存分配出去翰苫,可能效率會(huì)高一些,沒有找到證明此寫法的詳細(xì)資料
            m_ptr = (char *) mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
            if (m_ptr == MAP_FAILED) {
                MMKVError(@"fail to mmap [%@], %s", m_mmapID, strerror(errno));
            }

            // check if we fail to make more space
            if (![self isFileValid]) {
                MMKVWarning(@"[%@] file not valid", m_mmapID);
                return NO;
            }
            // keep m_output consistent with m_ptr -- writeAcutalSize: may fail
            delete m_output;
            m_output = new MiniCodedOutputData(m_ptr + offset, m_size - offset);
            m_output->seek(m_actualSize);
        }

        // 加密
        if (m_cryptor) {
            m_cryptor->reset();
            auto ptr = (unsigned char *) data.bytes;
            m_cryptor->encrypt(ptr, ptr, data.length);
        }

        if ([self writeAcutalSize:data.length] == NO) {
            return NO;
        }

        delete m_output;
        m_output = new MiniCodedOutputData(m_ptr + offset, m_size - offset);
        BOOL ret = [self protectFromBackgroundWritting:m_actualSize // 全量寫回,實(shí)現(xiàn)kv的排重
                                            writeBlock:^(MiniCodedOutputData *output) {
                                                output->writeRawData(data);
                                            }];
        if (ret) {
            [self recaculateCRCDigest];
        }
        return ret;
    }
    return YES;
}

// MMKV.mm 
// 2. 檢查文件剩余空間是否夠廊移,不夠的話按照一定的策略分配笋粟,將kv寫入內(nèi)存映射區(qū)域,保持兩者同步
- (BOOL)appendData:(NSData *)data forKey:(NSString *)key {
    size_t keyLength = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    size_t size = keyLength + pbRawVarint32Size((int32_t) keyLength); // size needed to encode the key
    size += data.length + pbRawVarint32Size((int32_t) data.length);   // size needed to encode the value

    BOOL hasEnoughSize = [self ensureMemorySize:size];
    if (hasEnoughSize == NO || [self isFileValid] == NO) {
        return NO;
    }
    // 文件是空的埃唯,全量寫入撩匕,case與編碼方式相關(guān)
    if (m_actualSize == 0) {
        NSData *allData = [MiniPBCoder encodeDataWithObject:m_dic];
        if (allData.length > 0) {
            if (m_cryptor) {
                m_cryptor->reset();
                auto ptr = (unsigned char *) allData.bytes;
                m_cryptor->encrypt(ptr, ptr, allData.length);
            }
            BOOL ret = [self writeAcutalSize:allData.length];
            if (ret) {
                ret = [self protectFromBackgroundWritting:m_actualSize
                                               writeBlock:^(MiniCodedOutputData *output) {
                                                   output->writeRawData(allData); // note: don't write size of data
                                               }];
                if (ret) {
                    [self recaculateCRCDigest];
                }
            }
            return ret;
        }
        return NO;
    } else {    // case與編碼方式相關(guān),增量寫入
        BOOL ret = [self writeAcutalSize:m_actualSize + size];
        if (ret) {
            static const int offset = pbFixed32Size(0);
            ret = [self protectFromBackgroundWritting:size
                                           writeBlock:^(MiniCodedOutputData *output) {
                                               output->writeString(key);
                                               output->writeData(data); // note: write size of data
                                           }];
            if (ret) {
                auto ptr = (uint8_t *) m_ptr + offset + m_actualSize - size;
                if (m_cryptor) {    // 這里是在寫入內(nèi)存映射區(qū)后才做的加密墨叛,因?yàn)閷懭氲膁ata加入了其他需要的bit(data長度)
                    m_cryptor->encrypt(ptr, ptr, size);
                }
                [self updateCRCDigest:ptr withSize:size];
            }
        }
        return ret;
    }
}
// MMKV.mm 
// 寫入方法
- (BOOL)setRawData:(NSData *)data forKey:(NSString *)key {
    if (data.length <= 0 || key.length <= 0) {
        return NO;
    }
    CScopedLock lock(m_lock);

    [m_dic setObject:data forKey:key];  // 1. 寫入m_dic
    m_hasFullWriteBack = NO;

    return [self appendData:data forKey:key];   // 2. 寫入文件
}

讀:

因?yàn)閙_dic已經(jīng)保證與文件同步了止毕,所以直接讀m_dic就可以了,在需要讀數(shù)據(jù)時(shí)進(jìn)行解碼漠趁,所以在讀時(shí)是要明確知道數(shù)據(jù)類型扁凛,如果搞錯(cuò)了,行為是不確定的

// MMKV.mm

// 從m_dic中獲取value(NSData類型)
- (NSData *)getRawDataForKey:(NSString *)key {
    CScopedLock lock(m_lock);
    [self checkLoadData];
    return [m_dic objectForKey:key];
}

- (id)getObjectOfClass:(Class)cls forKey:(NSString *)key {
    if (key.length <= 0) {
        return nil;
    }
    NSData *data = [self getRawDataForKey:key]; // 從獲取data
    if (data.length > 0) {  // 解碼, 支持NSObject<NSCoding>的類型和自定義解碼器支持的類型

        if ([MiniPBCoder isMiniPBCoderCompatibleType:cls]) {
            return [MiniPBCoder decodeObjectOfClass:cls fromData:data]; 
        } else {
            if ([cls conformsToProtocol:@protocol(NSCoding)]) {
                return [NSKeyedUnarchiver unarchiveObjectWithData:data];
            }
        }
    }
    return nil;
}

crc校驗(yàn)

對(duì)于大文件的寫入闯传,可能發(fā)生錯(cuò)誤的幾率較大谨朝,所以對(duì)保存kv的文件使用crc32進(jìn)行校驗(yàn)(可靠性),產(chǎn)生crc碼也需要保存,但是因?yàn)閏rc比較小字币,所以發(fā)生錯(cuò)誤的幾率是比較小的则披,如果crc文件也要校驗(yàn),那就是個(gè)無盡的循環(huán)了纬朝。在每次映射結(jié)束后都會(huì)做crc校驗(yàn)收叶。每次寫入時(shí)要更新crc碼。crc碼的更新方式有兩種:

  1. 重新計(jì)算全部數(shù)據(jù)的crc碼
  2. 做增量的crc碼計(jì)算
// MMKV
- (BOOL)checkFileCRCValid {
    if (m_ptr != nullptr && m_ptr != MAP_FAILED) {
        int offset = pbFixed32Size(0);
        m_crcDigest = (uint32_t) crc32(0, (const uint8_t *) m_ptr + offset, (uint32_t) m_actualSize);   // 獲取文件的crc碼

        // for backward compatibility
        if (!isFileExist(m_crcPath)) {
            MMKVInfo(@"crc32 file not found:%@", m_crcPath);
            return YES;
        }
        NSData *oData = [NSData dataWithContentsOfFile:m_crcPath];
        uint32_t crc32 = 0;
        @try {
            MiniCodedInputData input(oData);
            crc32 = input.readFixed32();    // 獲取已經(jīng)記錄的crc碼
        } @catch (NSException *exception) {
            MMKVError(@"%@", exception);
        }
        if (m_crcDigest == crc32) {
            return YES; // 校驗(yàn)通過
        }
        MMKVError(@"check crc [%@] fail, crc32:%u, m_crcDigest:%u", m_mmapID, crc32, m_crcDigest);
    }
    return NO;
}
// MMKV.mm
// 通過增量更新crc碼
- (void)updateCRCDigest:(const uint8_t *)ptr withSize:(size_t)length {
    if (ptr == nullptr) {
        return;
    }
    // 將原來crc碼傳入共苛,進(jìn)行增量的crc碼計(jì)算判没,第一個(gè)參數(shù)是原來的crc碼,如果原來的crc碼為0隅茎,則相當(dāng)于全量
    m_crcDigest = (uint32_t) crc32(m_crcDigest, ptr, (uint32_t) length);    

    if (m_crcPtr == nullptr || m_crcPtr == MAP_FAILED) {
        [self prepareCRCFile];
    }
    if (m_crcPtr == nullptr || m_crcPtr == MAP_FAILED) {
        return;
    }

    static const size_t bufferLength = pbFixed32Size(0);
    if (m_isInBackground) {
        if (mlock(m_crcPtr, bufferLength) != 0) {
            MMKVError(@"fail to mlock crc [%@]-%p, %d:%s", m_mmapID, m_crcPtr, errno, strerror(errno));
            // just fail on this condition, otherwise app will crash anyway
            return;
        }
    }

    @try {
        MiniCodedOutputData output(m_crcPtr, bufferLength);
        output.writeFixed32((int32_t) m_crcDigest);
    } @catch (NSException *exception) {
        MMKVError(@"%@", exception);
    }
    if (m_isInBackground) {
        munlock(m_crcPtr, bufferLength);
    }
}

aes加密

MMKV 使用了 AES CFB-128 算法來加密/解密澄峰。具體是采用了 OpenSSL(1.1.0i 版)的實(shí)現(xiàn)。我們選擇 CFB 而不是常見的 CBC 算法辟犀,主要是因?yàn)?MMKV 使用 append-only 實(shí)現(xiàn)插入/更新操作俏竞,流式加密算法更加合適。-- 摘自MMKV github wiki

線程安全

MMKV是線程安全的

MMKV使用c++的類初始化和析構(gòu)的特性定義了ScopedLock(作用域鎖):

class CScopedLock {
    NSRecursiveLock *m_oLock;

public:
    CScopedLock(NSRecursiveLock *oLock) : m_oLock(oLock) { [m_oLock lock]; }    // 初始化時(shí)加鎖

    ~CScopedLock() {    // 析構(gòu)時(shí)解鎖
        [m_oLock unlock];
        m_oLock = nil;
    }
};

/*
{
    CScopedLock lock(g_instanceLock);
    操作臨界資源堂竟。魂毁。。
} 超出作用域出嘹,調(diào)用lock的析構(gòu)函數(shù)席楚,解鎖
*/

使用了NSRecursiveLock進(jìn)行加鎖,這降低了死鎖的風(fēng)險(xiǎn)税稼,但是對(duì)性能會(huì)有少量的消耗

  1. 對(duì)于每個(gè)mmkv實(shí)例都會(huì)放入一個(gè)global的dic保存(緩存)烦秩,來避免每次都要走做初始化,并且為該對(duì)象添加了強(qiáng)引用郎仆,防止被釋放只祠,并添加了g_instanceLock鎖來保障,每次dic進(jìn)行寫操作時(shí)扰肌,加鎖保護(hù)抛寝,而且保證初始化行為線程安全
  2. 在mmkv中的實(shí)例變量是臨界資源,所以每次都要加鎖狡耻,這里需要注意的是在多線程的其情況下墩剖,close之后再使用,其行為是不確定的夷狰,原因如下:
// call this method if the instance is no longer needed in the near future
// any subsequent call to the instance is undefined behavior
- (void)close;

- (void)close {
    CScopedLock g_lock(g_instanceLock);
    CScopedLock lock(m_lock);
    MMKVInfo(@"closing %@", m_mmapID);

    [self clearMemoryCache];

    // 這里從dic中移除了該實(shí)例岭皂,所以引用計(jì)數(shù)會(huì)-1,不同線程有不同autoreleasepool沼头,所以可能被釋放
    [g_instanceDic removeObjectForKey:m_mmapID];    
}

內(nèi)存警告

因?yàn)閮?nèi)存過高肯能會(huì)OOM爷绘,而且會(huì)降低app運(yùn)行速度(內(nèi)存交換)书劝,所以在內(nèi)存警告時(shí)對(duì)內(nèi)存釋放

// MMKV

// 主要是兩個(gè)工作:1. 清理內(nèi)存中m_dic 2. 關(guān)閉映射
- (void)clearMemoryCache {
    CScopedLock lock(m_lock);

    if (m_needLoadFromFile) {
        MMKVInfo(@"ignore %@", m_mmapID);
        return;
    }
    m_needLoadFromFile = YES;

    [m_dic removeAllObjects];   // 清理m_dic
    m_hasFullWriteBack = NO;

    if (m_output != nullptr) {
        delete m_output;
    }
    m_output = nullptr;

    if (m_ptr != nullptr && m_ptr != MAP_FAILED) {
        if (munmap(m_ptr, m_size) != 0) {   // 關(guān)閉映射
            MMKVError(@"fail to munmap [%@], %s", m_mmapID, strerror(errno));
        }
    }
    m_ptr = nullptr;

    if (m_fd >= 0) {
        if (close(m_fd) != 0) { // 關(guān)閉文件
            MMKVError(@"fail to close [%@], %s", m_mmapID, strerror(errno));
        }
    }
    m_fd = -1;
    m_size = 0;
    m_actualSize = 0;

    if (m_cryptor) {
        m_cryptor->reset();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市土至,隨后出現(xiàn)的幾起案子购对,更是在濱河造成了極大的恐慌,老刑警劉巖陶因,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骡苞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡楷扬,警方通過查閱死者的電腦和手機(jī)解幽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烘苹,“玉大人躲株,你說我怎么就攤上這事×秃猓” “怎么了霜定?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長廊鸥。 經(jīng)常有香客問我望浩,道長,這世上最難降的妖魔是什么惰说? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任曾雕,我火速辦了婚禮,結(jié)果婚禮上助被,老公的妹妹穿的比我還像新娘。我一直安慰自己切诀,他們只是感情好揩环,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幅虑,像睡著了一般丰滑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒庵,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天褒墨,我揣著相機(jī)與錄音,去河邊找鬼擎宝。 笑死郁妈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绍申。 我是一名探鬼主播噩咪,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顾彰,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了胃碾?” 一聲冷哼從身側(cè)響起涨享,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仆百,沒想到半個(gè)月后厕隧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俄周,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年吁讨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栈源。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挡爵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甚垦,到底是詐尸還是另有隱情茶鹃,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布艰亮,位于F島的核電站闭翩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迄埃。R本人自食惡果不足惜疗韵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侄非。 院中可真熱鬧蕉汪,春花似錦、人聲如沸逞怨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叠赦。三九已至驹马,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間除秀,已是汗流浹背糯累。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留册踩,地道東北人泳姐。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像暂吉,于是被迫代替她去往敵國和親仗岸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子允耿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 最近了解到微信開源了一個(gè)高性能的key-value組件MMKV, 想寫一篇文章做一下記錄. MMKV是基于mmap...
    Lin__Chuan閱讀 958評(píng)論 0 1
  • 1. 硬鏈接和軟連接區(qū)別 硬連接-------指通過索引節(jié)點(diǎn)來進(jìn)行連接。在Linux的文件系統(tǒng)中扒怖,保存在磁盤分區(qū)...
    杰倫哎呦哎呦閱讀 2,262評(píng)論 0 2
  • 本文轉(zhuǎn)自微信開發(fā)團(tuán)隊(duì)凌國的分享较锡。原文 MMKV 是基于 mmap 內(nèi)存映射的移動(dòng)端通用 key-value 組件,...
    那樣風(fēng)采閱讀 42,368評(píng)論 6 35
  • UNIX網(wǎng)絡(luò)編程第二卷進(jìn)程間通信對(duì)mmap函數(shù)進(jìn)行了說明盗痒。該函數(shù)主要用途有三個(gè):1蚂蕴、將一個(gè)普通文件映射到內(nèi)存中,通...
    宇文黎琴閱讀 3,514評(píng)論 0 4
  • 今天看到一位朋友寫的mysql筆記總結(jié)俯邓,覺得寫的很詳細(xì)很用心骡楼,這里轉(zhuǎn)載一下,供大家參考下稽鞭,也希望大家能關(guān)注他原文地...
    信仰與初衷閱讀 4,736評(píng)論 0 30