I made a class, called Timer. Its designated initializer starts a timer with a value in seconds. It works great. However I am having trouble updating the controller w/e the timer ticks.
Right now, for every tick I am sending a NSNotificationCenter with a userInfo that is a simple dictionary with the current time, which does not sound the best way to do it...
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:self.timerCount] forKey:#"timerCount"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TimerCountChanged"
object:self
userInfo:dict];
Should I be using some other technique or am I doing it the right way?
Thank you in advance!
EDIT:
I need to initialize different Timers, using different values. I tried to use Delegates, but I only had one method in my controller to update the UI for all those Timers!
Would it be bad if I do something like? Passing a UIButton to my Model also does not seem to be the best solution but it works.
-(void)timer:(Timer *)timer didTriggerAt:(NSTimeInterval)time andButton:(UIButton *)button
{
[button setTitle:[NSString stringWithFormat:#"%.0f", time] forState:UIControlStateNormal];
}
- (IBAction)startCountDown:(UIButton *)sender
{
self.timer1 = [[Timer alloc] initWithTimeInSeconds:10 andButton:sender];
self.timer1.delegate = self;
}
I have 3 Timers in my MainView, the user can start them whenever he wants. They can also have different times, which is also defined by the user.
Sending Notifications is good, but you may not observe it as in regular time.
Sometimes it gets delayed and you may observe them in irregular time interval.
You can use
Delegate Pattern.
Call method by selector
EDIT:
From
Apple documentation on Performance CodeSpeed on Notifications.
The fewer notifications you send, the smaller the impact on your
application’s performance. Depending on the implementation, the cost
to dispatch a single notification could be very high. For example, in
the case of Core Foundation and Cocoa notifications, the code that
posts a notification must wait until all observers finish processing
the notification. If there are numerous observers, or each performs a
significant amount of work, the delay could be significant.
If you only have one client object for each Timer instance, then you should use the delegate pattern. You would define a TimerDelegate protocol with a method that a Timer object can call whenever the timer ticks.
e.g.
#class Timer;
#protocol TimerDelegate
- (void) timer:(Timer *)timer didTriggerAt:(NSTimeInterval)time;
#end
#interface Timer
...
#property (assign) id<TimerDelegate> delegate;
...
#end
If you indeed require multiple listeners each time a Timer instance ticks, then the NSNotificationCenter approach would be a better fit. Instead of passing info in the userInfo dictionary, I probably would expose an #property on Timer called currentTime, so that when a client object gets the notification, they could simply access currentTime on the notifying Timer, instead of (IMO clunkily) reading data out of userInfo.
Related
I have a class with a property
#property (nonatomic) double* myDoubles
This property has 3 doubles in it
myDoubles[0]; //0.02
myDoubles[1]; //0.42
myDoubles[2]; //0.99
If the values change, I'd like the following method to be called
[self setNeedsDisplay];
I tried using FBKVOController, but that didn't work..
_observer = [FBKVOController controllerWithObserver:self];
[_observer observe:self
keyPath:#"myDoubles"
options:NSKeyValueObservingOptionNew
action:#selector(setNeedsDisplay)];
I don't want to start an NSTimer and just check for changes.
This is not possible.
Notifications work because the code making changes does so through some method that knows to notify listeners of the change. If that same code were simply to write to the memory location backing the data, the notification would never be triggered.
What you want to do is simply declare a memory location that code will write to; no notification can happen from this (unless you have very system-dependent support making it possible - a memory watchpoint - and then your question changes significantly. Such support, when available, is very limited and not of good generic value).
I am trying to update a view when something happens in another class, and after some looking, it appeared that the most common way to do this was to use either delegates or blocks to create a callback. However, I was able to accomplish this task using notifications. What I want to know is: Is there a problem using notifications to trigger methods calls? Are there any risks I'm not aware of? Is there a reason I'd want to use blocks/delegates over notifications?
I'm new to Objective-C, so I'm not sure if the approach I'm taking is correct.
As an example, I'm trying to set the battery level of a BLE device on the ViewController. I have a BluetoothLEManager, which discovers the peripheral, its services/characteristics, etc. But to do this, I need to initiate the "connection" in the detailViewController, then update the battery level once I find it.
Here is some example code of what I'm doing:
DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Selected tag UUID: %#", [selectedTag.tagUUID UUIDString]);
tagName.text = selectedTag.mtagName;
if(selectedTag.batteryLevel != nil){
batteryLife.text = selectedTag.batteryLevel;
}
uuidLabel.text = [selectedTag.tagUUID UUIDString];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setBatteryLevel:) name:#"SetBatteryLevel" object:nil];
}
...
-(void)setBatteryLevel:(NSNotification*)notif{
NSMutableString* batLevel = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:#"%#", selectedTag.batteryLevel]];
[batLevel appendString:#" %"];
selectedTag.batteryLevel = batLevel;
batteryLife.text = selectedTag.batteryLevel;
}
BluetoothLEManager.m:
...
-(void) getBatteryLevel:(CBCharacteristic *)characteristic error:(NSError *)error fetchTag:(FetchTag *)fetchTag
{
NSLog(#"Getting battery Level...");
NSData* data = characteristic.value;
const uint8_t* reportData = [data bytes];
uint16_t batteryLevel = reportData[0];
selectedTag.batteryLevel = [NSString stringWithFormat:#"%i", batteryLevel];
NSLog(#"Battery Level is %#", [NSString stringWithFormat:#"%i", batteryLevel]);
[[NSNotificationCenter defaultCenter] postNotificationName:#"SetBatteryLevel" object:nil];
}
...
Let me know if you need any other code, but this is the basics of it all.
Each approach has different strengths and weaknesses.
Delegates and protocols require a defined interface between the object and it's delegate, a one-to-one relationship, and that the object have specific knowledge of the delegate object it's going to call.
Methods with completion blocks involve a similar one-to-one relationship between an object and the object that invokes the method. However since blocks inherit the scope in which they're defined, you have more flexibility as to the context that's available in the completion block. Blocks also allow the caller to define the completion code in same place that the call takes place, making you code more self-documenting.
In both cases, the object that is notifying the delegate or invoking the completion block has to know who it's talking to, or what code is being executed.
A delegate call is like an auto shop calling you back to let you know your car is done. The service manager has to have your phone number and know that you want a call.
A block is more like a recipe you give to a chef. Give the chef a different recipe and he/she performs a different task for you.
Notifications are much less tightly coupled. It's like a town crier, yelling announcements in a crowded public square. The crier doesn't need to know who's listening, or how many people are listening.
Likewise, when you send a notification, you don't know who, if anybody, is listening, or how many listeners there are. You don't need to know. If 10 objects care about the message you are broadcasting, they can all listen for it, and they'll all be notified. The message sender doesn't have to know or care who's listening.
Sometimes you want tighter coupling, and sometimes you want looser coupling. It depends on the problem you're trying to solve.
I'm writing a custom file system cache component that has an index dictionary that represents important attributes about the files within the folder.
This is for an iOS app, and I'm writing in Objective-C
At various points in the implementation of adding objects / deleting object from the cache, the index dictionary needs to be saved to disk.
In order to stop this operation happening needlessly many times over, for example if objects are added to the cache in a for.. loop, I want to make a system that every time the dictionary is modified, a state is set to ensure that at some point in the future the dictionary will be saved. This should not happen immediately however, in case another change is made quickly, in which case the first 'save' operation should not happen, but another one should be queued up.
In pseudo-code:
//This is the method called by all other parts of the program whenever the dictionary is modified and it needs to be changed
-(void) dispatchSaveIndexDictionary {
//cancel any previous requests to save.
//queue up a save operation some short time later.
}
How I've implemented this:
-(void)saveIndexDictionaryDispatchDelayed
{
NSLog(#"dispatching index save");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(saveIndexDictionaryWriteToDisk) object:nil];
//Delay 0 means it gets queued up asap, which means the index dictionary on disk remains in sync whenever possible.
// - This is to solve the case of adding multiple objects in a for... loop
[self performSelector:#selector(saveIndexDictionaryWriteToDisk) withObject:nil afterDelay:0];
}
-(void)saveIndexDictionaryWriteToDisk
{
NSLog(#"Writing cache index to disk : %#", self.cachePath);
[NSKeyedArchiver archiveRootObject:self.indexDictionary
toFile:[OFMFileSystemCache indexDictionaryFullPathWithCachePath:self.cachePath]];
}
Using performSelector:withObject:afterDelay:0
I expected that this would always perform the 'write to disk' method AFTER any of the 'dispatch ' operations, i.e. we could have multiple write operations, if tasks took a long time, but that the 'write' operation would always be the last thing to happen.
I've seen from the logs that this does not always happen, if I do the simple use case of adding 10 files to the cache, then sometimes I get 'dispatching index save' happening and no call afterwards to 'Writing cache index to disk'. I don't really understand how this is possible!
Is there some reason why my implementation isn't a good idea (I guess there must be as it doesn't work very well)?
What do you think is a good secure design for this type of delayed method call, as it's critical that the index remains up to date with the contents of the cache. I.E. write to cache should always happen last, after all modifications have been made.
Thanks.
I've done something similar in my caches in the past. What I end up doing instead of using performSelector:afterDelay: is setup an NSTimer. Something like:
// elsewhere, setup an NSTimer* ivar called saveTimer
-(void) saveToDisk{
saveTimer = nil;
// actually save here
}
-(void)resetSaveTimer{
if(saveTimer) [saveTimer invalidate];
saveTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(saveToDisk) userInfo:nil repeats:NO];
}
-(void)doStuffWithCache{
// do stuff, add stuff, whatever
[self resetSaveTimer];
}
Depending on your threading, you may also want to add #synchronized(yourCacheDictionary){...} for each of the method bodies, just to make sure you're not trying to write to disk while also editing the dictionary, etc.
Following on from adam.wulf, I wanted to post the solution that I have finally settled on.
This uses NSTimer as suggested, because I had found inconsistent behaviours I couldn't explain with the 'NSObject performSelector:afterDelay:' approach.
After trying the timers approach I needed to modify the solution in some important ways.
1 - make sure the timer is dispatched on a queue with a properly set up runloop for timer execution. I'm using an external caching library for some caching operations, and this calls back on queues that are not set up appropriately. The easiest solution for me was to always dispatch the timer call on the main queue.
2 -Dispatch the actual write operation on a dedicated serial queue, so as not to block the main queue (where the timer method will fire as it is dispatched on that queue).
3 - As suggested wrapping the index Dictionary write to disk methods in #synchronized(indexDictionary) { } to ensure the contents are not modified while being written.
#pragma mark - Saving Index Dictionary
-(void)saveIndexDictionaryDispatchDelayed
{
dispatch_async(dispatch_get_main_queue(), ^{
if (_dispatchSaveTimer) {
[_dispatchSaveTimer invalidate];
_dispatchSaveTimer = nil;
}
_dispatchSaveTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(saveIndexDictionaryWriteToDisk)
userInfo:nil
repeats:NO];
});
}
-(void)saveIndexDictionaryWriteToDisk
{
dispatch_async(_cacheOperationQueue, ^{
#synchronized (self.indexDictionary) {
_dispatchSaveTimer = nil;
NSLog(#"Writing cache index to disk : %#", self.cachePath);
[NSKeyedArchiver archiveRootObject:self.indexDictionary
toFile:[OFMFileSystemCache indexDictionaryFullPathWithCachePath:self.cachePath]];
}
});
}
I have an iOS app with a tabbar and 3 different UIViewControllers, one for each tab. The app uses SudzC to interface with a C# .NET webservice to pull data from a database.
There is one webservice method that is called from all three view controllers, but I want to enforce that only one view controller can call the method at any point in time and no other view controller can call it until the data has been returned.
I tried to solve this by defining a NSLock in the AppDelegate, and then implementing the following code in each viewController:
if([SharedAppDelegate.loginLock lockBeforeDate:[[[NSDate alloc] init] dateByAddingTimeInterval:30.0]])
{
// got the lock so call the webservice method
SDZiOSWebService* webService = [SDZiOSWebService service];
[webService Login:self action:#selector(handleRelogin:) username:userName password:password];
}
else
{
// can't get lock so logout
self->reloginInProgress = false;
[SharedAppDelegate doLogout];
}
The handler for the webservice return is defined as (truncated for clarity)
-(void)handleRelogin: (id) result {
SDZLoginResult *loginResult = (SDZLoginResult*)result;
if(loginResult.Status)
{
SharedAppPersist.key = loginResult.key;
}
else
{
SharedAppPersist.key = #"";
}
[SharedAppDelegate.loginLock unlock];
}
My understanding is that the first UIViewController would get a lock and the others would block for up to 30 seconds waiting to get hold of the lock. However in the rare instances where more than one viewController tries to access the lock at the same time I get the following error instantly:
*** -[NSLock lockBeforeDate:]: deadlock (<NSLock: 0x2085df90> '(null)')
Can anyone tell me what I am doing wrong? I have a good understanding of locks in C/C++ but these Objective-C locks have be stumped.
In my opinion you shouldn't use locks (which are "evil") for this simple case.
What you can try to use is a NSOperationQueue, set to manage 1 concurrent operation at a time, and then let the view controllers to enqueue their web service calls: the operation queue will guarantee that only one operation at a time will be performed.
The other advantage of the operation queue is that a view controller can check if the queue is empty or not and then decide to enqueue its call or not, based on the current status.
Finally you can use KVO to observer the queue status so each view controller can simply check this before submitting a new request.
Another possibility, similar to using NSOperationQueue, is to create a private GCD serial queue and again enqueue all web service requests (wrapped inside a block). While GCD serial queues are more straightforward to implement than NSOperationQueues (IMHO) they don't offer the same advantages of observability and the possibility to cancel operations.
If its just that you want 1 view to access the web-service at a time. you can make use of Singleton Classes.
Here's a link to one of the examples out the many on the net.
http://www.galloway.me.uk/tutorials/singleton-classes/
also you can use NSUserDefaults to store a bool value to inform you if a view is using the web-service or not.
A simple example will be:
To Store Value
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"active_connection"];
[NSUserDefaults synchronize];
To Retrieve
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"active_connection"]) {
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"active_connection"];
[NSUserDefaults synchronize];
// Send request to web-service
}
I hope this helps you. Happy Coding.!!
I have some code that uses a fair amount of GCD code. I need to implement a way to schedule a unit of work after some delay, but can be canceled and moved further out if needed.
Think of a handler for clicks; something to distinguish single clicks from double clicks. To do this, one would get the click event, and set up a short timer to act on it. If another click event came through before the timer fired, it would be canceled and started again. When the timer did eventually fire, it would have the correct number of clicks.
Anyways, this would be easy enough to implement with NSTimers or the performSelector stuff on NSObject. Maybe something like
NSUInteger tapCount = 0;
- (void)handleClickEvent
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(fireEvent:) object:nil];
tapCount++;
[self performSelector:#selector(fireEvent:) withObject:#(tapCount) afterDelay:2.0];
}
- (void)fireEvent:(NSNumber *)clickCount
{
// Act on the coalesced event
NSUInteger numClicks = [clickCount unsignedIntegerValue];
if ( numClicks == 1 ) // single click
if ( numClicks == 2 ) // double click
}
Before doing it this way however, I am wondering if there is a way to do this with the GCD functions. I know you can't undo enqueueing a block, so dispatch_after isn't really an option. I know there are dispatch timer sources, but they seem like they are more used for firing periodic tasks. I don't know if they can be easily canceled and started later like I would need.
Thanks for any suggestions.
dispatch_source_set_timer() will reschedule a dispatch timer source (which can be one-shot and non-repeating if you pass DISPATCH_TIME_FOREVER as the interval parameter).
Note that this API is non-preemptive, i.e. unless you call dispatch_source_set_timer() from the target queue of the timer source, the timer handler could already be running at the time of reschedule.
However, once dispatch_source_set_timer() returns, it is guaranteed that the timer will no longer fire at the previously set target time.