Objective-C iOS Generating Info_Hash from Torrent File - ios

I'm currently pulling my hair out trying to generate an info_hash from a torrent file I've downloaded. I created the Torrent file using uTorrent and using PeerTracker as my tracking software.
I'm using an updated version of the code found here:
https://code.google.com/p/rouge-server/source/browse/trunk/driver/objective-c/RougeDriver/BEncoding.m?r=88
{
announce = "http://sub.url.com:8080/announce.php";
"created by" = "uTorrent/1870";
"creation date" = 1425134140;
encoding = "UTF-8";
info = {
length = 4;
name = "test.txt";
"piece length" = 16384;
pieces = "\U00a9J\U00e8\U00c2\U00c3\U00b1\U00f5\U00b6L\bs\U201d\U00eb\U00c8\U00e1\U00f2/\U00aa\U201d";
};
}
Below is the exact encoding method I'm using:
+ (void)encode:(id)object toString:(NSMutableString *)string
{
if([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *dictionary = (NSDictionary *)object;
[string appendString:#"d"];
for(id key in [dictionary allKeys])
{
[self encode:key toString:string];
[self encode:[dictionary objectForKey:key] toString:string];
}
[string appendString:#"e"];
}
else if([object isKindOfClass:[NSString class]])
{
NSString *stringObject = (NSString *)object;
NSString *format = #"%lu:%#";
[string appendFormat:format, [string length], stringObject];
}
else if([object isKindOfClass:[NSArray class]])
{
NSArray *array = (NSArray *)object;
[string appendString:#"l"];
for(id item in array)
{
[self encode:item toString:string];
}
[string appendString:#"e"];
}
else if([object isKindOfClass:[NSNumber class]])
{
NSNumber *number = (NSNumber *)object;
NSString *format = #"i%de";
[string appendFormat:format, [number intValue]];
}
}
This is my first time working with Torrents and bencoding. I know I'm only supposed to Encode the "info" dictionary contained within the main dictionary.
Does anyone one know where I could be going wrong here?

The actual pieces content is data:
0xa9, 0x4a, 0x8f, 0xe5, 0xcc, 0xb1, 0x9b, 0xa6, 0x1c, 0x4c, 0x08, 0x73, 0xd3, 0x91, 0xe9, 0x87, 0x98, 0x2f, 0xbb, 0xd3
As #Encombe states this is the SHA1 hash.
From Wikipedia:
The specification does not deal with encoding of characters outside the ASCII set; to mitigate this, some BitTorrent applications explicitly communicate the encoding (most commonly UTF-8) in various non-standard ways.
And pieces is hex data, not UTF-8.

I finally found the solution to this issue. As everyone suspected it was an encoding issue.
I followed the instructions from this SO:
Calculating the info-hash of a torrent file
However, I made one small tweak:
+ (NSDictionary *)decodeDictionary:(HTData *)data
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[data getNextCharacter];
while([data showNextCharacter] != 'e')
{
NSString *key = [self decodeString:data];
id object;
if([key isEqualToString:#"info"])
{
NSInteger startIndex = [data getCurrentIndex];
NSInteger endIndex;
object = [self decodeNextObject:data];
endIndex = [data getCurrentIndex];
NSMutableData *charData = [[NSMutableData alloc] init];
for(NSInteger i = startIndex; i < endIndex; i++)
{
char c = [data charAtIndex:i];
[charData appendBytes:&c length:1];
}
NSData *hashedInfo = [charData SHA1];
[dictionary setObject:hashedInfo forKey:#"info_hash"];
}
else
{
object = [self decodeNextObject:data];
}
[dictionary setObject:object forKey:key];
}
[data getNextCharacter];
return dictionary;
}
I'm now calculating the hash within the dictionary decoder. I'm also working with individual chars and appending them directly to an NSMutableData object. This avoids the encoding issues I had and generates the correct hash.
Not the most elegant solution but I'm just glad it works.

Related

How to detect if a dictionary is empty or null

I am receiving a JSON string that I need to iterate to retrieve some objects values.
This is the structure
-meta
-objects
|_cabdriver
|_employee
|client
There are objects under the objects tree and there are also child nodes, like cabdriver and client. The child node cabdriver has also another child node called employee.
This is the way I am iterating it:
NSArray *messageArray = [json objectForKey:#"objects"];
historialServicios = [[NSMutableArray alloc]init];
// Parse and loop through the JSON
for (dictionary in messageArray) {
//datos de nivel objects
NSString * date = [dictionary objectForKey:#"date"];
NSString * origin = [dictionary objectForKey:#"origin"];
NSString * destiny = [dictionary objectForKey:#"destiny"];
NSString * rate = [dictionary objectForKey:#"service_rate"];
NSString * state = [dictionary objectForKey:#"state"];
NSString * time_service = [dictionary objectForKey:#"time_service"];
NSString * id_service = [dictionary objectForKey:#"id"];
//datos de nivel cliente
NSDictionary *level2Dict = [dictionary objectForKey:#"client"];
NSString *client_id = [level2Dict objectForKey:#"id"];
//datos de nivel cabdriver
NSDictionary *cabdriverLevelDict=[dictionary objectForKey:#"cabdriver"];
//datos de nivel employee
NSDictionary *employeeLevelDict = [cabdriverLevelDict objectForKey:#"employee"];
//datos del employee
NSString *driverName = [employeeLevelDict objectForKey:#"name"];
NSString *driverLastname = [employeeLevelDict objectForKey:#"lastname"];
NSString *driverPhone = [employeeLevelDict objectForKey:#"phone"];
NSString *driverId = [employeeLevelDict objectForKey:#"id"];
[historialServicios addObject:#{
#"time_service": time_service,
#"id_service": id_service,
#"rate": rate,
#"destiny": destiny,
#"state": state,
#"origin": origin,
#"client_id":client_id,
#"date": date,
#"driverName":driverName,
#"driverLastname": driverLastname,
#"driverPhone": driverPhone,
#"driverId": driverId
}];
NSLog(#"DESPUES DE ANADIR OBJETOS");
NSLog(#"OBJETO ANADIDO==>TIME SERVICE = %#, ID SERVICE=%#, SERVICE RATE=%#,SERVICE DATE=%#,DESTINY=%#, STATE =%#,CLIENT ID=%#, ORIGIN=%#,DRIVER NAME=%#, DRIVER LASTNAME=%#,DRIVER PHONE=%#, DRIVER ID=%#",time_service,id_service,rate,date,destiny,state,client_id,origin,driverName,driverLastname,driverPhone,driverId);
//insertamos objetos en diccionario historialServicios
}
Everything works fine if the object has all nodes but some times, the node cabdriver is empty and doesn't have the employee child node. If it is the case I get an exception is thrown and the app crashes.
How can I determined if the node employee doesn't exist and avoid to get the exception?
Thank you.
You could declare a category to deal with the [NSNull null] values that are injected into your json.
#interface NSDictionary (NilNull)
- (id)optionalObjectForKey:(id)key;
- (id)optionalObjectForKey:(id)key defaultValue:(id)defaultValue;
#end
#implementation NSDictionary (NilNull)
- (id)optionalObjectForKey:(id)key {
return [self optionalObjectForKey:key defaultValue:nil];
]
- (id)optionalObjectForKey:(id)key defaultValue:(id)defaultValue {
id obj = [self objectForKey:key];
return (obj == [NSNull null] || !obj) ? defaultValue : obj;
}
#end
Then use that instead:
NSDictionary *cabdriverLevelDict = [dictionary optionalObjectForKey:#"cabdriver"];
NSDictionary *employeeLevelDict = [cabdriverLevelDict optionalObjectForKey:#"employee"];
You haven't posted the contents of your exception, but from the looks of it, it's probably related to trying to add nil values to your new dictionary.
Then use a default value of [NSNull null] for all your data lookups that produce objects with which you will construct your final dictionary. The full lookup source will now be like this:
NSString * date = [dictionary optionalObjectForKey:#"date" defaultValue:[NSNull null]];
NSString * origin = [dictionary optionalObjectForKey:#"origin" defaultValue:[NSNull null]];
NSString * destiny = [dictionary optionalObjectForKey:#"destiny" defaultValue:[NSNull null]];
NSString * rate = [dictionary optionalObjectForKey:#"service_rate" defaultValue:[NSNull null]];
NSString * state = [dictionary optionalObjectForKey:#"state" defaultValue:[NSNull null]];
NSString * time_service = [dictionary optionalObjectForKey:#"time_service" defaultValue:[NSNull null]];
NSString * id_service = [dictionary optionalObjectForKey:#"id" defaultValue:[NSNull null]];
//datos de nivel cliente
NSDictionary *level2Dict = [dictionary optionalObjectForKey:#"client" defaultValue:[NSDictionary dictionary]];
NSString *client_id = [level2Dict optionalObjectForKey:#"id" defaultValue:[NSNull null]];
//datos de nivel cabdriver
NSDictionary *cabdriverLevelDict=[dictionary optionalObjectForKey:#"cabdriver" defaultValue:[NSDictionary dictionary]];
//datos de nivel employee
NSDictionary *employeeLevelDict = [cabdriverLevelDict optionalObjectForKey:#"employee" defaultValue:[NSDictionary dictionary]];
//datos del employee
NSString *driverName = [employeeLevelDict optionalObjectForKey:#"name" defaultValue:[NSNull null]];
NSString *driverLastname = [employeeLevelDict optionalObjectForKey:#"lastname" defaultValue:[NSNull null]];
NSString *driverPhone = [employeeLevelDict optionalObjectForKey:#"phone" defaultValue:[NSNull null]];
NSString *driverId = [employeeLevelDict optionalObjectForKey:#"id" defaultValue:[NSNull null]];
Try this here:
if( cabdriverLevelDict.allkeys.count ){
// Do something with the dict
} else {
// dict is empty
}
Basically, you need to check every single result that you get. If you don't do that, your app is open to attacks, and one attack might allow a hacker into the user's device and cause unlimited damage. Where you expect a dictionary, you might get nil, you might get a null, you might get a number, or a string, just anything. It's quite simple.
NSDictionary* dict = ...;
if (! [dict isKindOfClass:[NSDictionary class]]) dict = nil;
In Objective-C, nil objects are quite safe. You can use objectForKey [#"employee"], for example, and all that will happen is that you get nil as the result. And you could have received nil anyway.
There is no point checking for [NSNull null] only, because any other result that the server gave you will crash your app just the same. Just check for what you actually expect. Throwing away incorrect data is fine, after all the JSON deserialiser will throw away everything if just a single byte of data is wrong.
Sometimes you need to do a bit more care because servers misbehave and you have to cope with it. For example, a server supposed to return an array of dictionaries might give you just a dictionary if there is only one, so you would check for example
NSArray* arrayOfDicts = ...;
if ([arrayOfDicts isKindOfClass:[NSDictionary class]] arrayOfDicts = #[arrayOfDicts];
else if (! [arrayOfDicts isKindOfClass:[NSArray class]] arrayOfDicts = nil;
As others have pointed out, if any of the objects passed into the dictionary are nil, that will throw an exception that crashes your app. By doing the following:
[historialServicios addObject:#{
#"time_service": time_service,
#"id_service": id_service,
#"rate": rate,
#"destiny": destiny,
#"state": state,
#"origin": origin,
#"client_id":client_id,
#"date": date,
#"driverName":driverName,
#"driverLastname": driverLastname,
#"driverPhone": driverPhone,
#"driverId": driverId
}];
You're depending that all these objects (eg time_service, id_service, etc) are not nil. As you've pointed out, they can be nil, so you need to have a means of checking for each object you do. I would recommend using an NSMutableDictionary, making a category method that only adds the key/value pair if they are both not nil:
#implementation NSMutableDictionary (Util)
-(void)setObjectOrRemoveIfNil:(id)anObject forKey:(id<NSCopying>)aKey
{
if (anObject == nil)
{
[self removeObjectForKey:aKey];
}
else
{
[self setObject:anObject forKey:aKey];
}
}
#end
And then put together your dictionary like so:
NSMutableDictionary* values = [NSMutableDictionary dictionary];
[values setObjectOrRemoveIfNil:time_service forKey:#"time_service"];
[values setObjectOrRemoveIfNil:id_service forKey:#"id_service"];
//Keep going with the rest of your values.
Finally we use that dictionary like you did already:
[historialServicios addObject:values];
check the count for the dictionary
if ([cabdriverLevelDict count] == 0) {
NSLog("empty");
}
else{
// Do your stuff !!
}
if (![cabdriverLevelDict isKindOfClass:[NSNull class]] ){
//do something
}
try this
You can try
NSDictionary *employeeLevelDict = [cabdriverLevelDict objectForKey:#"employee"];
if (employeeLevelDict.count != 0)
{
// do something if dict is not empty
}
else
{
}];

iOS How To Convert NSDictionary To URL QueryString In Simplest Way [duplicate]

With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.
Currently I'm manually appending "?" followed by name value pairs joined by "&", but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn't fail entirely when it tries to connect to the URL.
This feels like something I should be able to use a pre-baked API for.... is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?
Introduced in iOS8 and OS X 10.10 is NSURLQueryItem, which can be used to build queries. From the docs on NSURLQueryItem:
An NSURLQueryItem object represents a single name/value pair for an item in the query portion of a URL. You use query items with the queryItems property of an NSURLComponents object.
To create one use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:
NSURLComponents *components = [NSURLComponents componentsWithString:#"http://stackoverflow.com"];
NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:#"q" value:#"ios"];
NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:#"count" value:#"10"];
components.queryItems = #[ search, count ];
NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10
Notice that the question mark and ampersand are automatically handled. Creating an NSURL from a dictionary of parameters is as simple as:
NSDictionary *queryDictionary = #{ #"q": #"ios", #"count": #"10" };
NSMutableArray *queryItems = [NSMutableArray array];
for (NSString *key in queryDictionary) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
}
components.queryItems = queryItems;
I've also written a blog post on how to build URLs with NSURLComponents and NSURLQueryItems.
You can create a category for NSDictionary to do this -- there isn't a standard way in the Cocoa library that I could find either. The code that I use looks like this:
// file "NSDictionary+UrlEncoding.h"
#import <cocoa/cocoa.h>
#interface NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString;
#end
with this implementation:
// file "NSDictionary+UrlEncoding.m"
#import "NSDictionary+UrlEncoding.h"
// helper function: get the string form of any object
static NSString *toString(id object) {
return [NSString stringWithFormat: #"%#", object];
}
// helper function: get the url encoded string form of any object
static NSString *urlEncode(id object) {
NSString *string = toString(object);
return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}
#implementation NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString {
NSMutableArray *parts = [NSMutableArray array];
for (id key in self) {
id value = [self objectForKey: key];
NSString *part = [NSString stringWithFormat: #"%#=%#", urlEncode(key), urlEncode(value)];
[parts addObject: part];
}
return [parts componentsJoinedByString: #"&"];
}
#end
I think the code's pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.
I wanted to use Chris's answer, but it wasn't written for Automatic Reference Counting (ARC) so I updated it. I thought I'd paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.
+(NSString*)urlEscapeString:(NSString *)unencodedString
{
CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ", kCFStringEncodingUTF8);
CFRelease(originalStringRef);
return s;
}
+(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
{
NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];
for (id key in dictionary) {
NSString *keyString = [key description];
NSString *valueString = [[dictionary objectForKey:key] description];
if ([urlWithQuerystring rangeOfString:#"?"].location == NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
}
}
return urlWithQuerystring;
}
The other answers work great if the values are strings, however if the values are dictionaries or arrays then this code will handle that.
Its important to note that there is no standard way of passing an array/dictionary via the query string but PHP handles this output just fine
-(NSString *)serializeParams:(NSDictionary *)params {
/*
Convert an NSDictionary to a query string
*/
NSMutableArray* pairs = [NSMutableArray array];
for (NSString* key in [params keyEnumerator]) {
id value = [params objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
for (NSString *subKey in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[value objectForKey:subKey],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, escaped_value]];
}
} else if ([value isKindOfClass:[NSArray class]]) {
for (NSString *subValue in value) {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)subValue,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, escaped_value]];
}
} else {
NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[params objectForKey:key],
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8);
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, escaped_value]];
[escaped_value release];
}
}
return [pairs componentsJoinedByString:#"&"];
}
Examples
[foo] => bar
[translations] =>
{
[one] => uno
[two] => dos
[three] => tres
}
foo=bar&translations[one]=uno&translations[two]=dos&translations[three]=tres
[foo] => bar
[translations] =>
{
uno
dos
tres
}
foo=bar&translations[]=uno&translations[]=dos&translations[]=tres
I refactored and converted to ARC answer by AlBeebe
- (NSString *)serializeParams:(NSDictionary *)params {
NSMutableArray *pairs = NSMutableArray.array;
for (NSString *key in params.keyEnumerator) {
id value = params[key];
if ([value isKindOfClass:[NSDictionary class]])
for (NSString *subKey in value)
[pairs addObject:[NSString stringWithFormat:#"%#[%#]=%#", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];
else if ([value isKindOfClass:[NSArray class]])
for (NSString *subValue in value)
[pairs addObject:[NSString stringWithFormat:#"%#[]=%#", key, [self escapeValueForURLParameter:subValue]]];
else
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, [self escapeValueForURLParameter:value]]];
}
return [pairs componentsJoinedByString:#"&"];
}
- (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
NULL, (CFStringRef) #"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
}
If you are already using AFNetworking (as was the case with me), you can use it's class AFHTTPRequestSerializer to create the required NSURLRequest.
[[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:#"YOUR_URL" parameters:#{PARAMS} error:nil];
In case you only require the URL for your work, use NSURLRequest.URL.
Here is a simple example in Swift (iOS8+):
private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"
private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
return components.URL
}
return nil
}
I took Joel's recommendation of using URLQueryItems and turned into a Swift Extension (Swift 3)
extension URL
{
/// Creates an NSURL with url-encoded parameters.
init?(string : String, parameters : [String : String])
{
guard var components = URLComponents(string: string) else { return nil }
components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }
guard let url = components.url else { return nil }
// Kinda redundant, but we need to call init.
self.init(string: url.absoluteString)
}
}
(The self.init method is kinda cheesy, but there was no NSURL init with components)
Can be used as
URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
I've got another solution:
http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict
+(NSString*)urlEscape:(NSString *)unencodedString {
NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)unencodedString,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
kCFStringEncodingUTF8);
return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
}
// Put a query string onto the end of a url
+(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
// Convert the params into a query string
if (params) {
for(id key in params) {
NSString *sKey = [key description];
NSString *sVal = [[params objectForKey:key] description];
// Do we need to add ?k=v or &k=v ?
if ([urlWithQuerystring rangeOfString:#"?"].location==NSNotFound) {
[urlWithQuerystring appendFormat:#"?%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
} else {
[urlWithQuerystring appendFormat:#"&%#=%#", [Http urlEscape:sKey], [Http urlEscape:sVal]];
}
}
}
return urlWithQuerystring;
}
You can then use it like so:
NSDictionary *params = #{#"username":#"jim", #"password":#"abc123"};
NSString *urlWithQuerystring = [self addQueryStringToUrl:#"https://myapp.com/login" params:params];
-(NSString*)encodeDictionary:(NSDictionary*)dictionary{
NSMutableString *bodyData = [[NSMutableString alloc]init];
int i = 0;
for (NSString *key in dictionary.allKeys) {
i++;
[bodyData appendFormat:#"%#=",key];
NSString *value = [dictionary valueForKey:key];
NSString *newString = [value stringByReplacingOccurrencesOfString:#" " withString:#"+"];
[bodyData appendString:newString];
if (i < dictionary.allKeys.count) {
[bodyData appendString:#"&"];
}
}
return bodyData;
}
Yet another solution, if you use RestKit there's a function in RKURLEncodedSerialization called RKURLEncodedStringFromDictionaryWithEncoding that does exactly what you want.
Simple way of converting NSDictionary to url query string in Objective-c
Ex: first_name=Steve&middle_name=Gates&last_name=Jobs&address=Palo Alto, California
NSDictionary *sampleDictionary = #{#"first_name" : #"Steve",
#"middle_name" : #"Gates",
#"last_name" : #"Jobs",
#"address" : #"Palo Alto, California"};
NSMutableString *resultString = [NSMutableString string];
for (NSString* key in [sampleDictionary allKeys]){
if ([resultString length]>0)
[resultString appendString:#"&"];
[resultString appendFormat:#"%#=%#", key, [sampleDictionary objectForKey:key]];
}
NSLog(#"QueryString: %#", resultString);
Hope will help :)
If you are already using AFNetwork, you can use their built in serializer to to produce an encoded URL;
NSString *baseURL = #"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"true",#"option1", data, #"option2", token, #"token", #"3.0", #"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(#"%#", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....
Note; this is an extension to an above answer. The edit queue is full so cannot be added to the existing answer.

Converting NSDictionary to XML

I need to Post data in XML format. The server accepts a specific xml format. I don't want to write the xml by hand, what i want to do is create a NSMutableDictionary populate it and from NSMutableDictionary convert it XML.
I use this:
[NSPropertyListSerialization dataWithPropertyList:data format:NSPropertyListXMLFormat_v1_0 options:0
The sample return is this:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0">
<dict>
<key>email</key>
<string>me#n.net</string>
<key>invoice_date</key>
<string>2012-10-11T10:35:09Z</string>
<key>invoice_lines</key>
<array>
<dict>
<key>product_id</key>
<integer>1021493</integer>
<key>quantity</key>
<real>1</real>
<key>retail_price</key>
<real>110</real>
</dict>
</array>
<key>payments</key>
<array>
<dict>
<key>amount</key>
<real>288.5</real>
</dict>
<dict>
<key>payment_type_id</key>
<integer>1</integer>
</dict>
</array>
The above format is not readable from the server.
The server need an xml feed like this.
<invoice>
<invoice_date>2012-10-11T10:35:09Z</invoice_date>
<email>me#n.net</email>
<invoice_lines type="array">
<invoice_line>
<product_id>1021505</product_id>
<quantity>1</quantity>
<retail_price>45</retail_price>
</invoice_line>
</invoice_lines>
<payments type="array">
<payment>
<amount>288.5</amount>
</payment>
</payments>
</invoice>
Is it possible to generate the above xml coming from a NSDictionary?
Thanks!
The short answer is: No, there is no built-in ability to do this in the Cocoa libraries.
Because you're writing rather than parsing, and presumably dealing with a limited universe of possible tags, the code to output XML is actually not that complicated. It should just be a simple method in your Invoice object, something like:
- (NSString*) postStringInXMLFormat
{
NSMutableString* returnValue = [[NSMutableString alloc] init];
if([self email])
{
[returnValue appendString:#"<email>"];
[returnValue appendString:[self email]];
[returnValue appendString:#"</email>"];
}
if([self invoice_date])
...
and so on. At the end return
[NSString stringWithString:returnValue]
There are plenty of third-party projects out there that try to generalize this process; several of them are listed in this answer:
Xml serialization library for iPhone Apps
But if all you're looking to do is create a single, stable format that your server side will recognize, and you don't have a ridiculous number of entities to convert, it's probably less work to roll your own.
Here is a recursive way to convert a dictionary to a string of XML. It handles dictionaries, arrays, and strings. Dictionary keys are the XML tags and the dictionary objects are used as values or child items in the tree. In the case of an array each element in the array is placed on the same child level with the same tag.
- (NSString*)ConvertDictionarytoXML:(NSDictionary*)dictionary withStartElement:(NSString*)startElement{
NSMutableString *xml = [[NSMutableString alloc] initWithString:#""];
[xml appendString:#"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[xml appendString:[NSString stringWithFormat:#"<%#>",startElement]];
[self convertNode:dictionary withString:xml andTag:nil];
[xml appendString:[NSString stringWithFormat:#"</%#>",startElement]];
NSString *finalXML=[xml stringByReplacingOccurrencesOfString:#"&" withString:#"&"];
NSLog(#"%#",xml);
return finalXML;
}
- (void)convertNode:(id)node withString:(NSMutableString *)xml andTag:(NSString *)tag{
if ([node isKindOfClass:[NSDictionary class]] && !tag) {
NSArray *keys = [node allKeys];
for (NSString *key in keys) {
[self convertNode:[node objectForKey:key] withString:xml andTag:key];
}
}else if ([node isKindOfClass:[NSArray class]]) {
for (id value in node) {
[self convertNode:value withString:xml andTag:tag];
}
}else {
[xml appendString:[NSString stringWithFormat:#"<%#>", tag]];
if ([node isKindOfClass:[NSString class]]) {
[xml appendString:node];
}else if ([node isKindOfClass:[NSDictionary class]]) {
[self convertNode:node withString:xml andTag:nil];
}
[xml appendString:[NSString stringWithFormat:#"</%#>", tag]];
}
}
If you wouldn't use libraries or need additional customization:
- (NSString*)convertDictionaryToXML:(NSDictionary*)dictionary withStartElement:(NSString*)startElement
{
return [self convertDictionaryToXML:dictionary withStartElement:startElement isFirstElement:YES];
}
- (NSString*)convertDictionaryToXML:(NSDictionary*)dictionary withStartElement:(NSString*)startElement isFirstElement:(BOOL) isFirstElement
{
NSMutableString *xml = [[NSMutableString alloc] initWithString:#""];
NSArray *arr = [dictionary allKeys];
if (isFirstElement)
{
[xml appendString:#"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"];
}
[xml appendString:[NSString stringWithFormat:#"<%#>\n", startElement]];
for(int i=0; i < [arr count]; i++)
{
NSString *nodeName = [arr objectAtIndex:i];
id nodeValue = [dictionary objectForKey:nodeName];
if([nodeValue isKindOfClass:[NSArray class]])
{
if([nodeValue count]>0)
{
for(int j=0;j<[nodeValue count];j++)
{
id value = [nodeValue objectAtIndex:j];
if([value isKindOfClass:[NSDictionary class]])
{
[xml appendString:[self convertDictionaryToXML:value withStartElement:nodeName isFirstElement:NO]];
}
}
}
}
else if([nodeValue isKindOfClass:[NSDictionary class]])
{
[xml appendString:[self convertDictionaryToXML:nodeValue withStartElement:nodeName isFirstElement:NO]];
}
else
{
if([nodeValue length]>0){
[xml appendString:[NSString stringWithFormat:#"<%#>",nodeName]];
[xml appendString:[NSString stringWithFormat:#"%#",[dictionary objectForKey:nodeName]]];
[xml appendString:[NSString stringWithFormat:#"</%#>\n",nodeName]];
}
}
}
[xml appendString:[NSString stringWithFormat:#"</%#>\n",startElement]];
NSString *finalxml=[xml stringByReplacingOccurrencesOfString:#"&" withString:#"&"];
return finalxml;
}
and call this as:
NSString *xmlString = [self convertDictionaryToXML:data withStartElement:startElementName];
Taking the answer from #RemembranceNN ahead and converted it to Swift.
extension Dictionary {
func getXML(withStartElement startElement: String) -> String {
(self as NSDictionary).getXML(withStartElement: "message")
}
}
extension NSDictionary {
func getXML(withStartElement startElement: String, addLeafChildAsAttributes attrFlag: Bool, dontCreateNameSpaceChilds nsFlag: Bool) -> String {
var string = ""
var startTag = "<\(startElement)"
var tagValue = ""
for (key, value) in self {
if let arr = value as? NSArray {
for item in arr {
if let itemDict = item as? NSDictionary {
tagValue.append(itemDict.getXML(withStartElement: "\(key)", addLeafChildAsAttributes: attrFlag, dontCreateNameSpaceChilds: nsFlag))
} else {
tagValue.append("<\(key)>")
tagValue.append("\(item)")
tagValue.append("</\(key)>")
}
}
} else if let nestedDict = value as? NSDictionary {
tagValue.append(nestedDict.getXML(withStartElement: "\(key)", addLeafChildAsAttributes: attrFlag, dontCreateNameSpaceChilds: nsFlag))
} else {
if attrFlag || (nsFlag && "\(key)" == "xmlns") {
startTag.append(" \(key)=\"\(value)\"")
}
if !nsFlag || "\(key)" != "xmlns" {
tagValue.append("<\(key)>")
tagValue.append("\(value)")
tagValue.append("</\(key)>")
}
}
}
startTag.append(">")
string.append(startTag)
string.append(tagValue)
string.append("</\(startElement)>")
return string
}
}

NSNull value inside a object generated by JSONKit (or similar)

I'm storing the result of JSONKit parse in a key/value database (LevelDB), but the JSON I'm downloading has some filed set to null, and this won't let you serialize the corresponding generated object (NSArray or NSDictionary), to store it as an object.
¿Any idea how can I deep iterate over a NSSomething (Dictionary or Array) to change those values?
There is a post https://github.com/johnezang/JSONKit/issues/25 that explains how to modify the framework to omit it from dictionaries and array's
Sometime it happened to me that a null value was set to something what was not recognized neither as NSNull nor as NSString. Therefore i´ve replaced all null strings in the json string before i parse it with NSJSONSerialization. I´ve read the data into a NSData object, copied it into a NSString object, replaced the null strings and copied it again into a NSData object as NSJSONSerialization expects a NSData object. Maybe you can write it shorter but it works.
Here´s the code
NSString *jsonPath = [myPath stringByAppendingPathComponent:#"myDataFile.json"];
NSMutableData *myJSON = [[NSMutableData alloc] initWithContentsOfFile:jsonPath];
NSString *jsonString = [[NSString alloc] initWithBytes:myJSON.bytes length:myJSON.length encoding:NSUTF8StringEncoding];
jsonString = [jsonString stringByReplacingOccurrencesOfString:#"null" withString:#"\"placeholder\""];
NSData * jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&jsonParsingError];
after that all previous null occurances will contain a placeholder string.
I parsed the JSON to a mutableObject [[JSONDecoder decoder] mutableObjectWithUTF8String:(const unsigned char *) [json UTF8String] length:[json lengthOfBytesUsingEncoding:NSUTF8StringEncoding] error:nil ]; and then used this code to fix it:
-(id) fixObject: (id) a
{
if([a isKindOfClass:[NSMutableDictionary class]])
{
NSMutableDictionary *ad = a;
for (NSObject *key in ad)
{
if([[ad objectForKey:key] isKindOfClass:[NSMutableDictionary class]] || [[ad objectForKey:key] isKindOfClass:[NSMutableArray class]])
[self fixObject:[ad objectForKey:key]];
else
{
if((NSNull *)[ad objectForKey:key] == [NSNull null]) [ad setObject:#"" forKey:key];
}
}
}else if([a isKindOfClass:[NSMutableArray class]])
{
NSMutableArray *ar = a;
for (NSObject *ob in ar)
{
if([ob isKindOfClass:[NSMutableDictionary class]] || [ob isKindOfClass:[NSMutableArray class]])
{
[self fixObject:ob];
}
else if((NSNull *)ob == [NSNull null])
{
[ar removeObject:ob];
}
}
}
return a;
}
If you find a better way to do this, let me know!

iOS Memory Management & NSString Initialisation

Still learning iOS development with ObjectiveC and iOS, and trying to realy understand memory management! Appreciate any advise on the snippet below, eg:
1) Analyser says there are potential memory leaks, but can't solve them?
2) Should I keep alloc and init the NSStrings in the for loop and when appended to?
Thanks
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
debugPrint ("TRACE", [[#"Lookup Char Name for = " stringByAppendingString: inCharID] UTF8String]);
NSString *tempName = [[NSString alloc] initWithFormat: #""];
if (![inCharID isEqualToString: #""])
{
// Potentially lookup multiple values
//
NSString *newName = [[NSString alloc] initWithFormat: #""];
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
NSLog( #"Lookup %i : %#", [idList count], nextID);
newName = [[NSString alloc] initWithFormat: #"C%#", nextID];
// Append strings
if ([tempName isEqualToString: #""])
tempName = [[NSString alloc] initWithFormat: #"%#", newName];
else
tempName = [[NSString alloc] initWithFormat: #"%#+%#", tempName, newName];
}
[newName release];
}
return [tempName autorelease];
}
You don't need any of the calls to alloc, release, or autorelease. Instead, use [NSString stringWithFormat:] to create instances of NSString that you don't own, and therefore don't need to manage. Also, consider using NSMutableString to simplify your code a bit, for example along the lines of the following (untested) version:
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
NSMutableString *tempName = nil;
if (![inCharID isEqualToString: #""])
{
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
[tempName appendString:#"+"]; // Does nothing if tempName is nil.
if (tempName == nil)
tempName = [NSMutableString string];
[tempName appendFormat:#"C%#", nextID];
}
}
return tempName;
}
You have 2 alloc initWithFormat for tempName. One before the loop and one within the loop.
Use ARC (Automatic Reference Counting) for new projects. For older projects it may be easy to convert them, if not ARC can be disabled on a file-by-file basis where necessary.
Using a mutable string, autoreleased convience methods and a little rerfactoring:
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
NSMutableString *tempName = [NSMutableArray array];
if (inCharID.length)
{
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
if (tempName.length == 0)
[tempName appendFormat: #"%#C", nextID];
else
[tempName appendFormat: #"+%#C", nextID];
}
}
return tempName;
}

Resources