MMKV的編碼和解碼

sunshinfight發表於2019-03-16

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;
}
複製程式碼

相關文章