NSArchiver
因爲OS X 10.2已被棄用,不可用AFAIK iOS上子類NSCoder,再造NSArchiver
在另一方面,NSKeyedArchiver
已知缺乏對速度&簡明部分(some users報告100多次性能差異在NSKeyedArchiver
和NSArchiver
之間)。我要歸檔的對象主要是NSObject
的子類,其中包含NSMutableArray
的NSNumber
,以及包含原始類型的對象(主要爲double
)。我不相信,開銷密鑰存檔意味着值得。
所以我決定在iOS上子類NSCoder
創建一個串行編碼器,風格爲NSArchiver
。
我知道鍵控存檔可能派上用場:向前兼容性和其他細節,它可能是我最終會用到的,但我很想知道我可以通過串行存檔獲得什麼類型的演出。坦率地說,我認爲我可以通過這樣做來學習很多東西。所以我不是感興趣的替代解決方案;
我一直的Cocotron來源的啓發,提供了一個開源的NSArchiver
TLDR:我想繼承NSCoder
重建NSArchiver
我使用ARC,爲編制iOS 6 & 7,並假設現在是32位系統。我只是使用NSHashTable
(weakObjectsHashTable
)來防止類名重複:類將在第一次遇到時被描述,然後通過引用引用它們。
我用一個NSMutableData
建立檔案:
@interface Archiver {
NSMutableData *_data;
void *_bytes;
size_t _position;
NSHashTable *_classes;
}
@end
的基本方法是:
-(void)_expandBuffer:(NSUInteger)length
{
[_data increaseLengthBy:length];
_bytes = _data.mutableBytes;
}
-(void)_appendBytes:(const void *)data length:(NSUInteger)length
{
[self _expandBuffer:length];
memcpy(_bytes+_position, data, length);
_position += length;
}
我使用_appendBytes:length:
轉儲原始類型,例如int
,char
,float
,double
...等。沒有什麼有趣的。
C風格的字符串都使用拋投這種索然無味地方法:
-(void)_appendCString:(const char*)cString
{
NSUInteger length = strlen(cString);
[self _appendBytes:cString length:length+1];
}
最後,歸檔類信息和對象:
-(void)_appendReference:(id)reference {
[self _appendBytes:&reference length:4];
}
-(void)_appendClass:(Class)class
{
// NSObject class is always represented by nil by convention
if (class == [NSObject class]) {
[self _appendReference:nil];
return;
}
// Append reference to class
[self _appendReference:class];
// And append class name if this is the first time it is encountered
if (![_classes containsObject:class])
{
[_classes addObject:class];
[self _appendCString:[NSStringFromClass(class) cStringUsingEncoding:NSASCIIStringEncoding]];
}
}
-(void)_appendObject:(const id)object
{
// References are saved
// Although we don't handle relationships between objects *yet* (we could do it the exact same way we do for classes)
// at least it is useful to determine whether object was nil or not
[self _appendReference:object];
if (object==nil)
return;
[self _appendClass:[object classForCoder]];
[object encodeWithCoder:self];
}
我對象的encodeWithCoder:
方法都像她那樣,沒什麼奇特的:
[aCoder encodeValueOfObjCType:@encode(double) at:&_someDoubleMember];
[aCoder encodeObject:_someCustomClassInstanceMember];
[aCoder encodeObject:_someMutableArrayMember];
解碼方式幾乎相同; 解析程序擁有它已知的類的NSMapTable
,並查找它不知道的類的名稱。
@interface Unarchiver(){
NSData *_data;
const void *_bytes;
NSMapTable *_classes;
}
@end
我不會來煩你用
-(void)_extractBytesTo:(void*)data length:(NSUInteger)length
和
-(char*)_extractCString
有趣的東西可能是在對象解碼代碼的細節:
-(id)_extractReference
{
id reference;
[self _extractBytesTo:&reference length:4];
return reference;
}
-(Class)_extractClass
{
// Lookup class reference
id classReference = [self _extractReference];
// NSObject is always nil
if (classReference==nil)
return [NSObject class];
// Do we already know that one ?
if (![_classes objectForKey:classReference])
{
// If not, then the name should follow
char *classCName = [self _extractCString];
NSString *className = [NSString stringWithCString:classCName encoding:NSASCIIStringEncoding];
free(classCName);
Class class = NSClassFromString(className);
[_classes setObject:class forKey:classReference];
}
return [_classes objectForKey:classReference];
}
-(id)_extractObject
{
id objectReference = [self _extractReference];
if (!objectReference)
{
return nil;
}
Class objectClass = [self _extractClass];
id object = [[objectClass alloc] initWithCoder:self];
return object;
}
最後,中心的方法(我w烏爾德感到驚訝,如果這個問題是在這裏某處)
-(void)decodeValueOfObjCType:(const char *)type at:(void *)data
{
switch(*type){
/* snip, snip */
case '@':
*(id __strong *) data = [self _extractObject];
break;
}
}
對應的encodeWithCoder:
以前的片段中initWithCoder:
方法會去這樣的事情
if (self = [super init]) {
// Order is important
[aDecoder decodeValueOfObjCType:@encode(double) at:& _someDoubleMember];
_someCustomClassInstanceMember = [aDecoder decodeObject];
_someMutableArrayMember = [aDecoder decodeObject];
}
return self;
我decodeObject
實現是完全_extractObject
。
現在,這一切都應該很好,很好。事實上,我能夠存檔/取消存檔我的一些對象。檔案看起來很好,只要我願意在十六進制編輯器中檢查它們,我就可以解除某些包含double
的其他類的NSMutableArray
的自定義類的存檔。
但由於某些原因,如果我嘗試解檔包含的NSMutableArray
NSNumber
我的目標之一,我碰到這個問題:
malloc: *** error for object 0xc112cc: pointer being freed was not allocated
似乎每NSNumber
一行在數組,並且地址0xc112cc
對於每一行都是相同的。在malloc_error_break
中放置斷點告訴我錯誤來自-[NSPlaceholderNumber initWithCoder:]
(從我的_extractObject
方法中調用)。
這是一個與我使用ARC有關的問題嗎?我錯過了什麼?
你能分享一個最小的initWithCoder:實現嗎? – James
我用示例'initWithCoder:'實現更新了我的帖子。這非常簡單。 – Olotiar
它可能與標記指針有關,檢查是否是您正在歸檔的實際值「0xc112cc」? –