Consolidate multiple calls to observer into one single metric - ios

I have setup a WKHTTPCookieStoreObserver in my class. I want to check if my cookie is missing and if so, I want to emit a single metric and set the cookie again. The problem is that the observer is asynchronous and creates multiple metrics for the single event. How can I consolidate all the calls to the observer into one single metric?
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore {
dispatch_async(dispatch_get_main_queue(), ^{
[cookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *cookies) {
NSString *cookieValue = nil;
for (NSHTTPCookie *cookie in cookies) {
if ([cookie.name isEqualToString:cookieName]) {
cookieValue = cookie.value;
}
}
if (![cookieValue isEqualToString:expectedCookieValue]) {
[self createAndPublishCookieMismatchMetric]; // this line gets called ~300 times
[self writeCookieToWKWebsiteDataStore]; // write the missing cookie to the cookie store
}
}];
});
}
I want to merge all ~300 calls to function and emit only one metric for this event. Even though I write the cookie back to the store from the next line, it looks like there already are multiple calls in the queue waiting to be processed. What's the correct solution here?

Related

Function cannot run concurrently

I would like to make a function that only run once and cancel if is still running.
I tried it using a simple lock boolean on start/end, but sometimes it's "overlapping".
There's a better and secure way to do that?
#property (assign) BOOL lock;
- (void)myFuntion
{
if (self.lock) {
NSLog(#"(Canceled) Syncing is already running...");
return;
}
self.lock = YES;
// My Code
self.lock = NO;
}
The NSLock class should be able to help you here. I have not tried this example directly, but something like:
NSLock *myFunctionLock=[NSLock new]; // this should be a class data member/property/etc.
- (void)myFuntion
{
if (![myFunctionLock tryLock])
return; /* already running */
// My Synchronized Code
[myFunctionLock unlock];
}
We are all assuming that you are talking about concurrent programming, where you are running the same code on different threads. If that's not what you mean then you would need to explain what you DO mean, since code that runs on the same thread can only execute a function once at any particular moment.
Take a look at NSLock's tryLock function. The first caller to assert the lock gets back a TRUE, and can proceed to access the critical resource. Other callers get back FALSE and should not access the critical resource, but won't block.

Access Contacts on ButtonAction and perform Segue

Hi I'm currently working on a project for a customer where i need to access the phones contacts.
I managed to ask for the Permission to access the contacts and i m handling the two different states (granted, denied).
Apparently the customer wants the following workflow:
hit an add button
ask for permission
granted:
performs a segue to a tablewview with all contacts listed
denied: performs a segue to a differnt view and keeps asking on the inital button hit to grant access to the contacs
I ve managed the complete flow and fetched the complete data. No i m stuck with two problems:
I can't get the the ask for permission alertview pop up again (from
my understanding the user needs to set this in the Application
Settings ->App privacy settings). Is this correct?
It appears that if access is granted for the first time and i
perform a segue the tableview is empty because the data array is nil
(i can't figure out why).
- (void)addButtonTouched {
[self.addressBook accessAddressBook];
[self.activityView startActivityViewWithMessage:#"Wait a second"];
if (access) {
self.contactsArray = [self.addressBook getAllContacts];
if (self.contactsArray.count != 0) {
[self performSegueWithIdentifier:#"addEntrySegue" sender:self];
} else {
[self performSegueWithIdentifier:#"noContactsSegue" sender:self];
}
}
Am I pushing to soon to the next ViewController to fill self.contactsArray?
My other approach was to send a Notifictaion to my rootViewController when the access was granted and then perform the segue. This was the closest result i could get, but the ViewController push delayed aber 8-10 seconds.
> - (void)receivedNotification:(NSNotification *)notification {
> if (access) {
> self.contactsArray = [self.addressBook getAllContacts];
> if (self.contactsArray.count != 0) {
> [self performSegueWithIdentifier:#"addEntryrSegue" sender:self];
> } else {
> [self performSegueWithIdentifier:#"noContactsSegue" sender:self];
> }
> }
> }
Thanks in advance. I hope i got this explained well enough.
Yes. The user has to re-enable it manually from the setting. Your best bet may be to create the alternate view with instructions on how to accept it.
The array is filled with all the contacts. You want to make sure that you do two things. One: reloadData() on the table. Two: make sure you handle asynchronous operation correctly. So, the best way to handle this is running the code like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Fill array with contacts here. Run the function. Whatever you need to do goes here.
dispatch_sync(dispatch_get_main_queue(), ^{
// Reload the table. Whatever UI changes you want go here.
});
});

ios - get initial data from server on app start

I'm new to iOS development and trying to solve following problem.
In my app (which speaks with REST API) I want to make initial request to server on app start to get user info. I decided to use separate service class with singleton method. It makes request to server once and then returns user instance.
#implementation LSSharedUser
+ (LSUser *)getUser {
// make request to api server on the first call
// on other calls return initialized user
static LSUser *_sharedUser = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LSHTTPClient *api = [LSHTTPClient create];
[api getUser:^(AFHTTPRequestOperation *operation, id user) {
_sharedUser = [[LSUser alloc] initWithDictionary:user];
} failure:nil];
});
return _sharedUser;
}
#end
My question is it a proper way of initializing global data from server? As you see request is async (with AFNetworking lib) so it will return null until request is finished.
Another problem here is that once it failed (bad connection for example) user will be null forever.
update your code like this
static LSUser *_sharedUser ;
+ (LSUser *)getUser {
// make request to api server on the first call
// on other calls return initialized user
if(_sharedUser){
//this will execute only at first time
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LSHTTPClient *api = [LSHTTPClient create];
[api getUser:^(AFHTTPRequestOperation *operation, id user) {
_sharedUser = [[LSUser alloc] initWithDictionary:user];
return _sharedUser;
} failure:nil];
});
}
//this will execute 2nd time
return _sharedUser;
}
for answer to line ->
Ques 2 )Another problem here is that once it failed (bad connection for example) user will be null forever.
->once _sharedUser is initialized user will get _sharedData. but until shared data is not initialized it will return null whenever called.
Ques 1 )My question is it a proper way of initializing global data from server? As you see request is async (with AFNetworking lib) so it will return null until request is finished.
a better way is to implement your own custom delegate methods. once request is fetched or when call that do your work in that delegate method.
for 1st time: execute calling delegate methods when request is fetched or failed.
for 2nd time: after if block call delegate methods.
The basic approach requires an asynchronous design.
Say you have an asynchronous method:
- (void) loadUserWithCompletion:(void (^)(NSDictionary* user, NSError* error))completion;
You execute whatever you need to execute in the "Continuation" (the completion block):
[self loadUserWithCompletion:^(NSDictionary* params, NSError*error) {
if (user) {
User* user = [[User alloc] initWithDictionary:params];
// better we ensure we execute the following on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
self.model.user = user;
// If we utilize KVO, observers may listen on self.model.user
// which now get a notification.
// We also may to notify the user through an alert:
...
});
}
else {
// handler error
}
}];
With the asynchronous programming style you need to be more carefully when letting the user execute arbitrary tasks (say, tabbing buttons). You may consider to disable tasks which require a user model. Alternatively, display an alert sheet when the user model is not yet available.
Usually, you use some "observer" technique to get notified when the user model is eventually available. You may use KVO or completion handlers, or use some dedicated third party library which is specialized for those problems (e.g. a Promise library).
You should also let the user cancel that task at any point. This however requires a more elaborated approach, where you have "cancelable" tasks and where you hold references to these tasks in order to able to send them a cancel message.

NSOperation and NSOperationQueue

I am concurrently downloading some information from server and I am using NSOperatioQueue for the same. I have an issue. For instance if a download operation fails for some reason I don't want to remove that operation from queue.
Right now even if its a failure as soon as it gets a response back from server the operation is removed from queue.
Is there any way to tell the queue that a particular operation is not logically finished and it should keep it in queue?
In my case, I am downloading a set of information. For example fetching all places in a County and then all houses for each county. So in certain cases county cannot be downloaded if the user is not logged in with a valid token. In that case server returns a failure message. I want to keep such items in queue so that I can try again when user logs in to the app.
Sample Code
self.downloadQueue.maxConcurrentOperationCount = 1;
for(Campaign *campaign in campaigns)
{
isContentUpdated = false;
if(self.operation)
self.operation = Nil;
self.operation = [[DownloadOutlets alloc] initWithCampaign:campaign];
[self.downloadQueue addOperation:operation];
}
where downloadQueue is an NSOperationQueue and DownloadOutlets extends NSOperation.
Thanks
You should not be keeping your failed operations in the queue. The failed operation has performed its task. You should have your operation controller listen to the state of the operations, via completionBlock or otherwise, and decide what to do next. If it comes to the determination that the operation has failed but a similar operation should be retried, it should add another operation to perform the task again.
Another approach would be to retry your download inside the operation until success, and only then end the operation. This is not optimal design, however, because the operation does not, and should not, have all the information required to decide whether to retry, inform the user, etc.
You shouldn't keep operations that failed in queue, but use the queue for serial fetching data, and stop queueing if the operation fails :
#implementation DataAdapter
// ...
-(void)setup{
// weak reference to self to avoid retain cycle
__weak DataAdapter* selfRef= self;
// create a block that will run inside the operation queue
void(^pullCountriesBlock)(void)= ^{
[[DownloadManager instance] fetchAllCountriesWithCompletionBlock:^(Result* result){
if(result.successful){
// on success
[selfRef didFetchDataForAction:action];
}else{
// on failure
[selfRef failedToFetchDataForAction:action];
}
};
self.actions= [NSMutableArray new];
[self.actions addObject:[DownloadAction actionWithBlock:pullCountriesBlock];
// add other actions
// ...
[self fetchData];
}
}
-(void)fetchData{
if(self.currentActionIndex >= self.actions.count){
[self finishedFetchingData];
return;
}
[self fetchDataForAction: self.actions[self.currentActionIndex] ];
}
-(void)fetchDataForAction:(DownloadAction*)action
[self.myOperationQueueImplementation enqueueOperationWithBlock:action.block];
}
If the download is successful, just enqueue the next action(increment the currentActionIndex and call fetchData). If it fails, you can act accordingly. What I'd do is start listening to interesting NSNotificationCenter events before calling fetchData the first time. You could listen to UserDidLogInNotification or any other that may allow the queue to continue running the downloads.

How can I wait for a NSURLConnection delegate to finish before executing the next statement?

This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}

Resources