NSJSONSerialization generating NSCFString* and NSTaggedPointerString* - objective-c

Executing NSJSONSerialization on the following json sometimes gives me NSCFString* and sometimes NSTaggedPointerString* on string values. Does anyone know why this is the case and what NSJSONSerialization uses to determine which type it returns?
jsonData = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&parseError];
{
"UserPermissionsService": {
"ServiceHeader": {},
"UserApplicationPermissions": {
"ApplicationPermissions": {
"ApplicationID": "TEST",
"Permission": [
{
"Locations": [
"00000"
],
"PermissionID": "LOGIN"
},
{
"Locations": [
"00000"
],
"PermissionID": "SALES_REPORT_VIEW"
}
]
}
}
}
}
"LOGIN" comes back as a NSTaggedPointerString*. "SALES_REPORT_VIEW" comes back is a NSCFString*. This is having an impact downstream where I'm using and casting the values.
UPDATE
Here's what I learned...
"NSTaggedPointerString results when the whole value can be kept in the pointer itself without allocating any data."
There's a detailed explanation here...
https://www.mikeash.com/pyblog/friday-qa-2015-07-31-tagged-pointer-strings.html
Since NSTaggedPointerString is a subclass of NSString showing quotes / not showing quotes should never been an issue for me as the data is used.
Thanks for everyone that commented. I'm comfortable I understand what NSJSONSerialization is doing.

Much of Foundation is implemented as class clusters. TL;DR you interact with it as an NSString but foundation will change the backing implementation to optimize for certain performance or space characteristics based on the actual contents.
If you are curious one of the Foundation team dumped a list of the class clusters as of iOS 11 here

I FIXED IT BY USING "MUTABLECOPY"
I had the same issue. For some "performance" mechanism apparently apple uses NSTaggedPointerString for "well known" strings such as "California" but this might be an issue since for some weird reason the NSJSONSerialization doesn't add the quotes around this NSTaggedPointerString type of strings. The work around is simple:
NSString *taggedString = #"California";
[data setObject:[taggedString mutableCopy] forKey:#"state"]
Works like a charm.

Related

Swift 3 NSDictionary to Dictionary conversion causes NSInvalidArgumentException

I have just converted my project from Swift 2.2 to 3.0, and I'm getting a new exception thrown in my tests. I have some Objective C code in one of my tests which reads in some JSON from a file:
+ (NSDictionary *)getJSONDictionaryFromFile:(NSString *)filename {
/* some code which checks the parameter and gets a string of JSON from a file.
* I've checked in the debugger, and jsonString is properly populated. */
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
return jsonDict;
}
I'm calling this from some Swift code:
let expectedResponseJSON = BZTestCase.getJSONDictionary(fromFile: responseFileName)
This works just fine most of the time, but I have one JSON file which causes the error:
failed: caught "NSInvalidArgumentException", "-[__NSSingleObjectArrayI enumerateKeysAndObjectsUsingBlock:]: unrecognized selector sent to instance 0x608000201fa0"
The strange thing about this is that the error is generated after the getJSONDictionaryFromFile method returns and before expectedResponseJSON in the Swift code is populated. To me, this seems to say that it's the conversion from an NSDictionary to Dictionary which is the problem. The offending JSON file is this one:
[
{
"status": "403",
"title": "Authentication Failed",
"userData": {},
"ipRangeError": {
"libraryName": "Name goes here",
"libraryId": 657,
"requestIp": "127.0.0.1"
}
}
]
If I remove the outermost enclosing [], this error goes away. I can't be the only person using an array as the top level entity of a JSON file in Swift 3, am I doing something wrong? What can I do to get around this error?
As is referenced in the comments, the problem is that getJSONDictionaryFromFile returns an NSDictionary * and my JSON input is an array. The only mystery is why this used to work in Swift 2.2! I ended up changing expectedResponseJSON to be an Any?, and rewrote my Objective C code in Swift:
class func getStringFrom(file fileName: String, fileExtension: String) -> String {
let filepath = Bundle(for: BZTestCase.self).path(forResource: fileName, ofType: fileExtension)
return try! NSString(contentsOfFile: filepath!, usedEncoding: nil) as String
}
class func getJSONFrom(file fileName: String) -> Any? {
let json = try! JSONSerialization.jsonObject(with: (getStringFrom(file: fileName, fileExtension: ".json").data(using: .utf8))!, options:.allowFragments)
return json
}
As a note to anyone who might cut and paste this code, I used try! and filepath! instead of try? and if let... because this code is used exclusively in tests, so I want it to crash as soon as possible if my inputs are not what I expect them to be.

How to handle a NSDictionary as a return type in iOS swift?

I'm trying to convert and existing, working objective c application over to swift and I'm getting a little tripped up with "closures". Here's the old working objective c block that makes returns a value from a web service:
- (IBAction)didTapSayHiButton {
[self.meteor callMethodName:#"sayHelloTo" parameters:#[self.username.text] responseCallback:^(NSDictionary *response, NSError *error) {
NSString *message = response[#"result"];
[[[UIAlertView alloc] initWithTitle:#"Meteor Todos" message:message delegate:nil cancelButtonTitle:#"Great" otherButtonTitles:nil] show];
}];
}
So here I'm getting either getting back a dictionary or response. And it works.
And here's how I'm trying to go about this with swift (the method is slightly different):
#IBAction func sayHi(sender : AnyObject) {
var params = [
"name": "Scotty"
]
meteor.callMethodName("sayHi", parameters: params, responseCallback: {
(response: Dictionary<String, String>, error: NSError) in
println("the name recieved back is: \(response)")
})
}
The error I'm getting in xCode: "NSDictionary is not a subtype of 'Dictionary'"
After looking through the swift book this is the best educated attempt that I can make. I've tried a few other things but each resulted in another type of error.
How do I make this work with swift?
Edit: I've also tried just using Dictionary and Dictionary<String, String>
I should also note that I'm using a bridging header to access objective c code (objectiveDDP). And that callMethodNamed is written in objective c as can be seen here: https://github.com/boundsj/ObjectiveDDP/blob/master/ObjectiveDDP/MeteorClient.h#L47
Update: by changing the method to:
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback:nil)
we were able to get it to work. But the second we try to add in the closure, it starts throwing the same original errors.
Try changing from using a Swift dictionary to explicitly using NSDictionary:
#IBAction func sayHi(sender : AnyObject) {
var params: NSDictionary = [
"name": "Scotty"
]
meteor.callMethodName("sayHi", parameters: params, responseCallback: {
(response: NSDictionary!, error: NSError) in
println("the name recieved back is: \(response)")
})
}
The trick, in this particular case, is to completely omit the closure's parameter types, and let the compiler figure it out. After searching around for a while, I found this post which led me to the solution.
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback: {
response, error in
let me:String = response["result"] as String!
println("called back \(me)")
})
If your not worried about accessing the closures parameters, apparently you can also using an underscore to ignore them completely:
meteor.callMethodName("sayHi", parameters:["scotty"] , responseCallback: {
_ in
// Do something here
})

Salesforce Rest Request, insert data

I'm using the salesforce iphonesdk, I wonder if anybody knows how to submit data into the salesforce object, like using INSERT ? Which method is to be used?
You need to use the create() call, passing it an array of SObjects to insert — see the documentation here.
You should probably read the Getting Started section of the reference material, especially the API Call Basics. Your question demonstrates a considerable lack of research, and I'd advise making sure you do research things yourself before asking a question on stackoverflow if you don't want it to be closed out. If you've read the documentation and still don't understand how to do something, then you're in a good position to ask for help!
Swift version:
Your controller class should conform to RestClientDelegate protocol
data is the dictionary(object) to be inserted
let data = ["Percentage__c": "1", "Amount__c":"11", "Volume__c":"111", "Promotion_Details__c": "00112233"]
let request = RestClient.shared.requestForCreate(withObjectType: "<your_table_name_>", fields: data)
RestClient.shared.send(request, delegate: self)
Another Way:
If you don't want to conform to RestClientDelegate, simply call:
RestClient.shared.send(request: request, onFailure: { (err, res) in
// some closure to handle error
}) { (any, res) in
// another closure to handle error
}
With guidance by Lacey Snr,
Found my own answer
NSMutableDictionary *dic = [[NSMutableDictionary alloc]init];
[dic setObject:#"Smith" forKey:#"Name_c"];
[dic setObject:#"5" forKey:#"PAX_c"];
SFRestRequest *request = [[SFRestAPI sharedInstance]
requestForCreateWithObjectType:#"Booking_Forms_c" fields:dic];
[[SFRestAPI sharedInstance] send:request delegate:self];

Get String from TextBox and compare

I'm trying something like my first comparison App in Obj-C and i'm already running into trouble.
Well, there is a textBox with unamebox:(id)unb and a textfield NSTextField* myOut;
Well, here was my first try:
if ([unb stringValue] == #"hello") {
[myOut setStringValue:(NSString *)#"hello dude"];
}
else {
[myOut setStringValue:(NSString *)#"What?"];
}
To my shame, this always setzt the text field to "What?"
When I try the isEqualtoString, it doesn't even do anything:
if ([unb isEqualToString:(NSString*)#"hello"]) {
[myOut setStringValue:(NSString *)#"hello dude"];
}
else {
[myOut setStringValue:(NSString *)#"What?"];
}
So, what shall I do to compare it?
by the way, I already read the links which were suggested above. If I missed anything important, I'm sorry
-isEqualToString: is a method on an NSString, not on an NSTextField. You should be getting an error from sending that message.
You want this:
[[unb stringValue] isEqualToString:#"hello"]

iPhone NSArray from Dictionary of Dictionary values

I have a Dictionary of Dictionaries which is being returned to me in JSON format
{
"neverstart": {
"color": 0,
"count": 0,
"uid": 32387,
"id": 73129,
"name": "neverstart"
},
"dev": {
"color": 0,
"count": 1,
"uid": 32387,
"id": 72778,
"name": "dev"
},
"iphone": {
"color": 0,
"count": 1,
"uid": 32387,
"id": 72777,
"name": "iphone"
}
}
I also have an NSArray containing the id's required for an item. e.g. [72777, 73129]
What I need to be able to do is get a dictionary of id => name for the items in the array. I know this is possible by iterating through the array, and then looping through all the values in the Dictionary and checking values, but it seems like it there should be a less longwinded method of doing this.
Excuse my ignorance as I am just trying to find my way around the iPhone SDK and learning Objective C and Cocoa.
First off, since you're using JSON, I'm hoping you've already found BSJSONAdditions and/or json-framework, both of them open-source projects for parsing JSON into native Cocoa structures for you. This blog post gives an idea of how to use the latter to get an NSDictionary from a JSON string.
The problem then becomes one of finding the matching values in the dictionary. I'm not aware of a single method that does what you're looking for — the Cocoa frameworks are quite powerful, but are designed to be very generic and flexible. However, it shouldn't be too hard to put together in not too many lines... (Since you're programming on iPhone, I'll use fast enumeration to make the code cleaner.)
NSDictionary* jsonDictionary = ...
NSDictionary* innerDictionary;
NSArray* requiredIDs = ...
NSMutableDictionary* matches = [NSMutableDictionary dictionary];
for (id key in jsonDictionary) {
innerDictionary = [jsonDictionary objectForKey:key];
if ([requiredIDs containsObject:[innerDictionary objectForKey:#"id"]])
[matches setObject:[innerDictionary objectForKey:#"name"]
forKey:[innerDictionary objectForKey:#"id"]];
}
This code may have typos, but the concepts should be sound. Also note that the call to [NSMutableDictionary dictionary] will return an autoreleased object.
Have you tried this NSDictionary method:
+ (id)dictionaryWithObjects:(NSArray *)objects forKeys:(NSArray *)keys
Drew's got the answer... I found that the GCC manual for the NSDictionary was helpful in a bare-bones way the other day when I had a similar question.
http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSDictionary.html