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

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.

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.

Convert any Data Type into NSData and back again

I'm working on an app where you can send either a text, image or a contact over Multipeer Connectivity to another device. It will then save in the second device's Core Data.
What I do is send the data over as an NSDictionary and convert it back again. So I am left with an NSDictionary on the receiving device. How then, can I save the object for the key of #"objectData" to Core Data?
I'd like it to work with NSString, UIImage & ABPerson.
// Create a new object in the managed object context.
Received *receivedData = [NSEntityDescription insertNewObjectForEntityForName:#"Received" inManagedObjectContext:self.managedObjectContext];
// Assign the properties based on what was input in the textfields.
// Check what type it is
NSString *dataType = [dictionary objectForKey:#"type"];
receivedData.type = dataType;
// Determine what type of data was passed over...
if ([dataType isEqualToString:#"Photo"])
{
receivedData.object = UIImageJPEGRepresentation([dictionary
objectForKey:#"object"], 0.5f);
NSLog(#"A photo saved in core data");
}
else
{
//receivedData.object = [NSKeyedArchiver archivedDataWithRootObject:[dictionary objectForKey:#"object"]];
receivedData.object = [[dictionary objectForKey:#"object"] dataUsingEncoding:NSUTF8StringEncoding];
}
// Save the managed object context.
NSError *error = nil;
[self.managedObjectContext save:&error];
I don't particularly want to do the if, else if statements to determine how to convert it core data as it would then be repeated when I display the data. Hoe else can I do it? I am currently getting errors with the NSKeyedArchiver type which I am unsure why and hence it is commented out.
Any help would be much appreciated!
Try this link for understand how its work
You can get NSData for any object conforming to the NSCoding (or NSSecureCoding) protocol:
NSData *data=[NSKeyedArchiver archivedDataWithRootObject:yourObject];
this line creates an object from NSData
TestClass *objTest=[NSKeyedUnarchiver unarchiveObjectWithData:yourData];
UIImage and NSString conform to NSCoding, but for ABPerson you'll need to use the vCardRepresentation which returns NSData.

Objective-C errors after getting the IAP product response

This code is from the Phonegap Code: IAP Plugin. The error happens on the line of the code right after the "sent js". All the elements sent to the function are non-nil except for the last one 'nil'. I even logged them out to make sure they were sent. This code is right out of the plugin (https://github.com/usmart/InAppPurchaseManager-EXAMPLE) and has not been modified except for the logging. In the debugger i saw that none of the objects were nil, so i don't understand why the error is happening.
Here is the error:
[__NSArrayI JSONRepresentation]: unrecognized selector sent to
instance 0xdc542d0
2013-02-13 23:26:17.209 GoblinSlots[4519:707] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSArrayI JSONRepresentation]: unrecognized selector sent to
instance 0xdc542d0'
here is the code:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response
{
NSLog(#"got iap product response");
for (SKProduct *product in response.products) {
NSLog(#"sending js for %#", product.productIdentifier);
NSLog(#" title %#", product.localizedTitle );
NSLog(#" desc%# - %#", product.localizedDescription, product.localizedPrice );
NSArray *callbackArgs = [NSArray arrayWithObjects:
NILABLE(product.productIdentifier),
NILABLE(product.localizedTitle),
NILABLE(product.localizedDescription),
NILABLE(product.localizedPrice),
nil ];
NSLog(#"sent js");
NSString *js = [NSString stringWithFormat:#"%#.apply(plugins.inAppPurchaseManager, %#)", successCallback, [callbackArgs JSONSerialize]];
NSLog(#"js: %#", js);
[command writeJavascript: js];
}
All the stuff to do JSON serialization seems to be already included with the Cordova plugins.
There's no need to download and install yet another JSON library.(a)
It appears that PhoneGap is in the process of switching from SBJson to JSONKit.(b)
PhoneGap is also in the process of changing all the JSON methods to use a "cdvjk_" prefix. (c)
As far as I can tell, something didn't quite go right during those changes.
What I did was edit the file Plugins/InAppPurchaseManager.m , where I made these changes:
Add the line
#import <Cordova/CDVJSON.h>
Replace the line
return [self respondsToSelector:#selector(cdvjk_JSONString)] ? [self cdvjk_JSONString] : [self cdvjk_JSONRepresentation];
with
return [self JSONString];
. (What's the right way to push this or a better bugfix back to the nice PhoneGap people?)
JSONRepresentation is a category that SBJson adds so you have to include SBJson.h in the class that uses it.

ObjC / iOS: NSCFString not NSString?

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.

Converting File Path From NSString To NSURL

I'm working through Cocoa smoothly, but this problem seems so basic it cancels out all the cool stuff I learned. :/
I have a generated file path, and it needs to be in NSURL format. From research, this is the code I wrote:
NSLog(#"Old path = %#", pathToFile);
NSURL *xmlURL = [[[NSURL alloc] init] fileURLWithPath:pathToFile];
NSLog(#"New path = %#", [xmlURL absoluteString]);
And the output:
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] Old path = file://localhost/Users/[username]/Music/iTunes/iTunes%20Music%20Library.xml
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] New path = (null)
First off, the alloc-init shouldn't even be necessary; other people seem to get away with it. In this case, if I don't alloc-init, I get an 'unrecognized selector' error on that line. Of course, now I'm just getting plain old (null).
Where did I goof?
Thanks!
The [[NSURL alloc] init] is not just unnecessary, it's invalid. fileURLWithPath: is a class method, which means you can only call it on the class object (that is, NSURL itself). It does not produce a compile error because -(NSURL *)init returns an object of type id, and does not result in a runtime error because -(NSURL *)init actually returns nil, and messages sent to nil will just cascade another nil as their return value.
This code should work:
NSString* pathToFile = #"/this/is/a/path";
NSURL* url = [NSURL fileURLWithPath:pathToFile];
I found your problem.
-[NSOpenPanel URLs] returns an array of NSURL objects, which you treat as NSString objects. That's not right. You should use the following:
NSURL* url = [[oPanel URLs] objectAtIndex:0];
The debugger could've show you that if you looked at the pathToFile variable. Make sure to check it next time. :) Hovering a variable with your mouse should get you its type.
However, remember that there are situations where you will legitimately encounter another type than the one you expected. For instance, the private NSPathStore2 class is part of the NSString cluster, and you can do everything NSString supports on NSPathStore2 objects. (If this happens and you're not too sure, check the documentation to see if the type you expect is a cluster type. That's how they're called in the documentation.)