-(void) test{
for(Person *person in persons){
__block CGPoint point;
dispatch_async(dispatch_get_main_queue(), ^{
point = [self.myview personToPoint:person];
});
usePoint(point); // take a long time to run
}
}
I need to run personToPoint() in the main queue to get the point, and usePoint() method doesn't need to run in main queue and take a long time to run. However, when running usePoint(point), point has not been assigned value because using dispatch_async. If using dispatch_sync method, the program will be blocked. the how can I using point after it has been assigned?
UPDATE:
how to implement the pattern of the following code:
-(void) test{
NSMutableArray *points = [NSMutableArray array];
for(Person *person in persons){
__block CGPoint point;
dispatch_async(dispatch_get_main_queue(), ^{
point = [self.myview personToPoint:person];
[points addObject:point];
});
}
usePoint(points); // take a long time to run
}
Something like the following would work. You might also put the entire for loop inside one dispatch_async() and let the main thread dispatch all the usePoint() functions at once.
-(void) test{
for(Person *person in persons){
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint point = [self.myview personToPoint:person];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usePoint(point); // take a long time to run
});
});
}
}
Solution for Updated question:
You use the same basic pattern as suggested above. That is you dispatch the stuff you need to do on the main thread to the main thread and then nest a dispatch back to a default work queue inside the main thread dispatch. Thus when the main thread finishes its work it will dispatch off the time consuming parts to be done elsewhere.
-(void) test{
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *points = [NSMutableArray array];
for (Person *person in persons){
CGPoint point = [self.myview personToPoint:person];
[points addObject:[NSValue valueWithCGPoint:point]];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
usePoint(points); // take a long time to run
});
});
}
Note that there was an error in your code as you can't add CGPoint's to an NSArray since they are not objects. You have to wrap them in an NSValue and then unwrap them in usePoint(). I used an extension to NSValue that only works on iOS. On Mac OS X you'd need to replace this with [NSValue valueWithPoint:NSPointToCGPoint(point)].
Related
I have a do loop that I want to execute a command every 1 second while a SWITCH is on.
The Code works fine ONCE, when I don't have the DO LOOP.
However, as soon as I add the LOOP, none of the labels in the view controller are updated, the back button for the storyboard doesn't work, and the SWITCH will not toggle off. Essentially, the DO LOOP keeps looping, but nothing on the screen will work, nor can I back out.
I know I'm doing it wrong. But, I don't now what. Any thoughts would be appreciated.
I attached the code that gets me in trouble.
Thanks,
- (IBAction)roaming:(id)sender {
UISwitch *roamingswitch = (UISwitch *)sender;
BOOL isOn = roamingswitch.isOn;
if (isOn) {
last=[NSDate date];
while (isOn)
{
current = [NSDate date];
interval = [current timeIntervalSinceDate:last];
if (interval>10) {
TheCommand.text=#"ON";
[self Combo:sendcommand];
last=current;
}
}
}
else
{
TheCommand.text=#"OFF";
}
}
iOS and OSX are event based systems and you cannot use loops like this in the main (UI) thread to do what you want to do, otherwise you don't allow the run loop to run and events stop being processed.
See: Mac App Programming Guide section "The App’s Main Event Loop Drives Interactions".
What you need to do is set-up a timer (NSTimer) which will fire every second:
.h file:
#interface MyClass : NSView // Or whatever the base class is
{
NSTimer *_timer;
}
#end
.m file:
#implementation MyClass
- (id)initWithFrame:(NSRect)frame // Or whatever the designated initializier is for your class
{
self = [super initInitWithFrame:frame];
if (self != nil)
{
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)dealloc
{
[_timer invalidate];
// If using MRR ONLY!
[super dealloc];
}
- (void)timerFired:(NSTimer*)timer
{
if (roamingswitch.isOn)
{
TheCommand.text=#"ON";
[self Combo:sendcommand];
}
}
#end
Give your processor enough time to update your view controller and not be interrupted by other processes. I give it 0.5 second before and after the view controller update signal.
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
self.CrashingTime.text = [NSString stringWithFormat: #"Crash Time = %f ms", outputOfCrashTime];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
Right to the point, then:
First snippet (AppDelegate):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//...code taken out...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *incomingEvent) {
if ([incomingEvent type] == NSKeyDown) {
NSUInteger flags = [incomingEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
if (flags==NSCommandKeyMask && ([incomingEvent keyCode] == 8)) {
[ClipboardUtilities logger:#"cmd+c recognized"];
[self determineAndAddToHistory];
}
}
}];
}
Second snippet (AppDelegate):
-(void) determineAndAddToHistory {
id clipDat = [ClipboardUtilities getClipboardDataNatively];
if ([clipDat isKindOfClass:[NSAttributedString class]])
NSLog(#"clipDat.string = %#",((NSAttributedString*)clipDat).string);
}
Third snippet (ClipboardUtilities class):
+(id) getClipboardDataNatively {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *classArray = #[[NSAttributedString class], [NSImage class]];
NSDictionary *options = [NSDictionary dictionary];
NSArray *objectsToPaste = nil;
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
if (ok) {
objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
}
NSLog(#"objectsToPaste count = %li",[objectsToPaste count]);
return [objectsToPaste objectAtIndex:0];
}
I've noticed some strange behaviour that I will try to explain with an example:
Input
Cmd+C the string "A"
Cmd+C the string "B"
Cmd+C the string "C"
Cmd+C the string "D"
Output from determineAndAddToHistory
A
A
B
C
So I'm noticing that it retains that first item for some reason...then returns me the second most recent item each time. I've tried outputting the objectsToPaste array in the getClipboardDataNatively method, and this still is the case. Could someone please let me know how I would approach this problem, or how they've solved it?
P.S. my ClipboardUtilities class does not implement any Delegates, or inherit from anything but NSObject.
Well I guess since nobody likes long questions (I'll have to figure out how to shorten this), I figured something out. For some reason, I get the hotkey call really quickly (the clipboard gets updated AFTER the key is called in fact). As a result, I just have a small delay, and my model is now updated properly:
NSTimer* briefDelay = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(determineAndAddToHistory) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:briefDelay forMode:NSRunLoopCommonModes];
I do not invalidate the timer manually, as per the documentation:
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
I know its something to do with locks or dispatch groups, but I just cant seem to code it...
I need to know if the address was a valid address before leaving the method. Currently the thread just overruns and returns TRUE. I've tried locks, dispatchers the works but can't seem to get it correct. Any help appreciated:
- (BOOL) checkAddressIsReal
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
}];
return result;
}
The documentation says that CLGeocoder calls the completionHandler on the main thread. Since you are probably also calling your method from the main thread it cannot wait for the geocoder's answer without giving it the opportunity to deliver the result.
That would be done by polling the runloop, using some API as -[NSRunLoop runMode:beforeDate:].
The disadvantage is that depending on the mode this will also deliver events and fire timers while waiting for the result.
Just use block as parameter:
- (void) checkAddressIsRealWithComplectionHandler:(void (^)(BOOL result))complectionHandler
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
complectionHandler(result);
}];
}
I have a problem with three clases. The the first class is called Player. This class has an NSMutableArray inside it called units. This array is made up of objects of the class Unit. This class in turn, has an NSMutableArray called bullets. It works like this:
At a certain point the class Player (it could just be the ViewController instead) adds an object to the units. Then, when an instance of Unit is initialized, as a result of the above, it creates an NSTimer that is in charge of creating bullets every second.
The thing is, is crashes somewhere in the middle of this with a SIGABRT that tells me that there was an exception because: Collection <__NSArrayM: 0xb1a2970> was mutated while being enumerated. Also, I took away the line that created bullets and it stops crashing, proving that is the problem. What does that mean!
Here is a bit of executable code that might work:
ViewController.h (instead of player)
#interface ViewController : UIViewController
{
NSMutableArray *units;
NSTimer *updateTimer;
}
-(void)Update;
ViewController.m
#implementation ViewController
//methods...
- (void)viewDidLoad
{
//more default code
//Initialized array and adds one object with the default constructor for simplicity
units = [[NSMutableArray alloc] initWithObjects:[[Unit alloc] init], nil]
}
-(void)Update
{
for(Unit *unit in units)
{
[unit Update];
if(unit.deleteFromList)
[units removeObject:unit];
}
}
//More methods
#end
Unit.h
#interface Unit : NSObject
{
NSMutableArray *bullets;
NSTimer *bulletTimer;
boolean deleteFromList;
}
#property(readonly, assign)deleteFromList;
-(void)Fire;
-(void)Update;
Unit.m
#implementation Unit
#synthesize deleteFromList;
-(id)init
{
if(self)
{
bullets = [[NSMutableArray alloc] init];
bulletTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(fire) userInfo:NULL repeats:true];
deleteFromList = false;
}
return self;
}
-(void)Fire
{
[bullets addObject:[[Bullet alloc] init]];
}
-(void)Update
{
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[bullets removeObject:bullet];
}
if(certainCondition)
deleteFromList = true;
}
The bullet class will be omitted because the contents are irrelevant to what happens. Also, all the classes and constructors were shortened because the rest is useless for this example
EDIT:
Another thing I forgot to add is that the timer is created in an enumeration of the NSMutableArray units in the update method i'm about to add. I'm also adding a variable to Unit and Bullet that orders it to delete. The bullet update changes the position and also changes the deleteFromList variable
You can't remove any item in NSMutableArray while for-loop or enumerating it.
Document: It is not safe to modify a mutable collection while enumerating through it. Some enumerators may currently allow enumeration of a collection that is modified, but this behavior is not guaranteed to be supported in the future.
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[bullets removeObject:bullet];
}
to
NSMutableArray *toRemove = [NSMutableArray array];
for(Bullet *bullet in bullets)
{
[bullet Update];
if(bullet.deleteFromList)
[toRemove addObject:bullet];
}
[bullets removeObjectsInArray:toRemove];
There are two easy approaches - one is to create an array of objects to remove, and the other is to iterate over a copy of the original way. This second technique can be somewhat easier to read.
Method 1
NSMutableArray *toRemove = [NSMutableArray array];
for (Bullet *bullet in bullets)
{
[bullet Update];
if (bullet.deleteFromList)
{
[toRemove addObject:bullet];
}
}
[bullets removeObjectsInArray:toRemove];
Method 2
for (Bullet *bullet in [bullets copy])
{
[bullet Update];
if (bullet.deleteFromList)
{
[bullets removeObject:bullet];
}
}
The first approach is slightly more verbose, but won't make a copy of the original array. If the original array is very large, and you don't want to make a copy of it for performance/memory reasons, the first approach is best. If it doesn't matter (99% of the time), I prefer the second approach.
As an aside, you shouldn't start a method name with a capital letter in Objective-C unless the method name starts with an abbreviation, e.g. URL, so [bullet Update] should really be renamed to [bullet update].
Or
NSIndexSet *indexSet = [units indexesOfObjectsPassingTest:^BOOL(Unit *udit, NSUInteger idx, BOOL *stop) {
[unit Update];
return unit.deleteFromList;
}];
[units removeObjectsAtIndexes:indexSet];
I populate a NSOutlineView reading a directory recursively.
After a directory and its subdirectories are read I refresh the outline calling reloadData inside a dispatch like shown below
-(void)readDir:(NSString*)path {
dispatch_async(dispatch_get_main_queue(), ^{ \
[outlineView reloadData];
});
//////////
//// some internal stuff
//////////
NSArray* subs = [self getSubDirs:path];
for (NSString* p in subs) {
[self readDir:p];
}
}
The method above is called from inside a dispatch to have the UI reactive
- (void)startAll {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self readDir:#"/"];
});
}
The problem is that sometimes dispatch_async(dispatch_get_main_queue(), ...) is called while subdirectories is reading (it's async at all!) and app crashes
If I use dispatch_sync(dispatch_get_main_queue(), ...) (note the sync version) the outline is drawn always correctly but it is very very slooooow so the question is:
how can I rearrange the code to be as fast as possible and wait that dispatch_[a]sync(dispatch_get_main_queue(), ...) has finished?
It sounds like the problem is that while some internal stuff is executing in one thread, your model is in an inconsistent state which reloadData can't properly handle.
Are you adding directory entries to their parent directory before they're sufficiently set up?
Late to this one, but it seems like it would make a lot more sense to write your readDir: like this:
-(void)readDir:(NSString*)path {
//////////
//// some internal stuff
//////////
NSArray* subs = [self getSubDirs:path];
for (NSString* p in subs) {
[self readDir:p];
// now refresh outline with newly acquired info and current data
dispatch_sync(dispatch_get_main_queue(), ^{ \
[outlineView reloadData];
});
}
}