Reloading json data with nstimer goes crazy after a minute - ios

My log window goes crazy, constantly reloading after a minute. Did I use NSTimer at the end correctly? Is performSelectorOnMainThread correct use? Thanks
-(void)URL
{
dispatch_async(myQueue, ^{
NSString* myURL= #"https://url.json";
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:myURL]];
NSLog(#"url: %#", myURL);
if ((!data) || (data==nil))//v1.2
{
NSLog(#"url: loading ERROR");
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(URL) userInfo:nil repeats:YES];
}else
{
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
}
});
}
- (void)fetchedData:(NSData *)responseData {
NSLog=#"fetch";
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSNumber* spot= [json objectForKey:#"amount"];
float spotfloat = [spot floatValue];
timer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(URL) userInfo:nil repeats:YES];
}

set the repeats to NO or set the actual maximum timer. it's an infinite loop if you don't set the maximum time.
[NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(URL) userInfo:nil repeats:NO];
- (void)fetchedData:(NSData *)responseData {
NSLog=#"fetch";
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSNumber* spot= [json objectForKey:#"amount"];
float spotfloat = [spot floatValue];
if (timer < 60)
{
timer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(URL) userInfo:nil repeats:YES];
}
else
{
[timer invalidate];
}
}

A couple of observations:
You are repeatedly creating new repeating timers, but never calling invalidate on the old ones and as a result, you will undoubtedly end up with a cascade of timer events as the old ones will keep firing. (A repeating timer will continue firing until you explicitly call invalidate. Simply nil-ing or replacing the object in your class property/ivar is insufficient.) If you want to replace a repeating timer, make sure to invalidate the old one first (otherwise the old will keep firing).
Generally, though, you'd either create a repeating timer once and let it keep firing, or you'd create non-repeating timer, and at the end of the method, schedule another non-repeating timer. Given that you're dealing with network requests that may take an indeterminate amount of time (i.e. it could still be trying the previous request by the time the next repeating timer fires), I'd suggest using non-repeating timers that schedule the next one at the end of the method. Or, given the hassles in creating timers in background queues, just use dispatch_after.
BTW, timers need a run loop to function properly, so your attempt to create a timer in myQueue if the request failed is unlikely to succeed. If you wanted to schedule a timer from the background queue, the easiest way to do this would be to create the timer with timerWithTimeInterval and then manually add it to the main run loop. (The alternative, of creating a new background NSThread and scheduling your timer on that, is overkill for such a simple problem.)
As an aside, I'd be inclined to run fetchedData on the background queue, too. Just dispatch the UI and/or model update back to the main queue. That way you minimize how much you're doing on the main queue.
I'm unclear as to how you're determining when to stop this process. Maybe you've concluded you don't need that at this point, but I'd suggest you include some cancellation logic, even if you don't avail yourself of it at this point.
You're using a shorter delay if you encounter network problem. That might be a dangerous solution because if your server failed because it was overwhelmed with requests coming in every 15 seconds from too many users, having clients start sending requests every 5 seconds might only make the situation worse.
Frankly, in an ideal scenario, you'd look at the exact cause of the error, and decide the correct behavior at that point. For example, if a request failed because the device doesn't have Internet connectivity, use Reachability to determine when the network is restored, rather than blindly trying every five seconds.
You're sending requests ever 5-15 seconds. If you really need that sort of interactivity, you might consider a different architecture (e.g. sockets). That's beyond the scope of this question, but something for you to research/consider.
Anyway, you might consider something like:
- (void)viewDidLoad
{
[super viewDidLoad];
self.myQueue = ...
[self schedulePollWithDelay:0];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.stopPolling = YES;
}
- (void)schedulePollWithDelay:(CGFloat)delay
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), self.myQueue, ^{
[self retrieveData];
});
}
-(void)retrieveData
{
if (self.stopPolling)
return;
NSString* myURL= #"https://url.json";
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:myURL]];
if (!data) {
NSLog(#"url: loading ERROR");
} else {
[self processFetchedData:data];
}
[self schedulePollWithDelay:15];
}
- (void)processFetchedData:(NSData *)responseData {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSNumber* spot = [json objectForKey:#"amount"];
float spotfloat = [spot floatValue];
dispatch_sync(dispatch_get_main_queue(), ^{
// update your UI or model here
});
}

Related

dispatch_semaphore_wait() can't receive the semaphore

my code is :
- (NSString*)run:(NSString*)command{
_semaphore = dispatch_semaphore_create(0);
// Create and start timer
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f
target:self
selector:#selector(getState:)
userInfo:nil
repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
[runLoop run];
//and it stuck there
// Wait until signal is called
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
return _state;
}
- (void)getState:(NSTimer*)time{
// Send the url-request.
NSURLSessionDataTask* task =
[_session dataTaskWithRequest:_request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSLog(#"result: %#", data);
} else {
_state = #"error";
NSLog(#"received data is invalid.");
}
if (![_state isEqualToString:#"inProgress"]) {
dispatch_semaphore_signal(_semaphore);
// Stop timer
[timer invalidate];
}
}];
[task resume];
}
after run the code
[runLoop run];
it had nothing happened!
so, what's wrong with the code?
Calling dispatch_semaphore_wait will block the thread until dispatch_semaphore_signal is called. This means that signal must be called from a different thread, since the current thread is totally blocked. Further, you should never call wait from the main thread, only from background threads.
is that helpful?
A couple of observations:
Your use of the semaphore is unnecessary. The run won't return until the timer is invalidated.
The documentation for run advises that you don't use that method as you've outlined, but rather use a loop with runMode:beforeDate: like so:
_shouldKeepRunning = true;
while (_shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
// this is intentionally blank
}
Then, where you invalidate the timer, you can set _shouldKeepRunning to false.
This notion of spinning up a run loop to run a timer is, with no offense intended, a bit dated. Nowadays if I really needed a timer running on a background thread, I'd use a dispatch timer, like outlined the first half of https://stackoverflow.com/a/23144007/1271826. Spinning up a runloop for something like this is an inefficient pattern.
But let's step back and look at what you're trying to achieve, I assume you're trying to poll some server about some state and you want to be notified when it's no longer in an "in progress" state. If so, I'd adopt an asynchronous pattern, such as completion handlers, and do something like:
- (void)checkStatus:(void (^ _Nonnull)(NSString *))completionHandler {
// Send the url-request.
NSURLSessionDataTask* task = [_session dataTaskWithRequest:_request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *state = ...
if (![state isEqualToString:#"inProgress"]) {
completionHandler(#"inProgress");
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self checkStatus:completionHandler];
});
}
}];
[task resume];
}
and then call it like so:
[self checkStatus:^(NSString *state) {
// can look at `state` here
}];
// but not here, because the above is called asynchronously (i.e. later)
That completely obviates the need for run loop. I also eliminate the timer pattern in favor of the "try again x seconds after prior request finished", because you can have timer problems if one request wasn't done by the time the next timer fires. (Yes, I know you could solve that by introducing additional state variable to keep track of whether you're currently in a request or not, but that's silly.)

Objective C GCD: Do multiple dispatch threads make things faster?

I have some timer code to invoke a AFNetworking 2.0 POST every 2.5 seconds. After experimentation with Xcode simulator and the actual iPhone, I found that on the iPhone, it takes about 5-6seconds before commands are sent out successfully to the server. As a result, the commands called every 2.5 seconds get piled up. On the Xcode simulator, there is no such problem. Commands return the success block in less than a second.
More background:
The aim of POSTing every 2.5 seconds is to keep the TCP connection open. This way, the calls are made even faster compared to opening a new connection every time.
One reason for the lag could be due to the main thread getting too many requests. I have several methods which in turn call more methods.
My question: Should I just use a single custom concurrent queue, or multiple custom concurrent queues? Is this even a good way to solve the networking problem I am facing?
Note: I currently use self.commandSent to track and ensure not too many commands are sent out before the previous POSTs are completed. Will be changing to dispatch groups for this purpose.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self startTimer];
}
-(void)startTimer
{
dispatch_async(self.myConcurrentQueue, ^{
self.startTime = CFAbsoluteTimeGetCurrent() ;
self.displayTimer = [NSTimer scheduledTimerWithTimeInterval:2.5 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES ] ;
});
}
-(void)timerFired:(NSTimer*)timer
{
//Should I use another dispatch thread here?
CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
[self updateDisplay:elapsedTime] ;
}
-(void)stopTimer
{
[self.displayTimer invalidate] ;
self.displayTimer = nil ;
CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
[self updateDisplay:elapsedTime] ;
}
-(void)updateDisplay:(CFAbsoluteTime)elapsedTime
{
[self maintainConnection];
}
- (void)maintainConnection {
//Should I use another dispatch thread here?
if (self.commandSent == YES ) {
if(self.temperatureChanged == YES) {
[self updateTemperature]; //updateTemperature and updateSpeed are similar. They call POST requests
[self updateSpeed];
self.temperatureChanged = NO;
} else {
[self updateSpeed];
}
}
}
- (void)updateSpeed {
self.commandSent = NO;
NSURL *baseURL = [NSURL URLWithString:self.BaseURLString];
NSDictionary *parameters = #{#"command": #"16",
#"dat": self.dataToSend,};
//Create instance of AFHTTPSessionManager and set response to default JSON
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:#"submit" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
self.commandSent = YES;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", [error localizedDescription]);
}];
}
AFNetworking is an async API built on top of Apples NSURLConnection and NSURLSession classes. It handles the background processes for you, and you should not try to add threading on top of the async logic already built into the library.
Concurrent programming can make certain tasks faster because on a multi-core system, you get all available cores doing part of a job at the same time, like having multiple workers building a house instead of just one.
Like building a house, though, there are dependencies. You can't pour the foundation until the sewer pipe and utility feeds are in place. You can't build the frame until the foundation is poured. You can't put the roof on until the walls are up. You can't hang drywall until the roof is on and the house is water-tight.
Similarly, when you break a computer task up to run it concurrently, there are often inter-dependencies between the tasks. In graphics processing, you have to do your vertex calculations first before you can render textures onto the resulting surfaces.
Your questions tell me that you don't really understand the concepts behind concurrent programming. There be dragons! Concurrent programming is very tricky business, the number of ways you can screw it up, large and small, are almost infinite, and the resulting problems are very difficult to diagnose and fix.
If you don't know about race conditions, locks, spin locks, semaphores, and such, stay away from concurrent programing. You will wind up with an unstable mess.

Data updates unlogically from database on iphone app

Im fetching data from database through a php-API with this code:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadDataWithSpinner];
[self reloadAllData];
}
- (void) loadDataWithSpinner {
if (!self.spinner) [self setupSpinnerView];
self.sessions = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
self.userId = [[NSUserDefaults standardUserDefaults] integerForKey:#"CurrentUserId"] ;
self.sessions = [self getAllSessionsForUserId:self.userId];
dispatch_async(dispatch_get_main_queue(), ^(void){
if (self.sessions) {
self.spinnerBgView.hidden = YES;
[self setupChartsAndCountingLabelsWithData];
}
});
});
}
- (NSArray *) getAllSessionsForUserId: (int) userId {
NSData *dataURL = nil;
NSString *strURL = [NSString stringWithFormat:#"http://webapiurl.com/be.php?queryType=fetchAllBasicSessions&user_id=%i", userId];
dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
NSError *error = nil;
if (dataURL) {
NSArray *sessions = [NSJSONSerialization JSONObjectWithData:dataURL options:kNilOptions error:&error];
return sessions;
} else {
return Nil;
}
}
Is there something wrong with the code? I'm getting the correct data when testing in a web-browser with the same database call. But in the app it sometimes updates straight away, as it should, and sometimes it takes up to five minutes for the data to update. Even though i remove the app from the phone/simulator, the data sometime hasn't been update when opening the app again.
I would really appreciate some help.
I finally found the answer. Its nothing wrong with my code, as I thought. The problem lies on the server side. And, as they say in this thread:
NSURLConnection is returning old data
It seems to be badly configured server-side or proxy. Which makes the cache act all wierd. I tried another server and everything worked just fine!
Hope this helps someone with the same issue, cause it sure wasted me ALOT of time.

How to prevent a method from being called all the time

-(void) parseXML
{
[self performSelector:#selector(parseXML) withObject:self afterDelay:55.0 ];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://apikeygoeshere.com/data.xml"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *xmlString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSDictionary *xml = [NSDictionary dictionaryWithXMLString:xmlString];
NSMutableArray *items = [xml objectForKey:#"TeamLeagueStanding"];
NSMutableArray *newTeamObjectArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in items) {
TeamObject *myTeams = [TeamObject teamFromXMLDictionary:dict];
[newTeamObjectArray addObject:myTeams];
}
NSNull *nullValue = [NSNull null];
NSNull *nullValue2 = [NSNull null];
[newTeamObjectArray insertObject:nullValue atIndex:0];
[newTeamObjectArray insertObject:nullValue2 atIndex:1];
NSLog(#"standingsdataaaaa %#", newTeamObjectArray);
}
I want to add a unbutton to my storyboard so the user can refresh the data whenever he wants, but i don't him to be able to do this more than once per hour,
Can anyone help me? Thank you.
Just in the action method or wherever you call to get the XML
setEnabled: NO and set an NSTimer to fire nod a date that is 3600 seconds from now.
When it fires, setEnabled:YES
It might be nice to create a visual indicator to the user like a counter.
EDIT: In order to account for the fact that you still want to run the parseXML method every 55 seconds with or without the button press, I'm changing my answer by putting the conditional in the IBAction method triggered by the button press instead of putting the conditional in parseXML:
Declare an NSTimer as a class variable. For example, at the top of your .m directly after your #synthesizes, declare an NSTimer:
NSTimer *parseTimer;
Then in the IBAction method triggered by the button press, only call parseXML if the timer is nil; and if it is in fact nil and the parseXML method is going to run, initiate the timer so it doesn't run again for another hour:
- (IBAction)buttonPressed:(sender)id {
// If the parseTimer is active, do call parseXML.
// (And perhaps fire an alert here)
if (parseTimer != nil) return;
// Otherwise initialize the timer so that it calls the the method which
// will deactivate it in 60*60 seconds, i.e. one hour
parseTimer = [NSTimer scheduledTimerWithTimeInterval:60*60 target:self selector:#selector(reactivateButton) userInfo:nil repeats:YES];
[self parseXML];
}
The deactivateParseTimer method should deactivate the timer and set it to nil so that parseXML may run again:
- (void)deactivateParseTimer {
[parseTimer invalidate];
parseTimer = nil;
}

What causes NSURLConnection to stop receiving before it's done?

My game needs to fill tableView cells with a bunch of things from my server database. This has been working fine. Then I upgraded Xcode to 4.6 and targeted iOS6.1, to please the App Review Team folks. Now, one of my connections never completes. (All of the other Posts seem to work correctly, as always.) Here's my post:
- (void) fillCells {
Cell_QtoA *newCell = [[Cell_QtoA alloc] initCellUser:usrID Grp:grpID Qtn:0 Gnm:#"na" Cat:#"na" Sit:#"na" Pfl:#"na" Lks:0 isA:0 ddA:0 ];
NSMutableURLRequest *reqPost = [SimplePost urlencodedRequestWithURL:[NSURL URLWithString:kFillCells] andDataDictionary:[newCell toDictC]];
(void) [[NSURLConnection alloc] initWithRequest:reqPost delegate:self];
}
I think it's working fine. The PHP and database haven't changed. Everything worked great yesterday, before the upgrades. Here's my connection method:
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"data = %#", data);
NSString *error;
NSArray *array = (NSArray *)[NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:0 errorDescription:&error];
if( error ) {
NSLog(#"Error = %#", error);
return;
}
NSLog(#"1st object in array of %d is %#", [array count], array );
}
Because I suspected net speeds to be an issue, I added a timer to the call, which I never needed before:
timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(fillCells) userInfo:nil repeats:YES];
The timer didn't help. Still get errors:
"Unexpected EOF" and "Unexpected character z (or whatever) at Line 1"
The NSLog of data shows hex data that appears cut off, like:
<3c3f786d 6c207665 ... 63743e0a 3c2f6172 7261793e 0a3c2f70 6c697374 3e>
It's like the reception is being interrupted. Anyone know what's happening here? Thanks!
You aren't using all the response data; as mention in the NSURLConnectionDelegate reference:
The newly available data. The delegate should concatenate the contents
of each data object delivered to build up the complete data for a URL
load.
So you need to create an NSData instance variable; clear before the request and append to it whenever new data arrives. Then use the didFinishLoading delegate method to trigger the call to propertyListFromData with the complete response.

Resources