ObjC / iOS: NSCFString not NSString? - objective-c

I'm making a small iOS application which requests a JSON file with ASIHTTPRequest and I want it to be parsed by JSONKit. I'm using the following code:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
// yada yada
NSURL *url = [NSURL URLWithString:#"http://localhost/file.json"]; // the file is a valid Json
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setTimeOutSeconds:20]; // Response takes too long on local machine
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
responseString = [request responseString]; // request type: ASIREQUEST, responseString is declared as NSString
NSLog(#"%# \n", [responseString class]); // NSCFString
NSDictionary *deserializedData = [responseString objectFromJSONString];
}
However, I'm seeing the following exception when the application runs:
[7646:207] -[NSCFString objectFromJSONString]: unrecognized selector sent to instance 0xdba0000
[7646:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-`[NSCFString objectFromJSONString]: unrecognized selector sent to instance 0xdba0000'`
What could be causing this exception? Why is it showing that I'm using an NSCFString here, even though I'm specifying an NSString?

NSString is something known as a class cluster. What this basically means is that when you construct an NSString you will get back something that is guaranteed to behave like an NSString as defined in its documentation, but could be implemented by some other class. In this case NSCFString is the type the system decided to return.
Anything that takes an NSString will take anything that is part of that cluster.
From looking at your code, ASI must define objectFromJSONString as part of a category on NSString. This will work perfectly fine with the clustering system, are you loading a static library that includes ASI? If so you need to add -all_load and -ObjC to your linker flags in order to get categories working from external libraries.
Edit:
Based on your comments then your solution is making sure the JSON library is included in your project. I'm assuming the objectFromJSONString was copy/pasted from some sample somewhere. It is part of the JSONKit library. Make sure both JSONKit.m and JSONKit.h are present in your project.
Update:
JSONKit is included (both .h and .m) but still the same error...
you are right, i just copied it because it seemed to do its job :)

You've included JSONKit.h, but you forgot to include JSONKit.m in the project. It compiles fine, but the implementation isn't there at runtime.

I had same problem because I enabled ARC and JSonKit doesn't support it after rectifying it worked.
Also when you include JSonKit.h and .m files please make sure you check target box too.

JSONKit declares a category called JSONKitDeserializing on NSString in order to allow you to call objectFromJSONString. Make sure you've included JSONKit.h in your source file where you are trying to use objectFromJSONString.

Related

+[NSData bookmarkDataWithContentsOfURL:]: unrecognized selector sent to class

I am trying to resolve an alias file's original path using Objective-C(or maybe C++; it's an .mm file). Not being very much familiar, I am somehow missing + and - methods' usage. I am aware of them being class and instance methods respectively, but in practice, the following the code, with the indicated lines give me following warning and error(at build):
Class method '+bookmarkDataWithContentsOfURL:' not found (return type defaults to 'id')
-
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSData bookmarkDataWithContentsOfURL:]: unrecognized selector sent to class 0x7fff88942cb8'
with 0x7fff88942cb8 being the NSData address as per lldb.
Which files should I make the changes in, to get bookmarkDataWithContentsOfURL:error: and URLByResolvingBookmarkData to work?
void *pathclass::resolveAliasFromURL(const char *filepath) const
{
NSError *error = nil;
NSString *filepathh = [[NSString alloc] initWithUTF8String:filepath];
NSData *bookmarkk = [NSData bookmarkDataWithContentsOfURL:filepathh]; /*problematic line*/
BOOL isstale = NO;
NSURL *actual = [NSURL URLByResolvingBookmarkData:bookmarkk bookmarkDataIsStale:isstale error:error];/*another problematic line, but build fails already*/
NSString *urlString = [actual absoluteString];
NSLog(#"%#",urlString);
}
If there are any other faults, please point out.
Your call to bookmarkDataWithContentsOfURL: is wrong in a few ways:
The signature looks like this:
+ (NSData *)bookmarkDataWithContentsOfURL:(NSURL *)bookmarkFileURL error:(NSError * _Nullable *)error;
First, the first parameter is of type NSURL*, not NSString*. Next, you miss off the error parameter completely (despite defining a variable for it). Lastly, the method is a class method on NSURL not NSData (NSData* is the return type).
So, first, make your file path into an NSURL*:
NSURL* bookmarkUrl = [NSURL URLWithString:filepathh];
Then, call the function using the proper arguments:
NSData *bookmarkk = [NSURL bookmarkDataWithContentsOfURL:bookmarkUrl error:&error];
You should check the returned value against nil - if it's nil, then an error occurred, and the error information will be contained inside error.
The documentation is quite helpful.
Your call to URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error: has similar problems: you are missing several parameters, the first parameter should be NSURL, etc. Again, the documentation should help.

Passing NSArray of custom objects as NSData via WatchConnectivity's sendMessageData

Once a WKInterfaceController's didAppear function is fired, I send an empty NSData to WCSession's default session with the sendMessageData callback function:
// WKInterfaceController
NSData *emptyData = [[NSData alloc] init];
[[WCSession defaultSession] sendMessageData:emptyData replyHandler:^(NSData *replyMessageData) {
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:replyMessageData];
} errorHandler:^(NSError *error) {
NSLog(#"WATCH: Error from replyData %#", error);
}];
The emptyData NSData object is sent because sendMessageData: is a non-null argument. I only use it to be able to fire WCSession's Delegate method, didReceiveMessageData on the iOS app. Then the replyHandler in that very function sends the appropriate data back to the replyHandler to the WKInterfaceController.
// UITableViewController
- (void)session:(WCSession *)session didReceiveMessageData:(NSData *)messageData replyHandler:(void (^)(NSData * _Nonnull))replyHandler
{
[self loadData:nil onSuccess:^(NSArray *tips) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:tips];
replyHandler(data);
}];
}
The problem I'm having is that I get a crash on the following line in the WKInterfaceController
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:replyMessageData];
Here's the error I get:
* Terminating app due to uncaught exception
'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (Tip) for key (NS.objects); the class may be defined in source
code or a library that is not linked'
What I've found so far:
The NSArray I'm trying to pass is made of custom objects (Tip.m). I know that all of the objects within the NSArray must conform to the NSCoding protocol (How to convert NSArray to NSData?), which I have done properly in my opinion. I've encoded and decoded every variable and object within the object with initWithCoder and encodeWithCoder.
My Tip.m object should be added to my WatchKit Extension (NSInvalidUnarchiveOperationException cannot decode object error in Apple Watch extension). Adding the Tip.m file only gives me: "Undefined symbols for architecture i386" from other objects.
Sorry for the long post but I've tried everything to find a solution to this problem, without success. Hope this helps more people that are having issues with WatchConnectivity Framework.
I solved this temporarily by using didReceiveMessage (the NSDictionary version instead of the NSData).
I sent a manually created NSDictionary of a single NSArray that held regular NSStrings of my previous custom objects.
I have the same scenario and reached the same problem. After some searching (without any luck) and experimenting, I've solved it by adding the -all_load flag to the linker flags in the extension target.

'unrecognized selector sent to instance'?

I'm new to Objective C and having trouble understanding why I am getting this error. I've checked other similar questions, but haven't been able to resolve the issue.
The error is "-[NSConcreteMutableData base64Decoded]: unrecognized selector sent to instance 0x6e15610"
Here is a snippet of the problem code, where the call to base64Decoded is causing the crash.
#import "DDData.h"
- (NSString *)decodeBase64:(NSString *)input
{
NSData* dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
And in DDData.h:
#import <Foundation/Foundation.h>
#interface NSData (DDData)
- (NSData *)base64Decoded;
#end
and DDData.m:
#implementation NSData (DDData)
- (NSData *)base64Decoded
{
// Excluding function code, as it never gets to here
}
#end
Just a note that the Project has ARC enabled. Any ideas as to what might be the issue here? Thanks.
EDIT: I have adjusted the above code to help debug the error:
NSData* dataDecoded = [input dataUsingEncoding:NSUTF8StringEncoding];
[dataDecoded base64Decoded];
dataDecoded gets a value from dataUsingEncoding, it is not nil when the call to base64Decoded is made. When I step over to the called to base64Decoded, it crashes.
Insert a break point in your code and step through it and you'll see exactly where it breaks.
You may also want to check that the DDData files are properly included in your project by looking at the target membership of those files, the .m should be ticked.
SimonH pointed out the solution correctly in one of the sub-comments. I was having the same problem with a custom method i defined in a NSData category. The solution better explained:
Make sure the .m file is included in the projects Build Phases->Compile Sources.
Right click on the .m file in the project navigator and click "Show file Inspector". Under File Inspector make sure you check the target you are building for otherwise it wont be included and the calling that method will crash.
You get that kind of message if you try to execute an undefined method on an object. Try it like this:
NSData *dataDecoded = [[input dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
There is no base64Decoded method as far as I know, but there is base64EncodedString. So when you send the base64Decoded message to your NSData object, it isn't recognized because it's simply not there.
Follow below debugging steps to resolve it.
Put breakpoints in your code and check step by step where that is breaking.
Also, check if you have added DDData.m source file in your project target properly.
You should also check the object presence before using it. Check below sample code.
- (NSString *)decodeBase64:(NSString *)input {
if(input) {
NSData *utfData = [input dataUsingEncoding:NSUTF8StringEncoding];
if(utfDFata) {
NSData* dataDecoded = [utfDFata base64Decoded];
return [NSString stringWithUTF8String:[dataDecoded bytes]];
}
}

Crash with SBJSON library

following code is copied from Stig Brautaset JSON library - I removed the comments to be more clear.
#interface NSString (NSString_SBJSON)
- (id)JSONFragmentValue;
- (id)JSONValue;
#end
I have a crash when I call:
NSString *jsonString = [request responseString];
...
NSDictionary *results = [jsonString JSONValue];//here is the crash with invalid selector.
I added this:
if (![jsonString respondsToSelector:#selector(JSONValue)])
{
NSLog(#"fix this!!!\n");
}
and the message is shown. Do you have any idea why this function in not called?
Thanks!
EDIT: I compile JSON framework as a static lib. The call is made in the same lib.
When I compile it as exe it runs without any problem.
You need to both link your static library and your final executable with the -ObjC linker option.
The -JSONValue method is implemented as a category on the NSString class in the file NSString+SBJSON.m file. Because of how the dynamic runtime works in Objective-C, categories defined in static libraries don't get loaded up unless you link with the -Objc flag. If the category doesn't get loaded, then dynamic binding fails, and you get the dreaded "doesn't respond to selector" exception.

How to handle the exceptions

NSString *stringURL=[[NSString alloc] init];
stringURL=[stringURL stringByAppendingFormat:kSearchBarURL,text.text];
NSString *url = [NSString stringWithString:stringURL];
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
NSString *responseids = [[NSString alloc] initWithData:data1 encoding:nil];
responseids = [responseids stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"\n\r\t"]];
SBJsonParser *parser=[SBJsonParser new];
NSData *data = [parser objectWithString:responseids error:nil];
NSMutableDictionary *searchURL = (NSMutableDictionary*)data;
I coded this i did not handle the exception for my code.
doing json and calling the service url and loading the data.
the application get crashes when my service is too low or no service found.
How to handle the exception for my code here..
Do I use #try #catch.
or
NSURLConnection for error handling.
Please help me out .
Thanks in advance.
Whenever an API makes use of NSError, you should use this rather than wrapping things up in a try…catch block as NSError is designed exactly for this. I usually reserve #try for things where I am really not able to anticipate what might go wrong. If NSError is in the mix, then you know that there is a potential for a problem that you should be handling gracefully.
More generally, your code has some strange stuff in it. You alloc init an empty NSString and then create a new string by appending a format. Not sure why you don't just use [NSString stringWithFormat]. Once you have the string, you can create the URL without the NSString *url bit.
You're also using a synchronous call to what I assume is a remote server. This has the potential to bog down your application if/when the server is not available. You're also not telling NSString what kind of encoding you expect your string to be in when it reads it from NSData. A better method depending on your server side would be to use NSString's stringWithContentsOfURL:usedEncoding:error: method. I would recommend that you use the various NSURLConnection callbacks. Have a look at the URL Loading System Programming Guide on Using NSURLConnection The NSURLConnection delegate methods are the ones you want to implement to provide this asynchronous processing.
For your trimming, you might be interested in the +whitespaceAndNewlineCharacterSet method on NSCharacterSet.
Finally, for your JSON parsing, you might be interested in the category that the SBJSON code adds to NSString, particularly -JSONValue which will give you the dictionary or array representation (as appropriate) of the NSString when parsed as JSON by SBJSON.
HTH