我的iOS程序正在接收JSON数据并尝试解析它,但由于某种原因我总是无法确定。多个线程几乎同时调用此函数。一旦我切换到使用GCDAsyncSocket,这才开始发生,奇怪的是。以下是接收和解析数据的相关代码:
// Called whenever I want my program to receive null-terminated data from the server:
[socket readDataToData:[NSData dataWithBytes:"\0" length:1] withTimeout:10 tag:0];
- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag{ // a GCDAsyncSocket delegate method
[self didReceiveNetworkData:data];
}
- (void)didReceiveNetworkData: (NSData*) data{
if (TESTING) NSLog(@"Received network data of length %lu===\n%@\n===", (unsigned long) data.length, [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]);
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData: data
options:kNilOptions
error:&error];
if (!json){
NSLog(@"Got an error parsing received JSON data: %@", error);
return;
}
// Then I handle the dictionary (omitted code)…
}
日志和断点调试器说这是收到的数据:
{
"responseType": -1
}
确切地说,它的字节是" {\ n \ t \" responseType \":\ t-1 \ n} \ 0"。我得到了这个"垃圾在最后" JSONObjectWithData函数运行后出错:
Got an error parsing received JSON data: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x10b203800 {NSDebugDescription=Garbage at end.}
根据在线JSON测试人员和我在单独项目中的简单测试,这是有效的JSON。那我为什么会收到错误?我想也许是在null终结符之后抱怨额外的字节,所以我尝试通过制作一个NSString然后将其转回NSData来修剪它,但这没有帮助:
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData: [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] dataUsingEncoding: NSUTF8StringEncoding]
options:kNilOptions
error:&error];
因为使用GCDAsyncSocket是开始这个问题的原因,我想知道它是否与多线程有关,但我真的想不出有什么原因可以解决这个问题。有什么想法吗?
P.S。我使用GCDAsyncSocket因为我不知道如何处理Objective-C中的低级套接字,所以我的原始代码有很多奇怪的错误。
答案 0 :(得分:7)
奇怪的是,这就是它所需要的!在didReceiveNetworkData函数的开头:
//Yes, I know this wastefully allocates RAM, so I'll fix it later.
NSString* string = [[[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] stringByReplacingOccurrencesOfString:@"\t" withString:@""] stringByReplacingOccurrencesOfString:@"\0" withString:@""];
data = [string dataUsingEncoding:NSUTF8StringEncoding];
我删除了所有'\ t'(在条目之前)和'\ 0'(在字符串末尾)字符。仅删除'\ 0'xor'\ t'不起作用;它必须是两者。除非我遗漏了某些东西,否则NSJSONSerialization关注制表符和尾随空字符似乎很奇怪!
顺便说一句,cJSON对此进行了编码,这意味着cJSON和NSJSONSerialization并不严格一致。在线JSON测试人员同意cJSON编码的方式。
无论如何我可能是错的,因为这在没有GCDAsyncSocket的情况下工作(尽管我仍然需要删除空字符,我现在还记得)。或者也许我的旧解决方案以某种方式删除了制表符。
答案 1 :(得分:3)
更新: 您可能以某种方式遇到转义字符问题。我自己不是JSON大师,但我记得我曾经将一个JSON字符串添加到字典中,然后从该字典创建了一个JSON。 JSON字符串本身变为转义符,因此在解析期间它将保留一个字符串。我的感觉是你需要删除转义字符。
也许你有一些竞争条件,并在JSONParsing正在进行时写入数据。确保将NSData而不是NSMutableData传递给执行序列化以确保的方法。 请记住,NSJSONSerialization不是流式JSON解析器。它需要一个不可变的JSON对象并将其序列化为Foundation对象。如果要向NSData添加字节,NSJSONSerialization将在某些时刻看到那时的字节流是无效的JSON(尚未)。
答案 2 :(得分:2)
即使我两次请求相同的JSON文件,我也遇到了这个问题。我通过改变我的iOS应用程序如何通过我自己创建的URL类自定义检索JSON来解决它:
我的老路:
//init the download JSON class
var downloadJSON = createDownloadJSONObject()
//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")
/** waits for the first JSON object to return the json file successfully */
//throws the garbage collection error even though requested file is the same
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")
我只能想象问题来自于我如何处理对象的清理。
工作方法:
//init the download JSON class
var downloadJSON = createDownloadJSONObject()
//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")
/** waits for the first JSON object to return the json file successfully */
//re instantiate the download JSON class
downloadJSON = createDownloadJSONObject()
//Downloads and parses the file correctly
downloadJSON.downloadJSON(url: "http://www.example.com/jsonfile.json")
//happy days
答案 3 :(得分:0)
您可以维护字典来存储从服务器获取的响应。 每项任务都会有独特的响应。所以用"键"创建一个字典。 as" taskIdentifier"任务和"价值观" as"数据"。
例如: 在didReceiveData或任何其他等效方法[在服务器获得响应的情况下]将taskIdentifier作为键存储在字典中的响应。
NSString *taskID = [@(dataTask.taskIdentifier) stringValue];
[_task_data_dictionary setObject:data forKey:taskID];
这里_task_data_dictionary是字典。这样你可以摆脱上面的错误。
在此之后,您可以使用此代码
使用相同的字典获取数据 NSData *data = [_task_data_dictionary objectForKey:taskNumber];
再次使用taskIdentifier。
希望这有帮助。