I need receive http-multipart responses from web service. The multipart response with pairs of JSON and image.
How to handle response in two parts (NSDictionary for JSON and NSData for image)?
Thanks in advance!
[UPDATE]
I wrote a category for NSData. Code below:
NSData+MultipartResponses.h
#import <Foundation/Foundation.h>
#interface NSData (MultipartResponses)
- (NSArray *)multipartArray;
- (NSDictionary *)multipartDictionary;
#end
NSData+MultipartResponses.m
#import "NSData+MultipartResponses.h"
#implementation NSData (MultipartResponses)
static NSMutableDictionary *parseHeaders(const char *headers)
{
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
int max=strlen(headers);
int start=0;
int cursor=0;
while(cursor<max)
{
while((headers[cursor]!=':')&&(headers[cursor]!='='))
{
cursor++;
}
NSString *key=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
cursor++;
while(headers[cursor]==' ')
{
cursor++;
}
start=cursor;
while(headers[cursor]&&(headers[cursor]!=';')&&((headers[cursor]!=13)||(headers[cursor+1]!=10)))
{
cursor++;
}
NSString *value;
if((headers[start]=='"')&&(headers[cursor-1]=='"'))
{
value=[[NSString alloc] initWithBytes:(headers+start+1) length:(cursor-start-2) encoding:NSASCIIStringEncoding];
}
else
{
value=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
}
[dict setObject:value forKey:key];
if(headers[cursor]==';')
{
cursor++;
}
else
{
cursor+=2;
}
while(headers[cursor]==' ')
{
cursor++;
}
start=cursor;
}
return dict;
}
- (NSDictionary *)multipartDictionaryWithBoundary:(NSString *)boundary
{
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
const char *bytes=(const char *)[self bytes];
const char *pattern=[boundary cStringUsingEncoding:NSUTF8StringEncoding];
int cursor=0;
int start=0;
int max=[self length];
int keyNo=0;
while(cursor<max)
{
if(bytes[cursor]==pattern[0])
{
int i;
int patternLength=strlen(pattern);
BOOL match=YES;
for(i=0; i<patternLength; i++)
{
if(bytes[cursor+i]!=pattern[i])
{
match=NO;
break;
}
}
if(match)
{
if(start!=0)
{
int startOfHeaders=start+2;
int cursor2=startOfHeaders;
while((bytes[cursor2]!=(char)0x0d)||(bytes[cursor2+1]!=(char)0x0a)||(bytes[cursor2+2]!=(char)0x0d)||(bytes[cursor2+3]!=(char)0x0a))
{
cursor2++;
if(cursor2+4==max)
{
break;
}
}
if(cursor2+4==max)
{
break;
}
else
{
int lengthOfHeaders=cursor2-startOfHeaders;
char *headers=(char *)malloc((lengthOfHeaders+1)*sizeof(char));
strncpy(headers, bytes+startOfHeaders, lengthOfHeaders);
headers[lengthOfHeaders]=0;
NSMutableDictionary *item=parseHeaders(headers);
int startOfData=cursor2+4;
int lengthOfData=cursor-startOfData-2;
if(([item valueForKey:#"Content-Type"]==nil)&&([item valueForKey:#"filename"]==nil))
{
NSString *string=[[NSString alloc] initWithBytes:(bytes+startOfData) length:lengthOfData encoding:NSUTF8StringEncoding];
keyNo++;
[dict setObject:string forKey:[NSString stringWithFormat:#"%d", keyNo]];
}
else
{
NSData *data=[NSData dataWithBytes:(bytes+startOfData) length:lengthOfData];
[item setObject:data forKey:#"data"];
keyNo++;
[dict setObject:item forKey:[NSString stringWithFormat:#"%d", keyNo]];
}
}
}
cursor=cursor+patternLength-1;
start=cursor+1;
}
}
cursor++;
}
return dict;
}
- (NSArray *)multipartArray
{
NSDictionary *dict=[self multipartDictionary];
NSArray *keys=[[dict allKeys] sortedArrayUsingSelector:#selector(localizedStandardCompare:)];
NSMutableArray *array=[NSMutableArray array];
for(NSString *key in keys)
{
[array addObject:dict[key]];
}
return array;
}
- (NSDictionary *)multipartDictionary
{
const char *bytes=(const char *)[self bytes];
int cursor=0;
int max=[self length];
while(cursor<max)
{
if(bytes[cursor]==0x0d)
{
break;
}
else
{
cursor++;
}
}
char *pattern=(char *)malloc((cursor+1)*sizeof(char));
strncpy(pattern, bytes, cursor);
pattern[cursor]=0x00;
NSString *boundary=[[NSString alloc] initWithCString:pattern encoding:NSUTF8StringEncoding];
free(pattern);
return [self multipartDictionaryWithBoundary:boundary];
}
#end
For me, your code didn't work. Instead, I rewrote that code in pure objective-c:
Take care that (my) boundaries in this code always have additional -- in front of the next boundary and a final boundary - those are stripped.
An NSArray is returned with a NSDictionary for each part, containing the keys "headers" as NSDictionary and "body" as NSData
- (NSArray *)multipartArrayWithBoundary:(NSString *)boundary
{
NSString *data = [[[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding] autorelease];
NSArray *multiparts = [data componentsSeparatedByString:[#"--" stringByAppendingString:boundary]]; // remove boundaries
multiparts = [multiparts subarrayWithRange:NSMakeRange(1, [multiparts count]-2)]; // continued removing of boundaries
NSMutableArray *toReturn = [NSMutableArray arrayWithCapacity:2];
for(NSString *part in multiparts)
{
part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSArray *separated = [part componentsSeparatedByString:#"\n\n"];
NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithCapacity:3];
for(NSString *headerLine in [[separated objectAtIndex:0] componentsSeparatedByString:#"\n"])
{
NSArray *keyVal = [headerLine componentsSeparatedByString:#":"];
[headers setObject:[[keyVal objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:[[keyVal objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
[toReturn addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[separated objectAtIndex:1] dataUsingEncoding:NSUTF8StringEncoding], #"body", headers, #"headers", nil]];
}
return toReturn;
}
Related
I have create a demo for downloading a file from the server it is working fine with foreground and background, but when I'm going to resume it, It will giving below Error
I have stcuk here could help me so solved it, I search in internet but I couldn't find anything helpful.
Download file and Pause Downlaod is working fine I have issue to resume it.
Task <3AE5BF24-3A4E-4713-8FC2-A0032022C913>.<6> finished with error [-3003] Error Domain=NSURLErrorDomain Code=-3003 "(null)" UserInfo={_NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundDownloadTask <3AE5BF24-3A4E-4713-8FC2-A0032022C913>.<6>"
), _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundDownloadTask <3AE5BF24-3A4E-4713-8FC2-A0032022C913>.<6>}
Here is the final Xcode project
Downlaod xcode project
#import "NSURLSession+ResumeData.h"
#import <UIKit/UIKit.h>
#define IS_IOS10ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10)
#pragma mark- private
static NSData * correctRequestData(NSData *data) {
if (!data) {
return nil;
}
// return the same data if it's correct
if ([NSKeyedUnarchiver unarchiveObjectWithData:data] != nil) {
return data;
}
NSMutableDictionary *archive = [[NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil] mutableCopy];
if (!archive) {
return nil;
}
NSInteger k = 0;
id objectss = archive[#"$objects"];
while ([objectss[1] objectForKey:[NSString stringWithFormat:#"$%ld",k]] != nil) {
k += 1;
}
NSInteger i = 0;
while ([archive[#"$objects"][1] objectForKey:[NSString stringWithFormat:#"__nsurlrequest_proto_prop_obj_%ld",i]] != nil) {
NSMutableArray *arr = archive[#"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:[NSString stringWithFormat:#"__nsurlrequest_proto_prop_obj_%ld",i]];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:#"$%ld",i+k]];
[dic removeObjectForKey:[NSString stringWithFormat:#"__nsurlrequest_proto_prop_obj_%ld",i]];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[#"$objects"] = arr;
}
i++;
}
if ([archive[#"$objects"][1] objectForKey:#"__nsurlrequest_proto_props"] != nil) {
NSMutableArray *arr = archive[#"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:#"__nsurlrequest_proto_props"];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:#"$%ld",i+k]];
[dic removeObjectForKey:#"__nsurlrequest_proto_props"];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[#"$objects"] = arr;
}
}
// Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root"
if ([archive[#"$top"] objectForKey:#"NSKeyedArchiveRootObjectKey"] != nil) {
[archive[#"$top"] setObject:archive[#"$top"][#"NSKeyedArchiveRootObjectKey"] forKey: NSKeyedArchiveRootObjectKey];
[archive[#"$top"] removeObjectForKey:#"NSKeyedArchiveRootObjectKey"];
}
// Reencode archived object
NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
return result;
}
static NSMutableDictionary *getResumeDictionary(NSData *data) {
NSMutableDictionary *iresumeDictionary = nil;
if (IS_IOS10ORLATER) {
id root = nil;
id keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
#try {
root = [keyedUnarchiver decodeTopLevelObjectForKey:#"NSKeyedArchiveRootObjectKey" error:nil];
if (root == nil) {
root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:nil];
}
} #catch(NSException *exception) {
}
[keyedUnarchiver finishDecoding];
iresumeDictionary = [root mutableCopy];
}
if (iresumeDictionary == nil) {
iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil];
}
return iresumeDictionary;
}
static NSData *correctResumeData(NSData *data) {
NSString *kResumeCurrentRequest = #"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = #"NSURLSessionResumeOriginalRequest";
if (data == nil) {
return nil;
}
NSMutableDictionary *resumeDictionary = getResumeDictionary(data);
if (resumeDictionary == nil) {
return nil;
}
resumeDictionary[kResumeCurrentRequest] = correctRequestData(resumeDictionary[kResumeCurrentRequest]);
resumeDictionary[kResumeOriginalRequest] = correctRequestData(resumeDictionary[kResumeOriginalRequest]);
NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
return result;
}
#implementation NSURLSession (ResumeData)
- (NSURLSessionDownloadTask *)downloadTaskWithCorrectResumeData:(NSData *)resumeData {
NSString *kResumeCurrentRequest = #"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = #"NSURLSessionResumeOriginalRequest";
NSData *cData = correctResumeData(resumeData);
cData = cData ? cData:resumeData;
NSURLSessionDownloadTask *task = [self downloadTaskWithResumeData:cData];
NSMutableDictionary *resumeDic = getResumeDictionary(cData);
if (resumeDic) {
if (task.originalRequest == nil) {
NSData *originalReqData = resumeDic[kResumeOriginalRequest];
NSURLRequest *originalRequest = [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData ];
if (originalRequest) {
[task setValue:originalRequest forKey:#"originalRequest"];
}
}
if (task.currentRequest == nil) {
NSData *currentReqData = resumeDic[kResumeCurrentRequest];
NSURLRequest *currentRequest = [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData];
if (currentRequest) {
[task setValue:currentRequest forKey:#"currentRequest"];
}
}
}
return task;
}
#end
sometimes, fatal error occurs in my app with EXC_BAD_ADDRESS error log.
What is the cause of my code?
crash log
0. Crashed: GGMutableDictionary Isolation Queue
0 libobjc.A.dylib 0x184e2c150 objc_msgSend + 16
1 CoreFoundation 0x1862e0d04 -[NSDictionary descriptionWithLocale:indent:] + 916
2 CoreFoundation 0x1862e0dac -[NSDictionary descriptionWithLocale:indent:] + 1084
3 Foundation 0x186da35a4 _NSDescriptionWithLocaleFunc + 76
4 CoreFoundation 0x1863788bc __CFStringAppendFormatCore + 8440
5 CoreFoundation 0x18637678c _CFStringCreateWithFormatAndArgumentsAux2 + 244
6 Foundation 0x186da3418 +[NSString stringWithFormat:] + 68
7 app 0x10019f4b8 -[TinyDB saveAsync] + 4296963256
8 app 0x10019c86c __26-[TinyDB putString:value:]_block_invoke + 4296951916
9 libdispatch.dylib 0x18526e9a0 _dispatch_client_callout + 16
10 libdispatch.dylib 0x18527bee0 _dispatch_barrier_sync_f_invoke + 84
11 app 0x10019c7e0 -[TinyDB putString:value:] + 4296951776
source file
This class is used asynchronously from many other class.
This class should have thread safe. But EXC_BAD_ADDRESS fatal error occurs in saveAsync method.
I think weakDictionaryRef or isolationQueue variables are deallocated. I want to fix this problem. What should I fix in this code?
Thank you for your advice.
TinyDB.h file
#interface TinyDB : NSObject
#property (nonatomic, retain) NSString * docPath;
// #property (nonatomic, retain) NSMutableDictionary * dictionary;
#property (nonatomic, retain) NSFileManager * fileManager;
#property (nonatomic, retain) NSString * dir;
#property (nonatomic, assign) BOOL flagWrite;
- (instancetype)initWithFile:(NSString *)file;
- (NSString *)getDocPath;
- (void)putDouble:(NSString *)key value:(double)value;
- (void)putInt:(NSString *)key value:(NSInteger)value;
- (void)putMutableArray:(NSString *)key value:(NSMutableArray *)value;
- (void)putString:(NSString *)key value:(NSString *)value;
- (void)putTinyDB:(NSString *)key value:(TinyDB *)value;
- (void)putLong:(NSString *)key value:(NSInteger)value;
- (void)putBool:(NSString *)key value:(BOOL)value;
- (void)putDictionary:(NSString *)key value:(NSDictionary *)value;
- (id)get:(NSString *)key;
- (NSMutableArray *)getMutableArray:(NSString *)key;
- (BOOL)has:(NSString *)key;
- (void)saveAsync;
- (void)save;
- (NSString *)jsonString;
- (NSString *)stringify:(id)obj;
- (NSString *)getSet:(id)value;
- (NSString *)getPairSet:(NSString *)key value:(id)value;
- (NSMutableDictionary*)getMutableDictionary:(NSString*)key;
- (NSString *)getString:(NSString *)key;
- (BOOL)getBool:(NSString*)key;
- (NSArray *)allKeys;
- (void)removeObjectForKey:(NSString*)key;
#end
TinyDB.m file
#implementation TinyDB
{
#private dispatch_queue_t isolationQueue_;
#private __strong NSMutableDictionary * _myDictionary;
}
#synthesize docPath=docPath,fileManager=fileManager,dir=dir,flagWrite=flagWrite;
BOOL flagOnSave = false;
BOOL flagWrite = true;
NSString* dir;
NSString* docPath = #"";
NSLock* _dicLock=nil;
NSFileManager* fileManager;
__weak id _weakDictionaryRef;
-(id)initWithFile:(NSString *)file{
self = [super init];
docPath = file;
// ##########################################
isolationQueue_ = dispatch_queue_create([#"GGMutableDictionary Isolation Queue" UTF8String], DISPATCH_QUEUE_CONCURRENT);
// ##########################################
fileManager = [NSFileManager defaultManager];
dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) firstObject];
dir = [NSString stringWithFormat:#"%#/myapp/",dir];
BOOL flagFileExist = [fileManager fileExistsAtPath:[[NSString alloc] initWithFormat:#"%#/%#.myapp",dir,docPath]];
_myDictionary = [[NSMutableDictionary alloc] init];
if(flagFileExist){
[BSDebugger log:[NSString stringWithFormat:#"DEBUG_myapp_CONFIG : File Found!!! %# --> ",file]];
#try{
NSMutableDictionary* dic = [[NSMutableDictionary alloc] initWithContentsOfFile:[[NSString alloc] initWithFormat:#"%#/%#.myapp",dir,docPath]];
_myDictionary = [[NSMutableDictionary alloc] initWithDictionary:dic];
}#catch(NSException * e){
_myDictionary = [[NSMutableDictionary alloc] init];
}#finally{
}
}else{
[BSDebugger log:[NSString stringWithFormat:#"DEBUG_myapp_CONFIG : File Not Found!!!--> %# ",file]];
_myDictionary = [[NSMutableDictionary alloc] init];
}
_weakDictionaryRef = _myDictionary;
[self saveAsync];
return self;
}
-(void)putString:(NSString*)key value:(NSString*)value{
dispatch_barrier_sync(isolationQueue_, ^{
if(value == nil){
return;
}
#try{
[_myDictionary setObject:value forKey:key];
}#catch(NSException* e){
NSMutableDictionary* buff = [[NSMutableDictionary alloc] initWithDictionary:_myDictionary];
_myDictionary = buff;
_weakDictionaryRef = _myDictionary;
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:value forKey:key];
});
}#finally{
}
[self saveAsync];
});
}
- (void)putDouble:(NSString *)key value:(double)value{
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:[[NSNumber alloc] initWithDouble:value] forKey:key];
[self saveAsync];
});
}
- (void)putInt:(NSString *)key value:(NSInteger)value{
dispatch_barrier_sync(isolationQueue_, ^{
#try{
[_myDictionary setObject:[[NSNumber alloc] initWithInteger:value] forKey:key];
}#catch(NSException* e){
NSMutableDictionary* buff = [[NSMutableDictionary alloc] initWithDictionary:_myDictionary];
_myDictionary = buff;
_weakDictionaryRef = _myDictionary;
[_myDictionary setObject:[[NSNumber alloc] initWithInteger:value] forKey:key];
}#finally{
}
[self saveAsync];
});
}
- (void)putMutableArray:(NSString *)key value:(NSMutableArray *)value{
dispatch_barrier_sync(isolationQueue_, ^{
if( key != nil && value != nil ){
[_myDictionary setObject:value forKey:key];
[self saveAsync];
}
});
}
- (void)putTinyDB:(NSString *)key value:(TinyDB *)value{
dispatch_barrier_sync(isolationQueue_, ^{
TinyDB* db = value;
NSString* docuPath = [db getDocPath];
#try{
[_myDictionary setObject:docuPath forKey:key];
}#catch(NSException* e){
NSMutableDictionary* buff = [[NSMutableDictionary alloc] initWithDictionary:_myDictionary];
_myDictionary = buff;
_weakDictionaryRef = _myDictionary;
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:docuPath forKey:key];
});
}#finally{
}
[self saveAsync];
});
}
- (void)putLong:(NSString *)key value:(NSInteger)value{
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:[[NSNumber alloc] initWithInteger:value] forKey:key];
[self saveAsync];
});
}
- (void)putBool:(NSString *)key value:(BOOL)value{
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:[[NSNumber alloc] initWithBool:value] forKey:key];
[self saveAsync];
});
}
- (void)putDictionary:(NSString *)key value:(NSDictionary *)value{
dispatch_barrier_sync(isolationQueue_, ^{
[_myDictionary setObject:value forKey:key];
[self saveAsync];
});
}
-(void)removeObjectForKey:(NSString*)key{
dispatch_barrier_sync(isolationQueue_, ^{
if( _myDictionary != nil && [_myDictionary objectForKey:key] != nil ){
[_myDictionary removeObjectForKey:key];
}
});
}
-(void) save{
dispatch_barrier_sync(isolationQueue_, ^{
#try {
// NSLog([NSString stringWithFormat:#"writeToFile Error : orgPath = %#/%#.myapp / %#",dir,docPath, self.dictionary ]);
// NSMutableDictionary* nsDic = self.dictionary;
if( _myDictionary != nil ){
NSDictionary * _dictionary =
(__bridge NSDictionary *)(CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(__bridge CFPropertyListRef)(_myDictionary),
kCFPropertyListImmutable));
if( _dictionary != nil ){
[_dictionary writeToFile:[[NSString alloc] initWithFormat:#"%#/%#.myapp",dir,docPath] atomically: false];
}
}
}#catch (NSException *exception) {
_myDictionary = [[NSMutableDictionary alloc] initWithDictionary:_myDictionary];
_weakDictionaryRef = _myDictionary;
}#finally {
}
});
}
// ################################################################
// normal function
- (NSString *)jsonString{
__block NSString* buff = #"";
dispatch_barrier_sync(isolationQueue_, ^{
if( _myDictionary != nil ){
NSDictionary * _dictionary = nil;
// dispatch_barrier_sync(isolationQueue_, ^{
_dictionary = (__bridge NSDictionary *)(CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(__bridge CFPropertyListRef)(_myDictionary),
kCFPropertyListImmutable));
if( _dictionary != nil ){
buff = [self stringify:_dictionary];
}
}else{
NSLog(#"buff = [self stringify:_myDictionary]; is nil ");
}
});
return buff;
}
- (NSString *)stringify:(id)obj{
if([obj isKindOfClass:[NSDictionary class]]){
int idx = 0;
NSString* buff = #"{";
if( obj != nil ){
NSDictionary* dic = [NSDictionary dictionaryWithDictionary:obj]; //obj;
for(NSString* key in dic){
id value = [dic objectForKey:key];
if(idx != 0){
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,#","];
}
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,[self getPairSet:key value:value]];
idx++;
}
}
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,#"}"];
return buff;
}else if([obj isKindOfClass:[NSArray class]]){
int idx = 0;
NSString* buff = #"[";
if( obj != nil ){
NSMutableArray* _a = [[NSMutableArray alloc] init];
for( int ai = 0; ai < [obj count]; ai++){
if( [obj objectAtIndex:ai] != nil ){
[_a addObject:[obj objectAtIndex:ai]];
}
}
NSArray* arr = [NSArray arrayWithArray:_a]; //obj;
for(id value in arr){
if(idx != 0){
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,#","];
}
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,[self getSet:value]];
idx++;
}
}
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,#"]"];
return buff;
}else{
return [self getSet:obj];
}
}
- (NSString *)getSet:(id)value{
NSString* buff = #"";
if([value isKindOfClass:[NSString class]]){
buff = [[NSString alloc] initWithFormat:#"%#\"%#\"",buff,value];
}else if([value isKindOfClass:[NSNumber class]]){
NSNumber* val = value;
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,[val stringValue]];
}else{
buff = [[NSString alloc] initWithFormat:#"%#%#",buff,[self stringify:value]];
}
return buff;
}
- (NSString *)getPairSet:(NSString *)key value:(id)value{
NSString* buff = #"";
if([value isKindOfClass:[NSString class]]){
buff = [[NSString alloc] initWithFormat:#"%#\"%#\":\"%#\"",buff,key,value];
}else if([value isKindOfClass:[NSNumber class]]){
buff = [[NSString alloc] initWithFormat:#"%#\"%#\":%#",buff,key,[value stringValue]];
}else{
buff = [[NSString alloc] initWithFormat:#"%#\"%#\":%#",buff,key,[self stringify:value]];
}
return buff;
}
-(NSMutableDictionary*)getMutableDictionary:(NSString*)key{
NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
id obj = [_myDictionary objectForKey:key];
if(obj != nil){
dic = [[NSMutableDictionary alloc] initWithDictionary:obj];
}
return dic;
}
-(NSArray *)allKeys{
__block NSArray* arr = nil;
dispatch_barrier_sync(isolationQueue_, ^{
if( _myDictionary != nil ){
arr = [_myDictionary allKeys];
}
});
return arr;
}
- (NSString *)getDocPath{
return docPath;
}
- (id)get:(NSString *)key{
__block id _obj = nil;
dispatch_barrier_sync(isolationQueue_, ^{
_obj = [_myDictionary objectForKey:key];
});
return _obj;
}
- (NSString *)getString:(NSString *)key{
__block NSString* returnStr = #"";
dispatch_barrier_sync(isolationQueue_, ^{
if([_myDictionary objectForKey:key]==nil){
returnStr = #"";
}
if([[_myDictionary objectForKey:key] isKindOfClass:[NSString class]]){
returnStr = [_myDictionary objectForKey:key];
}else{
returnStr = #"";
}
});
return returnStr;
}
- (BOOL)getBool:(NSString*)key{
__block BOOL flag = false;
dispatch_barrier_sync(isolationQueue_, ^{
if([_myDictionary objectForKey:key]==nil){
flag = NO;
}
if([[_myDictionary objectForKey:key] isKindOfClass:[NSNumber class]]){
NSNumber* boolValue = [_myDictionary objectForKey:key];
if([boolValue boolValue] == YES){
flag = YES;
}else{
flag = NO;
}
}else{
flag = NO;
}
});
return flag;
}
- (NSMutableArray *)getMutableArray:(NSString *)key{
__block NSMutableArray* _arr = nil;
dispatch_barrier_sync(isolationQueue_, ^{
_arr = [_myDictionary objectForKey:key];
});
return _arr;
}
- (BOOL)has:(NSString *)key{
__block BOOL flag = false;
dispatch_barrier_sync(isolationQueue_, ^{
if([_myDictionary objectForKey:key] != nil){
flag = true;
}else{
flag = false;
}
});
return flag;
}
// #########################
// save function
int flagTest = 0;
bool mark = 0;
NSTimer* saveTimer = nil;
-(void)saveAsync{
#try {
[BSDebugger log:[NSString stringWithFormat:#"_weakDictionaryRef : %# ",_weakDictionaryRef ]];
if( _myDictionary != nil ){
if( _weakDictionaryRef != nil ){
NSDictionary * _dictionary = nil;
_dictionary = (__bridge NSDictionary *)(CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(__bridge CFPropertyListRef)(_myDictionary),
kCFPropertyListImmutable));
#try{
flagOnSave = true;
UIBackgroundTaskIdentifier taskID = [myappCore beginBackgroundUpdateTask];
flagWrite = false;
NSString* orgPath = [[NSString alloc] initWithFormat:#"%#/%#.myapp",dir,docPath ];
#try {
if( _dictionary != nil ){
[_dictionary writeToFile:orgPath atomically:YES];
}
}#catch (NSException *exception) {
[BSDebugger log:[NSString stringWithFormat:#"DEBUG_myapp_TINYDB : %#",[exception callStackSymbols]]];
}#finally {
}
flagWrite = true;
flagOnSave = false;
[myappCore endBackgroundUpdateTask:taskID];
}#catch (NSException *exceptionMain) {
[BSDebugger log:[NSString stringWithFormat:#"DEBUG_myapp_TINYDB : %#",[exceptionMain callStackSymbols]]];
}#finally {
_dictionary = nil;
}
return;
// });
}
}
}#catch (NSException *exception) {
}#finally {
// [_dicLock unlock];
}
}
#end
Some ideas:
Turn on zombies (How to enable NSZombie in Xcode). You'll get more information about the object causing the crash when you do that.
If you can make the error happen at will, set a breakpoint at the top of saveAsync and step through it.
Change the stringWithFormat calls somehow so they don't reference anything that might be deallocated and see if the problem goes away. If it does, you'll know what was causing it.
I find it sometimes helps to override retain and release in objects I'm suspicious of and either print something out or set a breakpoint so I can see who might be over-releasing an object.
ANSWER
I heve a mistake when i call the fetchDailyForecast it should looks like
[self.fetchController fetchDailyForecast:self.currentLocation.coordinate completionBlock:^(DailyModel *newModel) {
_dailyCondition = newModel;
NSLog(#"newModel = %#", _dailyCondition);
}];
END
I try to use bloc's and wait but have a bad_access exaction. Firstly i call method from MangeDataController.m
[self.fetchController
fetchDailyForecast:self.currentLocation.coordinate
completionBlock:(void(^)(DailyModel *))_dailyCondition];
where the dailyCondition is instance of DailyModel.
Secondly here is fetchDailyForecast method realization in FetchController.m.
-(void)fetchDailyForecast:(CLLocationCoordinate2D)coordinate completionBlock:(void(^)(DailyModel *))completionBlock {
NSString *urlString = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/forecast/daily?lat=%f&lon=%f&units=imperial&cnt=7%#", coordinate.latitude, coordinate.longitude, _key];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
[self fetchJSONfromURL:urlString completionBlock:^(NSDictionary *weatherData) {
completionBlock([[DailyModel alloc] dataFromDictionaryDaily:weatherData]); --- (here the bad access);
}];
}
and thirdly in fatchDailyForecast i call fetchJSONfromURL here is realization:
-(void)fetchJSONfromURL:(NSString *)urlString completionBlock:(void (^)(NSDictionary *))completionBlock {
NSLog(#"url = %#", urlString);
[_manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responceObject) {
if (responceObject) {
completionBlock(responceObject);
} else {
NSLog(#"responce object error");
}
}
failure:^(NSURLSessionTask *operation, NSError *error){
NSLog(#"error %#", error);
}];
}
P.S. I think my mistake is that i try to pass day _dailyCondition but should pass something different. Thanks for any help!
**EDIT ------- DailyModel.m **
#import "DailyModel.h"
#implementation DailyModel
-(id)dataFromDictionaryDaily:(NSDictionary *)dic {
NSArray *arrayWithDictionarys = [dic objectForKey:#"list"];
NSDictionary *dictionaryWithWeekDays = [self indexKeyedDictionaryFromArray:arrayWithDictionarys];
// NSLog(#"dictionary with dayweeks = %#", dictionaryWithWeekDays);
_arrayWithTemp = [NSMutableArray new];
_arrayWithDate = [NSMutableArray new];
_arrayWithIcon = [NSMutableArray new];
for (int i = 0; i <= 6; i++) {
NSNumber* key = [NSNumber numberWithInt:i];
[self addValuesToArrayWithTemp:dictionaryWithWeekDays key:key index:i];
[self addValuesToArrayWithDate:dictionaryWithWeekDays key:key index:i];
[self addValueToArrayWithImage:dictionaryWithWeekDays key:key index:i];
}
return self;
}
- (NSDictionary *) indexKeyedDictionaryFromArray:(NSArray *)array
{
NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc] init];
[array enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop){
NSNumber *index = [NSNumber numberWithInteger:idx];
[mutableDictionary setObject:obj forKey:index];
}];
NSDictionary *result = [NSDictionary.alloc initWithDictionary:mutableDictionary];
return result;
}
-(void) addValuesToArrayWithTemp:(NSDictionary *)inputDic key:(NSNumber *)key index:(int)ind {
NSDictionary *currentDic = [inputDic objectForKey:key];
NSDictionary *temp = [currentDic objectForKey:#"temp"];
_tempLow = [temp objectForKey:#"min"];
_tempHigh = [temp objectForKey:#"max"];
_tempLow = [self convertToCelsius:_tempLow];
_tempHigh = [self convertToCelsius:_tempHigh];
NSString *tempString = [NSString stringWithFormat:#"%.02f / %.02f", _tempLow.floatValue , _tempHigh.floatValue];
[_arrayWithTemp insertObject:tempString atIndex:ind];
}
-(NSNumber *)convertToCelsius:(NSNumber *)far {
double f = (far.doubleValue - 32) / 1.8;
NSNumber *celsius = [NSNumber numberWithDouble:f];
return celsius;
}
-(void) addValuesToArrayWithDate:(NSDictionary *)inputDic key:(NSNumber *)key index:(int)ind {
NSDictionary *currenDic = [inputDic objectForKey:key];
NSNumber *tempNumber = [currenDic objectForKey:#"dt"];
double unixTimeStamp = tempNumber.doubleValue;
NSString* weekDay = [self convertToWeekDay:unixTimeStamp];
[_arrayWithDate insertObject:weekDay atIndex:ind];
}
-(NSString *)convertToWeekDay:(double) unixtime {
NSTimeInterval _interval = unixtime;
NSDate *date = [NSDate dateWithTimeIntervalSince1970:_interval];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:#"EEEE"];
NSString *dateString = [formatter stringFromDate:date];
return dateString;
}
-(void) addValueToArrayWithImage:(NSDictionary *)inputDic key:(NSNumber *)key index:(int)ind {
NSDictionary *currenDic = [inputDic objectForKey:key];
NSArray *weatherArray = [currenDic objectForKey:#"weather"];
NSDictionary *weather = weatherArray[0];
_icon = [weather objectForKey:#"icon"];
_icon = [self currentImageString:_icon];
[_arrayWithIcon insertObject:_icon atIndex:ind];
}
-(NSString *)currentImageString:(NSString *)ico {
NSDictionary *dic = #{
#"01d" : #"weather-clear",
#"02d" : #"weather-few",
#"03d" : #"weather-few",
#"04d" : #"weather-broken",
#"09d" : #"weather-shower",
#"10d" : #"weather-rain",
#"11d" : #"weather-tstorm",
#"13d" : #"weather-snow",
#"50d" : #"weather-mist",
#"01n" : #"weather-moon",
#"02n" : #"weather-few-night",
#"03n" : #"weather-few-night",
#"04n" : #"weather-broken",
#"09n" : #"weather-shower",
#"10n" : #"weather-rain-night",
#"11n" : #"weather-tstorm",
#"13n" : #"weather-snow",
#"50n" : #"weather-mist",
};
ico = [dic objectForKey:ico];
return ico;
}
#end
EDIT 2
I add the condition into (void)fetchDailyForecast:(CLLocationCoordinate2D)coordinate completionBlock:(void(^)(DailyModel *))completionBlock and now app don't crash but it's still not work and nslog say #"nothing"
[self fetchJSONfromURL:urlString completionBlock:^(NSDictionary
*weatherData) { if (completionBlock) { completionBlock(model = [[DailyModel alloc] dataFromDictionaryDaily:weatherData]); } else {
NSLog(#"nothing"); } }];
I use
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
and this sends out the call to the server when having an array in the format:
payments[]
payments[][email]=0&
payments[][category]=&
payments[][email]=1&
payments[][category]=&
I need the indexes for the server like.
payments[]
payments[0][email]=0&
payments[0][category]=&
payments[1][email]=1&
payments[1][category]=&
How can I achieve that?
Thanks.
AFHTTPRequestSerializer has a method setQueryStringSerializationWithBlock which allows you to provide your own block for serializing the parameters.
Unfortunately, the internal AF* functions for serialization are private, but you can copy them and make a small modification in these lines to add the indexes.
To set your own serialization block:
[serializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, id parameters, NSError *__autoreleasing *error) {
return ZAFQueryStringFromParametersWithEncoding(parameters, serializer.stringEncoding);
}];
Including the code, where the AFNetworking functions have been copied and prefixed with a Z to ZAF:
static NSString * const kAFCharactersToBeEscapedInQueryString = #":/?&=;+!##$()',*";
static NSString * AFPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
static NSString * const kAFCharactersToLeaveUnescapedInQueryStringPairKey = #"[].";
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}
static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
}
#pragma mark -
#interface ZAFQueryStringPair : NSObject
#property (readwrite, nonatomic, strong) id field;
#property (readwrite, nonatomic, strong) id value;
- (id)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding;
#end
#implementation ZAFQueryStringPair
- (id)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding);
} else {
return [NSString stringWithFormat:#"%#=%#", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)];
}
}
#end
#pragma mark -
static NSArray * ZAFQueryStringPairsFromDictionary(NSDictionary *dictionary);
static NSArray * ZAFQueryStringPairsFromKeyAndValue(NSString *key, id value);
static NSString * ZAFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (ZAFQueryStringPair *pair in ZAFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
}
return [mutablePairs componentsJoinedByString:#"&"];
}
NSArray * ZAFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return ZAFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * ZAFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"description" ascending:YES selector:#selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:#[ sortDescriptor ]]) {
id nestedValue = [dictionary objectForKey:nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:#"%#[%#]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
NSInteger idx = 0;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:#"%#[%ld]", key, idx++], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:#[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[ZAFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
It seems that since XCode 6.1, the iPhone 5S, iPhone 6 and iPhone 6+ simulators (all 64-bit) all return data from the following system method differently (keys are ordered differently) than their 32-bit simulator counterparts (e.g. iPhone 5 simulator)
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
This difference of key ordering caused a problem for me since we calculate the SHA1 of that JSON data (converted to NSString*) and use it for a validation test. Since the ordering changed, the SHA1 changed and the validation fails.
Simplified sample code (non-ARC) to get the SHA1 is below:
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
NSString * json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
NSString * sha1 = [MyUtils computeSHA1:json];
+(NSString*) computeSHA1:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
NSNumber* dataLen = [NSNumber numberWithUnsignedInteger:data.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, dataLen.unsignedIntValue, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x", digest[i]];
return output;
}
Apparently, this key ordering difference doesn't happen on the actual devices (previous behavior was preserved).
I also tried with the NSJSONWritingPrettyPrinted option but the JSON ordering is still inconsistent between simulators.
So, the question is: Does anyone have a recommendation on how to normalize such JSON data so as to not be susceptible to key ordering changes? Alternately, is there any way to get the previous (32-bit simulator) behavior?
Key ordering in dictionaries is not guaranteed. If you need them sorted, put them into an array and sort them.
The code below (non-ARC) worked for me to better canonicalize JSON output. The code assumes the class methods below are all in a class called MyUtils.
Simply pass the the NSDictionary to serialize into "canonicalized JSON" to canonicalJSONRepresentationWithDictionary:
The returned NSString* then contains serialized JSON that has the keys ordered lexicographically/alphabetically in a non-human readable format.
+(NSString *) canonicalJSONRepresentationWithDictionary:(NSDictionary *)dict
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"{"];
NSArray* keys = [[dict allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString* a, NSString* b) {
return [a compare:b];
}];
for (int i = 0; i < keys.count; i++) {
NSString* key = keys[i];
[json appendFormat:#"\"%#\":", key];
if ([dict[key] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:dict[key]]];
} else {
return nil;
}
if (i < keys.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"}"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithArray:(NSArray *) array
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"["];
for (int i = 0; i < array.count; i++) {
if ([array[i] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:array[i]]];
} else if ([array[i] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:array[i]]];
} else if ([array[i] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:array[i]]];
} else {
return nil;
}
if (i < array.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"]"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithString:(NSString *) string;
{
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:string, #"a", nil];
NSError * error;
NSData * jsonData = nil;
NSString * json = nil;
jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
if (!jsonData) {
NSLog(#"Got an error serializing json: %#", error);
return nil;
} else {
json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
}
NSRange colonQuote = [json rangeOfString:#":\""];
NSRange lastQuote = [json rangeOfString:#"\"" options:NSBackwardsSearch];
NSRange range = NSMakeRange(colonQuote.location + 2, lastQuote.location - colonQuote.location - 2);
NSString* rc = [json substringWithRange:range];
return rc;
}