Olo*_*iar 6 cocoa objective-c nscoder ios automatic-ref-counting
NSArchiver 自OS X 10.2起不推荐使用,并且在iOS上不可用AFAIK
在另一方面,NSKeyedArchiver已知缺乏对速度和简明一部分(一些用户报告之间的100倍以上的性能差异NSKeyedArchiver和NSArchiver).我要归档的对象主要是NSObject含子类NSMutableArray的NSNumber包含基本类型(主要是和对象double).我不相信开销密钥存档意味着值得.
所以我决定NSCoder在iOS上创建一个串行编码器的子类NSArchiver.
我知道键控档案可能派上用场:向后兼容和其他细节,它可能是我最终会使用的,但我很想知道串行存档可以获得什么样的表现.坦率地说,我认为通过这样做我可以学到很多东西.所以我对替代解决方案不感兴趣;
我受到了Cocotron的来源的启发,提供了一个开源的NSArchiver
TLDR:我想要子类化NSCoder来重建NSArchiver
我正在使用ARC,为iOS 6和7编译,现在假设是32位系统.
我现在对引用对象或字符串不感兴趣,我只使用a NSHashTable(weakObjectsHashTable)来防止类名被复制:类将在第一次遇到时被描述,然后通过引用引用.
我使用a NSMutableData来构建存档:
@interface Archiver {
NSMutableData *_data;
void *_bytes;
size_t _position;
NSHashTable *_classes;
}
@end
Run Code Online (Sandbox Code Playgroud)
基本方法是:
-(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;
}
Run Code Online (Sandbox Code Playgroud)
我使用_appendBytes:length:转储原始类型如 int,char,float,double...等,没有什么有趣的存在.
使用这种同样无趣的方法转储C风格的字符串:
-(void)_appendCString:(const char*)cString
{
NSUInteger length = strlen(cString);
[self _appendBytes:cString length:length+1];
}
Run Code Online (Sandbox Code Playgroud)
最后,归档类信息和对象:
-(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];
}
Run Code Online (Sandbox Code Playgroud)
encodeWithCoder:我的对象的方法看起来都像那样,没什么特别的:
[aCoder encodeValueOfObjCType:@encode(double) at:&_someDoubleMember];
[aCoder encodeObject:_someCustomClassInstanceMember];
[aCoder encodeObject:_someMutableArrayMember];
Run Code Online (Sandbox Code Playgroud)
解码几乎是一样的; unarchiver拥有NSMapTable它已经知道的类,并查找它不知道的类的名称.
@interface Unarchiver (){
NSData *_data;
const void *_bytes;
NSMapTable *_classes;
}
@end
Run Code Online (Sandbox Code Playgroud)
我不会厌倦你的具体细节
-(void)_extractBytesTo:(void*)data length:(NSUInteger)length
Run Code Online (Sandbox Code Playgroud)
和
-(char*)_extractCString
Run Code Online (Sandbox Code Playgroud)
有趣的东西可能在对象解码代码中:
-(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;
}
Run Code Online (Sandbox Code Playgroud)
最后,中心方法(如果问题出在这里,我不会感到惊讶)
-(void)decodeValueOfObjCType:(const char *)type at:(void *)data
{
switch(*type){
/* snip, snip */
case '@':
*(id __strong *) data = [self _extractObject];
break;
}
}
Run Code Online (Sandbox Code Playgroud)
initWithCoder:对应于前一个片段的方法encodeWithCoder:就是这样的
if (self = [super init]) {
// Order is important
[aDecoder decodeValueOfObjCType:@encode(double) at:& _someDoubleMember];
_someCustomClassInstanceMember = [aDecoder decodeObject];
_someMutableArrayMember = [aDecoder decodeObject];
}
return self;
Run Code Online (Sandbox Code Playgroud)
我的decodeObject实施正是如此_extractObject.
现在,所有这些都应该很好用.事实上; 我能够归档/取消归档我的一些对象.档案看起来很好,我愿意在十六进制编辑器中检查它们,并且我能够取消归档包含NSMutableArray其他一些包含s的类的自定义类double.
但出于某种原因,如果我尝试取消归档包含其中一个NSMutableArray的对象NSNumber,我会遇到这个问题:
malloc: *** error for object 0xc112cc: pointer being freed was not allocated
Run Code Online (Sandbox Code Playgroud)
NSNumber数组中似乎每行一行,并且0xc112cc每行的地址相同.设置断点malloc_error_break告诉我错误来自-[NSPlaceholderNumber initWithCoder:](从我的_extractObject方法调用).
这是与我使用ARC有关的问题吗?我错过了什么?
-(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr我的错误与对type代表 C 字符串 ( )的第二个参数的误解有关*type == '*'。在这种情况下addr,是 a const char **,指向 the 的指针const char *,它本身指向应编码0的常量终止数组。char
NSNumber encodeWithCoder:编码一个小的 C 字符串,表示支持该值的变量的类型(ifor int、dfor double 等。据我所知,它与指令相同@encode)。
我之前的误解(假设addr是 a const char *)进行了不正确的编码/解码,initWithCoder:因此失败了(底线:它试图使用free堆栈变量,因此错误消息以及每次调用的地址始终相同的事实功能)。
我现在有一个有效的实施。如果有人感兴趣,代码位于我的GitHub上,并获得 MIT 许可。
| 归档时间: |
|
| 查看次数: |
519 次 |
| 最近记录: |