MMKV-编码和解码

MMKV实现了一套编解码方法,除了引用了protocolbuf对基本类型的编码外,也实现了一些对OC类型的编码。

编码的类型的结构

enum MiniPBEncodeItemType {
    PBEncodeItemType_None,
    PBEncodeItemType_NSString,
    PBEncodeItemType_NSData,
    PBEncodeItemType_NSDate,
    PBEncodeItemType_NSContainer,
};

struct MiniPBEncodeItem {
    MiniPBEncodeItemType type;  // 类型
    int32_t compiledSize;   // (data长度+data内容)的长度
    int32_t valueSize;  //  data内容的长度
    union {
        void *objectValue;  
        /*
        NSData *buffer = [str dataUsingEncoding:NSUTF8StringEncoding];
        encodeItem->value.tmpObjectValue = (__bridge_retained void *) buffer;
        因为在使用tmpObjectValue是buffer在超出作用域后会在runloop睡眠前由autoreleasepool释放,所以要添加引用计数(__bridge_retained void*)
        */
        void *tmpObjectValue; // this object should release on dealloc
    }value ;
}

存储方式概述

kv的存储方式

key的长度 key value的长度 value

对OC中类型编码的支持

NSData(所有对象都用该形式存储)

void MiniCodedOutputData::writeData(NSData *value) {
    this->writeRawVarint32((int32_t) value.length); // 将长度写入
    this->writeRawData(value);  // 将数据写入
}

在函数的命名上raw代表内容本身的bits, 例如writeRawData;writeData代表写入了compiledSize+内容bits。

这是一个NSData的编码

NSData的长度(使用VarInt32编码) NSData的字节流

NSString

// MiniCodedOutputData.mm

void MiniCodedOutputData::writeString(NSString *value) {
    NSUInteger numberOfBytes = [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; // utf8编码的长度
    this->writeRawVarint32((int32_t) numberOfBytes);    // 将长度写入
    // 在长度后面写上字符串的utf8字节流
    [value getBytes:m_ptr + m_position  
             maxLength:numberOfBytes
            usedLength:0
              encoding:NSUTF8StringEncoding
               options:0
                 range:NSMakeRange(0, value.length)
        remainingRange:nullptr];    
    m_position += numberOfBytes;
}

NSString的编码如下,长度使用protobuf提供的Varint编码,内容部分使用utf8编码

这是一个NSString的编码

长度(使用VarInt32编码) NSString的utf8字节流

容器类型

// 这个方法有些混乱,感觉好像有点像个半成品
// 支持[String: Data],[String: String],类型容器或是嵌套的这几种类型容器的编码
// 支持NSString,NSData的完整编码方案,对于NSDate的编码需要依靠外部的一些实现
// 对于其他类型dic是不支持的
- (size_t)prepareObjectForEncode:(NSObject *)obj {
    if (!obj) {
        return m_encodeItems->size();
    }
    m_encodeItems->push_back(MiniPBEncodeItem());
    MiniPBEncodeItem *encodeItem = &(m_encodeItems->back());
    size_t index = m_encodeItems->size() - 1;
    
    if ([obj isKindOfClass:[NSString class]]) {
        NSString *str = (NSString *) obj;
        encodeItem->type = PBEncodeItemType_NSString;
        NSData *buffer = [str dataUsingEncoding:NSUTF8StringEncoding];
        encodeItem->value.tmpObjectValue = (__bridge_retained void *) buffer;
        encodeItem->valueSize = static_cast(buffer.length);
    } else if ([obj isKindOfClass:[NSDate class]]) {
        NSDate *oDate = (NSDate *) obj;
        encodeItem->type = PBEncodeItemType_NSDate;
        encodeItem->value.objectValue = (__bridge void *) oDate;
        encodeItem->valueSize = pbDoubleSize(oDate.timeIntervalSince1970);
        encodeItem->compiledSize = encodeItem->valueSize;
        return index; // double has fixed compilesize
    } else if ([obj isKindOfClass:[NSData class]]) {
        NSData *oData = (NSData *) obj;
        encodeItem->type = PBEncodeItemType_NSData;
        encodeItem->value.objectValue = (__bridge void *) oData;
        encodeItem->valueSize = static_cast(oData.length);
    } else if ([obj isKindOfClass:[NSDictionary class]]) {  // 这里是将容器中的元素按顺序放入vector中,容器编码相关的工作
        encodeItem->type = PBEncodeItemType_NSContainer;
        encodeItem->value.objectValue = nullptr;

        [(NSDictionary *) obj enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
            NSString *nsKey = (NSString *) key; // assume key is NSString
            if (nsKey.length <= 0 || value == nil) {
                return;
            }
#ifdef DEBUG
            if (![nsKey isKindOfClass:NSString.class]) {
                MMKVError(@"NSDictionary has key[%@], only NSString is allowed!", NSStringFromClass(nsKey.class));
            }
#endif

            size_t keyIndex = [self prepareObjectForEncode:key];
            if (keyIndex < self->m_encodeItems->size()) {
                size_t valueIndex = [self prepareObjectForEncode:value];
                if (valueIndex < self->m_encodeItems->size()) {
                    (*self->m_encodeItems)[index].valueSize += (*self->m_encodeItems)[keyIndex].compiledSize;
                    (*self->m_encodeItems)[index].valueSize += (*self->m_encodeItems)[valueIndex].compiledSize;
                } else {
                    self->m_encodeItems->pop_back(); // pop key
                }
            }
        }];

        encodeItem = &(*m_encodeItems)[index];
    } else {
        m_encodeItems->pop_back();
        MMKVError(@"%@ not recognized as container", NSStringFromClass(obj.class));
        return m_encodeItems->size();
    }
    encodeItem->compiledSize = pbRawVarint32Size(encodeItem->valueSize) + encodeItem->valueSize;

    return index;
}

dic的存储方式

data长度 dic的data

解码过程

每次进行内存映射时(loadFromFile),都会用decode方法,过程如下:

- (NSMutableDictionary *)decodeOneDictionaryOfValueClass:(Class)cls {
    if (cls == nullptr) {
        return nil;
    }

    NSMutableDictionary *dic = [NSMutableDictionary dictionary];

    m_inputData->readInt32();  // 忽略掉dic前的dic byte大小的记录

    while (!m_inputData->isAtEnd()) {
        NSString *nsKey = m_inputData->readString();  //  1. 读出长度 2. 根据长度读出数据
        if (nsKey) {
            id value = [self decodeOneObject:nil ofClass:cls];  1. 读出长度 2. 根据长度读出数据
            if (value) {
                [dic setObject:value forKey:nsKey];  // 写入dic,因为是从前到后读,所以后面的值会覆盖前面的
            } else {
                [dic removeObjectForKey:nsKey];
            }
        }
    }

    return dic;
}

你可能感兴趣的:(MMKV-编码和解码)