I have come down to this simple NSTimer learning. It is working fine with no parameter at all. But with two parameters it is throwing bad-access. Am i passing parameters in right way or there is something else I missed out.
Although same problem has been referred here and here but none is solving the purpose.
Code correctness along with concept(why is it bad access) will be appreciated. Thanks.
#implementation ScreeLog1
-(void)tickTock{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(onTick::)
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
#"a",#"b", nil] repeats:NO];
}
-(void)onTick:(NSString *)message1 :(NSString *)message2 {
NSLog(#"%#****%#\n",message1,message2);
}
#end
And yes i am using ARC, Xcode5.1
The selector you pass to the scheduledTimerWithTimeInterval method can only have one argument: the NSTimer. You handle your custom parameters via the userInfo dictionary.
Try this:
-(void)tickTock{
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(onTick:)
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
#"a",#"b",#"c",#"d", nil]
repeats:NO];
}
-(void)onTick:(NSTimer *)timer {
NSDictionary *userInfo = [timer userInfo];
NSLog(#"%#*****%#\n", userInfo[#"b"], userInfo[#"d"]);
}
Related
My code here(METHOD DEFINITION)
- (void)info:(NSString *)idno info1:(NSString *)package info2:(NSString *)rate info3:(NSString *)type info4:(NSString *)status;
{
//CODE HERE
}
My code here(METHOD CALLING)
[self performSelector:#selector(info:info1: info2:info3:info4:) withObject:#"a" withObject:#"b" withObject:#"c" withObject:#"d" withObject:#"e" ];
MY app displays the fallowing error:
No visible #interface for 'ViewController' declares the selector 'performSelector:withObject:withObject:withObject:withObject:withObject:'
Please help how to solve this problem
you can directly called the method as like instead of performSelector
[self info:#"a" info1:#"b" info2:#"c" info3:#"d" info4:#"e" ];
else if you want to continue your work with the help of performselector see this already answered in SO
This is how you have to do.
in viewDidLoad
// create a dict
NSDictionary *myDict = [[NSDictionary alloc] initWithObjectsAndKeys: #"my_pack" , #"package" , #"my_rate" , #"rate" , #"my_type", #"type" , #"my_status", #"status" , nil];
// Call your func
[self passMyValueHere:myDict];
your func
- (void) passMyValueHere : (NSDictionary *) myValues{
}
I have production code which I see that it crashes once in a while with a EXC_BAD_ACCESS KERN_INVALID_ADDRESS on the block handler. I could for the life of me figure out what is wrong with my code. I have tried to reproduce this and could not in my controlled environment. Here is the stripped out and cleaned up code :
Code Snippet :
typedef void (^TestCallBackHandler)(NSString* location, NSError* error);
#interface _TestClass : NSObject
#property (nonatomic, copy) TestCallBackHandler handler;
#property (nonatomic, strong)NSTimer *endTimer;
- (void)fireOneOff:(TestCallBackHandler)handler;
#end
#implementation _TestClass
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
- (void)dealloc
{
NSLog(#"%# Dealloced",self);
_handler = nil;
}
- (void)_stop
{
NSLog(#"** Stopping ? %#",self);
if (_handler)
_handler([NSString stringWithFormat:#"Hello %#",[NSDate date]], nil);
}
#end
The calling code in my class is defined as :
#property (nonatomic, strong)_TestClass *testClassInstance;
and called like this :
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Few things to note :
The startTestClass can be called multiple times
The app wakes up in the background and this can be created.
Any pointers, help highly appreciated.. I just cant put a finger in this code and say that is what is wrong. Please help!
Two hints which occur a little bit strange to me:
First: why are you setting the object to nil in it's own block.
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Second your object may be released already and the NSTimer is trying to execute a method on a release object
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
I also dont get what the code does exactly and why. I believe there would be an easier and better maintainable solution for what you are achieving in this class.
If you want to have more information about EXC_BAD_ACCESS, then you can turn of NSZombies. In Xcode go to Product > Scheme > Edit Scheme and set checked Enable Zombie Objects.
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
});
}
-(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;
}
I'm new using dispatch_queue_t and I have two problems with my app which I think that could be solved using GCD. The first one is that in the main view (ViewController) I have a label which is actualized by another class (AudioViewController), and when I do any user interaction in ViewController the label stop to actualize, so I think if I use dispatch_queue_t this problem will be solved.
The second thing is that in the same main view (ViewController) when I press a contribution I call another class (ContributionViewController) and this class accesses just a instance variable of another class (AudioViewController) which is refreshed all the time. When I start contribution, I made a loop to get more than one value to make some calculus with them and those values are all the same though.
I'll put some code here trying to clear the things.
ViewController.m
- (IBAction)makeContribution:(id)sender
{
NSLog(#"A: Contribution button clicked");
NSLog(#"-= START CONTRIBUTION =-");
cvc = [[ContributionViewController alloc] init];
cvc.avc = self.avc;
// Get NUM_CONTRIBUTIONS contributions to make average.
int contContribution;
for (contContribution = 0; contContribution < NUM_CONTRIBUTIONS; contContribution++) {
[cvc getEachContribution];
}
// Make average
[cvc makeAverage:NUM_CONTRIBUTIONS];
[cvc release];
}
AudioViewController.m
- (void)audioInitializationWithTimeInterval:(float)time
{
NSDictionary* recorderSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:44100],AVSampleRateKey,
[NSNumber numberWithInt:1],AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
nil];
NSError* error;
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recorderSettings error:&error];
//enable measuring
//tell the recorder to start recording:
[recorder record];
if (recorder) {
[recorder prepareToRecord];
recorder.meteringEnabled = YES;
[recorder record];
levelTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(levelTimerCallback:) userInfo:nil repeats:YES];
}
else
NSLog(#"%#",[error description]);
}
- (void)levelTimerCallback:(NSTimer *)timer
{
//NSLog(#"-= AVC =-");
[recorder updateMeters];
db = [recorder averagePowerForChannel:0] - DBOFFSET;
db += 120;
db = db < 0 ? 0 : db;
vc.lab_decibel.text = [NSString stringWithFormat:#"%0.0f", db];
}
ContributionViewController.m
- (void)getEachContribution
{
actualContribution = self.avc.db;
NSLog(#"Actual contribution: %f", actualContribution);
NSLog(#"Sum before: %0.2f", sumContribution);
sumContribution += actualContribution;
NSLog(#"Sum After: %0.2f", sumContribution);
}
- (void)makeAverage:(int)numOfContributions
{
self.average = self.sumContribution / numOfContributions;
NSLog(#"Average: %0.2f", self.average);
}
So, the main thing is dispatch_queue_t is going to solve my problems and how to do that? I've tried to put dispatch_queue_t on AudioViewController, ContributionViewController and ViewController, but the first didn't update the label, the second crashed and the third one the label still with 0 value.
Thanks for any tips to solve this problem.
EDIT 01:
The decibel label changes all the time.
Ok, this is starting to make sense. I didn't understand the mapView, contribution, etc. language. It was entirely cryptic, but I think I'm starting to understand your question. As I understand it, your question is why your user interface is not being updated. Ok, the big answer to your question is that you don't need GCD if you don't want to. The NSTimer does what you need.
Now, you say that your continuously updated db field is no longer updated. There's absolutely no reason why it shouldn't continue. I've tried it, and it works fine. There's something going on there. Personally, when I've got a NSTimer that is updating my UI, I make sure to turn it off in viewDidDisappear (no point in updating your UI if it's not visible) and always turn it on in viewDidAppear. When I did this, whenever I returned to my main view with the db numbers, it happily resumed. Or if I wanted another view controller to get the notifications, I just turned it back on using a delegate.
That might look like the following.
First, you probably want a delegate protocol:
// DecibelNotifierDelegate.h
#import <Foundation/Foundation.h>
#protocol DecibelNotifierDelegate <NSObject>
#required
- (void)decibelNotifierUpdate:(CGFloat)db;
#end
And then, you want to define your DecibelNotifier class:
// DecibelNotifier.h
#import <Foundation/Foundation.h>
#import "DecibelNotifierDelegate.h"
#interface DecibelNotifier : NSObject
#property (nonatomic, retain) id<DecibelNotifierDelegate> delegate;
#property CGFloat db;
- (void)startWithInterval:(float)time target:(id<DecibelNotifierDelegate>)delegate;
- (void)stop;
#end
and
// DecibelNotifier.m
#import "DecibelNotifier.h"
#import "AVFoundation/AVFoundation.h"
#interface DecibelNotifier ()
{
AVAudioRecorder *_recorder;
NSTimer *_levelTimer;
}
#end
// note, your code makes reference to DBOFFSET and I don't know what that is. Update this following line accordingly.
#define DBOFFSET 0
#implementation DecibelNotifier
#synthesize delegate = _delegate;
#synthesize db = _db;
- (id)init
{
self = [super init];
if (self)
{
NSDictionary* recorderSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
[NSNumber numberWithInt:44100],AVSampleRateKey,
[NSNumber numberWithInt:1],AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
nil];
NSError* error;
NSURL *url = [NSURL fileURLWithPath:#"/dev/null"];
_recorder = [[AVAudioRecorder alloc] initWithURL:url settings:recorderSettings error:&error];
if (!_recorder)
NSLog(#"%#",[error description]);
}
return self;
}
- (void)startWithInterval:(float)time target:(id<DecibelNotifierDelegate>)delegate
{
if (_recorder)
{
self.delegate = delegate;
[_recorder prepareToRecord];
_recorder.meteringEnabled = YES;
[_recorder record];
_levelTimer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(levelTimerCallback:) userInfo:nil repeats:YES];
}
}
- (void)stop
{
[_recorder stop];
[_levelTimer invalidate];
_levelTimer = nil;
self.delegate = nil;
}
- (void)levelTimerCallback:(NSTimer *)timer
{
[_recorder updateMeters];
CGFloat db = [_recorder averagePowerForChannel:0] - DBOFFSET;
db += 120;
db = db < 0 ? 0 : db;
_db = db;
[self.delegate decibelNotifierUpdate:db];
}
#end
So, the first view controller that wants the notifications might have a property:
#property (strong, nonatomic) DecibelNotifier *dBNotifier;
You can initialize it with:
self.dBNotifier = [[DecibelNotifier alloc] init];
You can then turn on notifications with:
[self.dBNotifier startWithInterval:0.25 target:self];
So the question is when you turn these notifications on and off. I do it in viewWillAppear and viewWillDisappear. You can also pass the dbNotifier to other view controllers and they can get notifications, too.
Like I said, I tried this out and it works fine, with any view controller that wants to get notifications can just turn it on and specify itself as the delegate, and you're off to the races.