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<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的儲存方式
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;
}
複製程式碼