openParentApplication:reply: error with asynchronous network call in containing app - ios

I'm getting stuck with an error when using my Watchkit Application. When I launch it, I ask the containing iOS app to get some data from network. The problem is that I get an error saying the containing app never calls 'reply()' :o But looking at my code, it should call it.
I tried to debug every step from openParentApplication to the 'reply()' call, and it seems to work well =X
Here is my code in the Watchkit extension
- (void)initDiaporamasWithSuccess:(void (^)())success andFailure:(void (^)(NSError*))failure {
NSLog(#"Ask to load diapos");
__weak typeof(self) weakSelf = self;
[WKInterfaceController openParentApplication:#{#"watchKit": #"watchKit.initDiapos"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
if (failure) {
failure(error);
}
return;
}
NSLog(#"got items : %#", replyInfo[#"diapos"]);
weakSelf.diaporamas = replyInfo[#"diapos"];
[weakSelf setDiaporama:replyInfo[#"firstDiapo"] AtIndex:0];
if (success) {
success();
}
}];
}
The result should be an NSDictionary containing an NSArray with some diaporamas basic informations, and an object (Diapo) containing the full informations of the first diaporama (e.g. self.diaporamas[0])
And here is the code in the containing app's AppDelegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// Maybe we could handle multiple watchKit extension calls that way ?
// Something like a key-value 'protocol' to run the right block of code
NSString *watchKitCall = userInfo[#"watchKit"];
NSLog(#"watchKit handled");
if ([watchKitCall isEqualToString:#"watchKit.initDiapos"]) {
[AppDelegate watchInitialObjects:^(NSDictionary *info) {
NSLog(#"Managed to get initial infos");
reply(info);
} failure:^(NSError *error) {
NSLog(#"Fail : %#", error);
reply(#{#"error": error});
}];
}
}
+ (void) watchInitialObjects:(void (^)(NSDictionary *info))success failure:(void (^)(NSError *error))failure {
NSDictionary *parameters = #{#"site" : #(14), #"limit" : #(10)};
[AppDelegate requestDiapoListWithParams:parameters success:^(NSArray *items) {
if ([items count] == 0)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
Diapo *firstDiapo = [items firstObject];
[AppDelegate requestDiapoDetailWithDiapo:firstDiapo success:^(Diapo *diapo) {
if (!diapo)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
NSDictionary *result = #{
#"firstDiapo" : diapo,
#"diapos" : items
};
success(result);
} failure:^(NSError *error) {
failure(error);
}];
} failure:^(NSError *error) {
failure(error);
}];
}
In the watchKitHandler, I call watchInitialObjects to get the diaporamas array and the first diaporama's informations.
In the watchInitialObjects, I make a first network call to get the array, and on success, I make an other network call to get the firs diaporama informations.
To make the calls and map the JSON into objects, I use RESTKit
I really don't get what could be the error =x
UPDATE
I forgot to write the error I get, here it is :
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7fcb53e12830 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
And I kept trying to know why I get this error, and I think I found it:
It seems that there is a (very little) timeout to do the work in the containing app. But I mapped the JSON data I received directly in the containing app and then, send those custom objects in the reply(). But when I removed the mapping part, it worked well !
So...that's why I think that was the problem =X
Does anybody could approve my thoughts or corrects me ?

After hours of searching and testing different codes, I finally found my problem...and it's obvious when we read the Apple documentation about 'application:handleWatchKitExtensionRequest:reply:' seriously...
here is the answer : (it's in the documentation)
The contents of the dictionary must be serializable to a property list file.
Which means that objects can ONLY be dictionaries, arrays, strings, numbers (integer and float), dates, binary data, or Boolean values
...I feel dumb ><

Related

How to call same service multiple time and store data in ios

I have a situation where I will be getting more than 25000 records from web service, it is sending using pagination technique.
so the problem is I just want to store the data so for that I am thinking to run it in a loop but in future records may vary (i.e 30000,50000 etc)
from backend I am getting on each page 10000 records,but i dont know how many times i have run the loop so how do I handle this problem?
-(void)vendorsListCalling:(NSInteger)pageIndex{
[[ServicesHandler new] callVendorDetailsServiceWithParams:#{#"pageno":#(pageIndex)} CompletionBLock:^(NSDictionary *response, NSError *error) {
if (error) {
NSLog(#"error log %#",error.localizedDescription);
}else{
NSDictionary *dict = response[#"params"][#"data"];
[vendorDictionay addEntriesFromDictionary:dict];
pageCount++;
[[NSUserDefaults standardUserDefaults] setObject:vendorDictionay forKey:#"vendorsDict"];
}
}];
}
above block is where i stuck .
Any suggestions would be more appreciated.
You can store data into sqlite database. And for recursive calling for service, you can modify the same method as,
-(void)vendorsListCalling:(NSInteger)pageIndex {
if (!loader) {
//Write code to Show your loader here
}
[[ServicesHandler new] callVendorDetailsServiceWithParams:#{#"pageno":#(pageIndex)} CompletionBLock:^(NSDictionary *response, NSError *error) {
if (error) {
NSLog(#"error log %#",error.localizedDescription);
//If it fails you need to call the service again with the same Index
[self vendorsListCalling:pageCount];
} else {
if (!response[#"params"][#"data"]) {
//Stop loader since you didn't received any data
} else {
NSDictionary *dict = response[#"params"][#"data"];
[vendorDictionay addEntriesFromDictionary:dict];
pageCount++;
// Store Data in database here //
//Call service with incremented Index
[self vendorsListCalling:pageCount];
}
}
}];
}

Send error from doNext block?

I'm using ReactiveCocoa and Overcoat/Mantle/AFNetworking to fetch data and authenticate a user from a RESTful API.
This is the code in the login view controller that manages the login button and textfields for credentials:
#weakify(self);
self.loginButton.rac_command =
[[RACCommand alloc] initWithEnabled:validCredentials
signalBlock:^RACSignal *(id input) {
#strongify(self);
return [[PFUserManager sharedManager] logInUser:self.usernameTextField.text
password:self.passwordTextField.text];
}];
// Handle errors for the login command
[self.loginButton.rac_command.errors subscribeNext:^(NSError *error) {
// Present the error message
[PFErrorAlertFactory showOVCError:error];
}];
// Take care of the signal from the request
[[self.loginButton.rac_command.executionSignals flatten] subscribeNext:^(NSNumber *success) {
#strongify(self);
[self clearTextFields];
[self.flowController controllerForMainScreen]; // Transition to "logged in state"
} error:^(NSError *error) {
#strongify(self);
[self clearTextFields];
}];
I have this method on a singleton UserManager class:
- (RACSignal *)logInUser:(NSString *)username password:(NSString *)password {
// Return a cold signal that sends next and complete when user is authenticated and error if authentication failed.
PFAPIClient *client = [[PFAPIClient alloc] initWithUsername:username password:password];
#weakify(self);
RACSignal *loginSignal = [[client rac_POST:kAuthenticationResourcePath parameters:nil] doNext:^(OVCResponse *response) {
#strongify(self);
self.currentUser = response.result;
NSError *error;
[SSKeychain setPassword:password forService:kKeychainServiceKey account:self.currentUser.username error:&error];
if (error) {
[PFErrorAlertFactory showLocalizedDescriptionOfError:error];
}
}];
return loginSignal;
}
This is all good, using this signal as a RACCommand signal for the button. I handle next, error and completed events in the login view controller and it works fine.
As you see in the UserManager code, in the doNext block, I show an error if the Keychain method returns one. I'm a little uncertain as to if this error handling belongs in this class.
It does work, the error shows as an UIAlertView, but should this UserManager class really be responsible for showing the error?
Errors coming from the rac_POST signal is handled by the login view controller, and I would like to handle the error from the Keychain method here as well. Is it possible to send an error to the subscriber of the rac_POST signal from within the doNext block? I'm missing a pointer to the subscriber though... As well, if an error occurs in the Keychain method, the signal still sends next and complete and the login is a success as far as the calling view controller knows. This is clearly not the way it's supposed to work.
Is there any other preferred way of handling this whole situation? I know that side effects in doNext blocks aren't preferred, but in this case I see no other solution as I want the UserManager to own this method and be able to set its own currentUser. Should I wrap this in a new signal and explicitly send next, complete and error instead?
Regards,
Jens
You can create SSKeychain category:
#interface SSKeychain (RACExtension)
- (RACSignal*)rac_setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;
#end
#implementation SSKeychain (RACExtension)
- (RACSignal*)rac_setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account
{
[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSError *error;
BOOL result = [SSKeychain setPassword:password forService:service account:account error:&error];
if (result) {
[subscriber sendNext:#(result)];
[subscriber sendCompleted];
} else {
[subscriber sendError:error];
}
}];
}
#end
And then use it your UserManager:
- (RACSignal *)logInUser:(NSString *)username password:(NSString *)password
{
PFAPIClient *client = [[PFAPIClient alloc] initWithUsername:username password:password];
#weakify(self);
return [[[client rac_POST:kAuthenticationResourcePath parameters:nil]
flattenMap:^RACStream *(OVCResponse *response){
#strongify(self)
return [[SSKeychain rac_setPassword:password forService:kKeychainServiceKey account:self.currentUser.username]
mapReplace:response];
}]
doNext:^(OVCResponse *response){
#strongify(self)
self.currentUser = response.result;
}]
}

Calling objective-C typedef block from swift

I'm trying to call a method from swift.
The method is in a singleton written in objective-C
the block in the header file:
typedef void(^VPersonResultBlock)(Person *person, NSError *error);
- (void)askForMe:(VPersonResultBlock)block;
and here's the implementation of that method.
- (void)askForMe:(VPersonResultBlock)block
{
if (_me) block(_me,nil);
else {
[Person getMeWithBlock:^(PFObject *person, NSError *error) {
if (!error) {
_me = (Person *)person;
block(_me,nil);
}
else if (error) {
block(nil,error);
}
else {
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation failed to retrieve the user.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Check your network connection and try again", nil)
};
NSError *error = [[NSError alloc] initWithDomain:#"VisesAsyncErrorDomain" code:-10 userInfo:userInfo];
block(nil,error);
}
}];
}
}
In Objective-C, I can call this and it autocompletes without confusion.
[[VDataStore instance] askForMe:^(Person *person, NSError *error) {
// do things with myself that aren't strange
}];
Now let's say I want to call the same method from swift. The bridging header is setup, with the header file imported, but swift's expectation is confusing.
VDataStore.askForMe(VDataStore)
This is what shows up in the autocomplete options
(VPersonResultBlock!) -> Void askForMe(self: VDataStore)
what I was hoping for, was for this to autocomplete into a closure, and although it appears to see all of the information correctly, what it's expecting isn't lining up with what objective-C seems to understand.
How do I call this properly from swift?
Directly translate your ObjC calling code to Swift is
VDataStore.instance().askForMe() {
person, error in
// do things with myself that aren't strange
}
Your problem is that askForMe is instance method but you are accessing from class object VDataStore.askForMe. Swift will give you a function object that takes an instance as input.

Objective-c passing error parameter to inside method

Its a common pattern to add an error output parameter when writing Objective-c methods.
As far as I know this is how you create a method that return an error if something is wrong:
- (void)doSomethingWithObj:(id)obj error:(NSError *__autoreleasing *)error {
BOOL success = NO;
// do somthing...
if (!success) {
*error = [NSError errorWithDomain:#"the.domain" code:0 userInfo:nil];
}
}
Now there are times when you just want that error parameter to reflect an error occurred in some other method you use inside your method, lets say:
- (void)fetchObjectInContext:(NSManagedObjectContext *)context error:(NSError *__autoreleasing *)error {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SomeObject"];
NSArray *results = [context executeFetchRequest:request error:nil];
}
So I thought ok, I'll just pass the error parameter to the inside method, like this:
- (void)fetchObjectInContext:(NSManagedObjectContext *)context error:(NSError *__autoreleasing *)error {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SomeObject"];
NSArray *results = [context executeFetchRequest:request error:error];
if (error) {
NSLog(#"error %#", error);
}
}
But this approach has two issues:
1. the if (error) check returns YES even if there is no error.
2. the log line generates this warning: Format specifies type 'id' but the argument has type 'NSError *__autoreleasing *'
So what am I doing wrong here?
There are a couple of things wrong. Firstly the NSError object should not be used to test for errors, instead use the method's return value. Therefore your first example method should return BOOL to indicate success:
- (BOOL)doSomethingWithObj:(id)obj error:(NSError *__autoreleasing *)error {
BOOL success = NO;
// do somthing...
if (!success) {
if (error) { // Check it's been passed, and if so create the error object.
*error = [NSError errorWithDomain:#"the.domain" code:0 userInfo:nil];
}
}
return success;
}
And test for results being nil, not error being non-nil:
- (void)fetchObjectInContext:(NSManagedObjectContext *)context error:(NSError *__autoreleasing *)error {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"SomeObject"];
NSArray *results = [context executeFetchRequest:request error:error];
if (!results) {
if (error && *error)
NSLog(#"error %#", [(*error) localizedDescription]); // Here is your 2. I think.
else
NSLog(#"Unknown error");
}
}
Secondly the error parameter is commonly optional (as seen in your code where you pass nil, which should be NULL actually). Therefore you need to test if it's been passed before dereferencing it (see code above).
However to answer your overall question, yes it's good to pass the error parameter along to subordinate method calls and is commonly used.
I have no idea about your 2. until you update your code... standing by. I think your 2. issue is because you need to use [error localizedDescription] with NSLog().
You are passing address of error not actual error this means &error
So you need to derefrence the error pointer.NSError *__autoreleasing * you are taking parameter as address of error.We generally do this because objective c can return only one value.But error need to be known from where we are calling the mehod so passing it as address of error will make change to error if there an error comes in calle function.
So if any error comes in below line
NSArray *results = [context executeFetchRequest:request error:error];
than it is automatically know to calle function i.e doSomethingWithObj
if (*error) {
NSLog(#"error %#", (*error).description);
}
Use
NSLog(#"error %#", (*error).description);
instead of
NSLog(#"error %#", (error).description);
you have to pass &error

iOS return bool value in block

I am trying to return a bool value from a class method I call that has a block in it. I get the error, Incompatible block pointer types sending.... How would I get around this? I just want to know if the class method I call completes with or without error...
+ (BOOL)saveSelectedDepartmentsToParse:(NSMutableDictionary *)dictionary {
NSArray *array = [dictionary allKeysForObject:#"YES"];
NSMutableArray *trimmedArray = [[NSMutableArray alloc] init];
for (NSString *string in array) {
NSString *final = [string removeAllInvalidCharacters];
[trimmedArray addObject:final];
}
NSLog(#"Array = %#", trimmedArray);
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObjectForKey:#"channels"];
[currentInstallation addObjectsFromArray:trimmedArray forKey:#"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
}
else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
}
}];
}
I just want to know if the class method I call
This is a misunderstanding of how asynchronous code works. When you are supplying a block to saveInBackgroundWithBlock:, that code is not executed straight away. It's executed at some later point by the Parse framework, and whichever part of Parse that does so would get the return value if the block were defined to have one, which it isn't. Your block isn't executed at the point at which you write it, so you can't return anything at the point at which you write it.
Your code isn't calling the block, and you can't return values to your code from it. It doesn't make sense to do so. If another part of your code wants to know when the saving has finished, you'll need to use some other mechanism than return values, such as calling a method from your block, posting a notification, or Key-Value Observing.
From the block keyword InBackground:
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
} else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
}
}];
I guess the block is called asynchronously.
If you want to get the result, you can wait here until the block is executed, but this make the saveInBackgroundWithBlock useless.
So NSNotification may be better:
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationParseSaveSucceeded object:nil];
} else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationParseSaveFailed object:nil];
}
}];

Resources