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的存储方式
对OC中类型编码的支持
NSData(所有对象都用该形式存储)
void MiniCodedOutputData::writeData(NSData *value) {
this->writeRawVarint32((int32_t) value.length); // 将长度写入
this->writeRawData(value); // 将数据写入
}
复制代码
在函数的命名上raw代表内容本身的bits, 例如writeRawData;writeData代表写入了compiledSize+内容bits。
这是一个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的编码
容器类型
// 这个方法有些混乱,感觉好像有点像个半成品
// 支持[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<int32_t>(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<int32_t>(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的存储方式
解码过程
每次进行内存映射时(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;
}
复制代码