How to set a variable within calling a method - eg [... { here }] - ios

I have this piece of code and tries to set the variable NotAllSaved to true but after call to the saveObject returns, the variable is false again.
NotAllSaved=false
[healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error){
if (!success){
if (error.code==HKErrorAuthorizationDenied) {
NotAllSaved=true;
} else {
...
}
}
}];
if (NotAllSaved) {
// Does never come here
}
How can I set the variable so I can handle the error outside the call to SaveObject? If I try to popup an alert there, the app takes a lot of time before showing the popup.
-
Added:
Thank you Lytic and Saheb Roy, your answers (and some additional Googling) solved my problem, so the solution that works for me is this:
__block bool NotAllSaved=false;
dispatch_group_t theWaitGroup = dispatch_group_create();
dispatch_group_enter(theWaitGroup);
[HDBI.healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error){
if (!success){
if (error.code==HKErrorAuthorizationDenied){
NotAllSaved=true;
} else {
.. .
}
}
dispatch_group_leave(theWaitGroup);
}];
dispatch_group_wait(theWaitGroup, DISPATCH_TIME_FOREVER);
if (NotAllSaved) {

This is a behavior of using blocks asynchronously.
The code contained in withCompletion:{} is actually not executed until the operation queue runs the block. Which typically will happen AFTER the check if(NotAllSaved)
The error must be handled within the completion block (or could call an error handler)
For Example:
NotAllSaved=false
[healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error)
{
if (!success)
{
if (error.code==HKErrorAuthorizationDenied)
{
NotAllSaved=true;
}
else
{
...
}
}
if (NotAllSaved)
{
// Will execute now
}
}];

This is because this isnt a method, its a block starting with the caret symbol ^, when you want to access any local variable inside a block, the block makes a copy of the variable or rather think of it like this, inside the block the variable is read only, to make the variable read write use this
__block BOOL NotAllSaved = false;
Now setting this by using this __block (2 underscores) you call the variable by its reference

Related

How can I throw an NSError from a Swift class and catch it in an Objective-C class?

I need to implement a try-catch structure in Objective-C to handle Swift thrown NSErrors.
I've written an NetService manager with Swift code and I am implementing it on an already existent Objective-C UI.
However, whenever I throw an error from my Swift class, the try-catch structure fails to catch the error and proceeds to the finally block.
Swift error definition:
enum NEONetServiceErrors: Int
{
case requestMadeWithoutIp
}
struct NEONetServiceErrorStrings
{
let requestMadeWithoutIp = ["NEONetService Error: Request made without IP": NEONetServiceErrors.requestMadeWithoutIp]
}
Swift error throwing:
#objc func requestHelloPage() throws
{
if serviceiPAddress != nil
{
makeHelloRequest()
}
else
{
throw NSError(domain: errorStrings.domain, code: NEONetServiceErrors.requestMadeWithoutIp.rawValue, userInfo:errorStrings.requestMadeWithoutIp)
}
}
Objective-C properties:
#property NEONetServiceManager* netServiceManager;
#property NSError* _Nullable __autoreleasing * _Nullable netServiceError;
Objective-C error handling:
- (IBAction)pressUpdateButton:(id)sender
{
#try
{
[self.netServiceManager requestHelloPageAndReturnError: self.netServiceError];
}
#catch (NSException *exception)
{
NSLog(#"Throwing");
}
#finally
{
NSLog(#"Finally");
}
}
Output:
2019-10-18 14:47:03.289268-0300 NEOFirmUpdate[16533:2389800] Start
2019-10-18 14:47:03.292696-0300 NEOFirmUpdate[16533:2389800] Finally
Could you help me figure out what I am doing wrong with my error-handling?
The problem is that a Swift Error / Objective-C NSError is not an NSException. You are configured to catch NSExceptions but that is irrelevant.
The way to "catch" an NSError in Objective-C when Swift throws an Error is by indirection with the NSError** parameter, just as it always has been.
NSError* err = nil;
BOOL ok = [self.netServiceManager requestHelloPageAndReturnError:&err];
if (ok) {
// proceed normally
} else {
// you got an error, it is sitting in `err`
}
(Notice how Swift supplies a BOOL result exactly so you can implement the correct pattern.)
That's because you're using objective-c exceptions there, and not actually checking for an error. To check for errors in objective-c, you pass a reference to your pointer and your function will fill that error out if there was an issue.
NSError *serviceError = nil;
[self.netServiceManager requestHelloPageAndReturnError:&serviceError];
if (serviceError) {
// there was a problem
}
If this is an asynchronous call, you'll need to do this in a closure instead:
NSError *serviceError = nil;
[self.netServiceManager requestHelloPage:^(NSError *error) {
if (error) {
// there was a problem
}
}];
In your Objective-C code you are Catching and NSException, not an NSError
Swift automatically bridges between the Error type and the NSError
class. Objective-C methods that produce errors are imported as Swift
methods that throw, and Swift methods that throw are imported as
Objective-C methods that produce errors, according to Objective-C
error conventions.
for more information you can click here

Waiting for network call to finish

What I'm trying to achieve is to make a network request and wait for it to finish, so that I can make a decission what should be apps next step.
Normally I would avoid such solution, but this is a rare case in which codebase has a lot of legacy and we don't have enough time to apply necessary changes in order to make things right.
I'm trying to write a simple input-output method with following definition:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;
The thing is that in order to perform some validation inside this method I need to make a network request just to receive neccessary information, so I'd like to wait for this request to finish.
First thing that popped in my mind was using dispatch_semaphore_t, so I ended up with something like this:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
dispatch_semaphore_signal(sema);
} failure:nil];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return [self.paymentCards firstCardForStatus:status];
}
Everything compiles and runs, but my UI freezes and I actually never receive sempahore's signal.
So, I started playing with dispatch_group_t with exactly the same results.
Look like I might have some problems with where code gets executed, but I don't know how to approach this and get the expected results. When I try wrapping everything in dispatch_async I actually stop blocking main queue, but dispatch_async return immediatelly, so I return from this method before the network request finishes.
What am I missing? Can this actually be acheived without some while hacks? Or am I trying to fight windmills?
I was able to achieve what I want with the following solution, but it really feels like a hacky way and not something I'd love to ship in my codebase.
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
flag = YES;
} failure:nil];
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
return [self.paymentCards firstCardForStatus:status];
}
I guess fetchLocationProviderStatusFor:completion:failure: calls those callbacks in main queue. That's why you get deadlock. It's impossible. We can't time travel yet.
The deprecated NSURLConnection.sendSynchronousRequest API is useful for those instances when you really can't (or just can't be bothered to) do things properly, like this example:
private func pageExists(at url: URL) -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 10
var response: URLResponse?
try! NSURLConnection.sendSynchronousRequest(request,
returning: &response)
let httpResponse = response as! HTTPURLResponse
if httpResponse.statusCode != 200 { return false }
if httpResponse.url != url { return false }
return true
}
Currently, your method causes work to be done on the main thread, which freezes the UI. Your solution works, but it would be best to change the method to include a completion block. Then, you could call the completion block at the end of the async block. Here's the example code for that:
- (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
completion([self.paymentCards firstCardForStatus:status]);
} failure:nil];
}

iOS completion block not returning control

I have written a lot of completion blocks but not sure why this is happening. The control of a block based function should not go forward if we call the block with the appropriate argument. But in my case it is doing so.
- (void) validateFormWithCompletion: (void(^)(BOOL valid)) completion
{
if (! [NetworkConstant appIsConnected])
{
[[AppThemeManager sharedInstance] showNoInternetMessage];
completion(NO);
}
emailIdTF.text = [emailIdTF.text trimWhiteSpaceAndNextLine];
if (emailIdTF.text.length == 0)
{
[[AppThemeManager sharedInstance] showNotificationWithTitle:#"Incomplete" subtitle:#"Please fill in a valid email id" duration:durationForTSMessage withTypeOfNotification:notificationWarning];
completion(NO);
}
else
{
completion(YES);
}
}
If there is no internet connection, the control should return from the first occurrence of completion(NO);. But instead it moves forward to email length check. Am I doing something wrong here?
If I understand your question, you need to add a return.
if (! [NetworkConstant appIsConnected])
{
[[AppThemeManager sharedInstance] showNoInternetMessage];
completion(NO);
return;
}
The return prevents the rest of the method from being executed if there is no network connection.
It also seems like there is no reason to be using a completion handler. There is no asynchronous processing inside your method.
Most probably the other times you called completion blocks they were placed within other completion blocks, called by asynchronous tasks, which is not the case in the given example. Thus using an completion block does not make sense how I understand your example.
- (BOOL) validateFormWithCompletion:(void(^)(BOOL valid)) completion
{
if (! [NetworkConstant appIsConnected]) {
[[AppThemeManager sharedInstance] showNoInternetMessage];
return NO;
}
emailIdTF.text = [emailIdTF.text trimWhiteSpaceAndNextLine];
if (emailIdTF.text.length == 0) {
[[AppThemeManager sharedInstance] showNotificationWithTitle:#"Incomplete" subtitle:#"Please fill in a valid email id" duration:durationForTSMessage withTypeOfNotification:notificationWarning];
return NO;
} else {
return YES;
}
}

How to stub method with response block using OCMock

I have a method that calls a request with a response block inside. I want to stub my response and return fake data. How can this be done?
-(void)method:(NSString*)arg1{
NSURLRequest *myRequest = ...........
[self request:myRequest withCompletion:^(NSDictionary* responseDictionary){
//Do something with responseDictionary <--- I want to fake my responseDictionary
}];
}
- (void)request:(NSURLRequest*)request withCompletion:(void(^)(NSDictionary* responseDictionary))completion{
//make a request and passing a dictionary to completion block
completion(dictionary);
}
If I understand you correctly, you want to mock request:withCompletion: and call the passed completion block.
Here is how I have done this in the past. I've adapted this code to your call, but I cannot check it for compilation errors, but it should show you how to do it.
id mockMyObj = OCClassMock(...);
OCMStub([mockMyObj request:[OCMArg any] completion:[OCMArg any]])).andDo(^(NSInvocation *invocation) {
/// Generate the results
NSDictionary *results = ....
// Get the block from the call.
void (^__unsafe_unretained completionBlock)(NSDictionary* responseDictionary);
[invocation getArgument:&callback atIndex:3];
// Call the block.
completionBlock(results);
});
You will need the __unsafe_unretained or things will go wrong. Can't remember what right now. You could also combine this with argument captures as well if you wanted to verify the passed arguments such as the setup of the request.

Why does saveInBackgroundWithBlock only work *once* in my Parse-enabled class?

I have a class with this code which gets called a few times per minute on average, and only runs on the main thread:
PFObject* eventObj = [PFObject objectWithClassName:#"AdminConsoleEvent"];
eventObj[kACParseEventName] = event;
eventObj[kACParseEventUrgency] = urgency;
if( param1 )
eventObj[kACParseEventParam1] = param1;
eventObj[kACParseEventPointerToAdminConsole] = self.adminConsole;
=== [eventObj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
+++ if( !succeeded ) {
//here
}
}];
If I put a breakpoint where === is, I see that every time eventObj is how I expect... a non-nil object with valid information on it.
If I put a breakpoint where +++ is, then I see that it gets hit exactly only once -- the first time this code is called. If I look on the Parse data browser (online), sure enough, only the first object gets saved (immediately)! The rest never show up.
Why the heck isn't the block (+++) ever running for subsequent calls? Why aren't the other objects being saved?
OK this fixed it...
[PFObject saveAllInBackground:#[eventObj, self.adminConsole] block:^(BOOL succeeded, NSError *error) {
I assume that this is because there was a circular reference: self.adminConsole had a reference being added to it for eventObj, and eventObj had a reference being added to it for self.adminConsole. For whatever reason, that breaks Parse for me if I use saveInBackground directly on the objects.

Resources