I need some guidance in how to debug problems with concurrency in Cocoa under 10.6. I'm converting a 'for' loop to use NSOperations but most of the time, the code just freezes at some point through the loop. I can see NSLog output in the console to that effect. On the rare occasion, the code will run all the way through and it's fine.
The code is model-layer only, initiated from a method in the controller. The method loops through just 8-10 model objects, instructing them to each write their output to a uniquely named file. 8 model objects = 8 separate files. There are no calls up to the GUI and the model objects are NSManagedObject subclasses, which contain a set of child NSManagedObject objects (0..n of them), which each owning object summarizes and writes out. The output format is JSON.
Code:
__block NSMutableArray *collectionOfCourses = [[NSMutableArray alloc] initWithCapacity:[[self courses] count]];
/* Create a filename. Use our title and set it to lowercase */
NSURL *ourFileURL = [aURL URLByAppendingPathComponent:[[self title] lowercaseString]];
ourFileURL = [ourFileURL URLByAppendingPathExtension:#"js"];
for (Course *aCourse in [self courses]) {
[[self opQueue] addOperationWithBlock:^{
NSArray *arrayForOneCourse = [aCourse arrayAndWriteToFileURL:aURL fileFormat:format];
[collectionOfCourses addObject:arrayForOneCourse];
}];
}
I do a lot of NSLogs, would that be the issue? Is NSLog'ing from background threads a bad thing?
Since I'm adding to the mutable array from withinside a block, is it correct that I declare the mutable array as __block? I've tried it both ways and seemingly no difference relating to this freezing problem.
How do I debug this problem using Xcode v4? I want to know the line of code that it's freezing on, or what two lines of code are executing at the same time and causing it to block the execution.. My former techniques of setting a single breakpoint and stepping through the code no longer work, because of the concurrency.
thanks
It's nothing to do with your block-scoped variable. Your problem is that neither NSMutableArray nor NSManagedObject are in any way, shape or form thread-safe. You cannot do what you're doing here. If you want to have this processed off the main thread, you need to use a dispatch queue or something similar to process each item serially (and even when you're back on the main thread, you should use that same queue before you read from your mutable array. It'd probably be easier and safer, however, to do something like copy the mutable array to an immutable version when you're finished and then dispatch a notification or call-back to the main thread with the new, immutable copy embedded.
Related
I have the following code in viewDidLoad:
NSManagedObjectContext *moc=[[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setParentContext:[AppCoreDataHandler sharedManagedObjectContext]];
self.restroom=[NSEntityDescription insertNewObjectForEntityForName:#"Restroom"
inManagedObjectContext:moc];
self.restroom.coordinate=[((AppDelegate *)[[UIApplication sharedApplication] delegate]) locationManager].location.coordinate;
self.review=[NSEntityDescription insertNewObjectForEntityForName:#"Review"
inManagedObjectContext:moc];
[self.restroom addReviewsObject:self.review];
Testing using the debug console shows that self.restroom.reviews is equal to an NSSet containing a review. By the time I reach 'viewWillAppear', self.restroom.reviews is nil. I'm not setting reviews to nil ANYWHERE in my code. Self.review remains completely valid the entire time. It simply doesn't make sense!
I'm at my wits end trying to figure this out. It can't be any multithreaded code, I'm not assigning anything else to the property/relationship, the object isn't weakly retained and releasing itself, it just... vanishes. Why?
Edit: Even more fun, I used key value observing to check on WHEN the value is changed.
From what I can read of the stack trace, it's being changed INSIDE the viewDidLoad function. But when I step-through the code, the key value notifications are clearly sent AFTER the function ends.
By pure dumb luck (read: hrm, I wonder if... nope. Ok then. I wonder if... and repeat for about two bazillions iterations), I figured it out.
The child MOC I created for these objects gets deallocated not after the last reference to it (which is what I'd expect ARC to do), but rather at the end of the function. And when it dies, it didn't deallocate the objects it was assigned to, but it did strip them of their relationships. (I'm going to guess that while it LOOKS like an NSSet is providing those, it was actually something in the guts of the MOC).
I have one global instance for one of NSManagedObject type. For the global instance, it has few member variables who are #dynamic properties declared in the following way
#property (retain) NSString *value;
And I have a few threads that would do the following simple operations
myInstance.value = [NSString stringWithString:newValue];
So the question is - do I need to synchronize the operation above? or is it naturally thread-safe already (as they are taking care by NSManagedObject)?
According to the documentation, this is not thread-safe. There's a whole article about threading and Core Data which you can read here. Essentially, it says that to properly use threading with Core Data, you need a separate managed object context for each thread. In the "If You Don’t Use Thread Containment" section, it specifically notes that both reading and mutating managed objects across threads can have unwanted effects.
What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.
When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.
During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.
I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.
What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.
Thanks to anyone willing to help out.
UPDATE
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).
I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:
Example:
[[self mutableArrayValueForKey:#"seatedPlayers"] addObject:person];
You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)
- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
[seatedPlayers insertObject:object atIndex:index];
}
Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.
I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling
insertObject:atArrangedObjectIndex:
on the ArrayController?
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?
First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.
Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.
What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:
[self addSeatedPlayersObject:person];
And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.
The advantages of this over setSeatedPlayers: are:
Your code to mutate the set will be shorter.
Because it's shorter, it will be cleaner.
Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.
I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)
The key to the magic of Key Value Observing is in Key Value Compliance. You initially were using a method name addObject: which is only associated with the "unordered accessor pattern" and your property was an indexed property (NSMutableArray). When you changed your property to an unordered property (NSMutableSet) it worked. Consider NSArray or NSMutableArray to be indexed properties and NSSet or NSMutableSet to be unordered properties. You really have to read this section carefully to know what is required to make the magic happen... Key-Value-Compliance. There are some 'Required' methods for the different categories even if you don't plan to use them.
Use willChangeValueForKey: and didChangeValueForKey: wrapped around a change of a member when the change does not appear to cause a KVO notification. This comes in handy when you are directly changing an instance variable.
Use willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects: wrapped around a change of contents of a collection when the change does not appear to cause a KVO notification.
Use [seatedPlayers setByAddingObject:sp] to make things shorter and to avoid needlessly allocating mutable set.
Overall, I'd do either this:
[self willChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
or this:
[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];
with the latter alternative causing an automatic invocation of the functions listed under 1. First alternative should be better performing.
I have a WebView that I need to ask to load a website from a different class.Therefore I implemented the following function:
-(void)performResearch:(NSString *)aString {
NSLog(#"Received Request!");
[[researcher mainFrame]
loadRequest:[NSURLRequest
requestWithURL:[NSURL URLWithString:aString]]];
}
In my other class, I call the function like this:
Researcher *res = [[Researcher alloc] init];
[res performResearch:#"http://www.twitter.com"];
[res release];
I get no compiler errors and the NSLog does indeed get called, however the WebView doesn't load the webpage. No errors in the Log either.I'm puzzled.
Could anyone tell me what's wrong?
-(void)performResearch:(NSString *)aString {
NSLog(#"Received Request!");
[[researcher mainFrame]
loadRequest:[NSURLRequest
requestWithURL:[NSURL URLWithString:aString]]];
}
In my other class, I call the function like this:
Researcher *res = [[Researcher alloc] init];
[res performResearch:#"http://www.twitter.com"];
[res release];
Within performResearch:, the object you sent the performResearch: message to in the latter code is self. researcher is some other variable; you are talking to a different object that is probably not this Researcher.
In your comment on Colin Gislason's answer, you say that researcher is not a Researcher, but is a WebView. You would do well to correct this inaccurate choice of variable name.
Once you've renamed the variable to something more accurate, like webView, you then need to make sure there is something in it. Simply having a variable is of no help; you need to have a pointer to an object in the variable. Otherwise, the pointer in the variable is nil, and messages to nil do nothing at all. They do not crash, they do not fail, they do not succeed, they do not break—execution continues as if nothing happened, because it did.
You say that the variable is an IBOutlet; this implies that you mean to hook up the outlet in IB. One possible cause of your problem is that you forgot to do that, which would explain the variable containing nil. Make sure it's connected in IB.
The other possible cause of your problem is that this nib has not been loaded. If the Researcher object owns the nib, then it is generally its responsibility to load it, which means you need to do that. If some other object is loading a nib with both a Researcher and a WebView in it, then your creation of another Researcher object in the latter code snippet is wrong; this second Researcher object has no relation to the one in the nib, nor has it a WebView from any source. In this latter case, you would have to cut out the creation of a Researcher object, and use the one in the nib.
That last solution may require adding an outlet to the object whose class hosts the second code snippet. I disapprove of that solution. It's difficult to say what you should do because I'm not sure where Researcher objects fit in the MVC triumvirate. I suspect you need to think some more about which category it's in; I think that it should be a Controller, and that you will need to do some refactoring to re-establish the boundaries you should have between powers.
Failure to uphold MVC in your app generally results in Cocoa being harder to use than it normally is.
You don't show where researcher is declared, but it looks like a normal instance variable. Where does the value come from?
If it comes from outside the Researcher class, then it looks like it is nil because your other class is creating a new Researcher instance but not providing the researcher attribute.. If you pass messages to nil objects, the message is ignored. That is why you don't get an error. Try breaking the load request line out into several lines and log the values of the objects.
EDIT:
Interface Builder sets the UIWebView attribute of one particular instance of the Researcher class. To update that web view, you need to call performResearch on the same instance. You cannot use a new instance because it won't be connected to a web view at all.
If possible, you should pass the original Researcher object to the new class and use that instead:
[self.researcher performResearch:#"http://www.twitter.com"];
I'm having a lot of issues with NSDate objects being prematurely deallocated. I suspect that the issues may be related to the way that I deal with the objects returned from NSDate convenience methods. I think that my showDate property declaration in the JKShow class should be "retain", but changing it to assign or copy seems to have no effect on the issue.
JKShow *show;
NSDate *date;
NSMutableArray *list = [[NSMutableArray alloc] init];
// Show 1
show = [[JKShow alloc] init];
//...
date = [gregorian dateFromComponents:dateComponents];
show.showDate = date;
[list addObject:[show autorelease]];
// Show 2
show = [[JKShow alloc] init];
//...
date = [gregorian dateFromComponents:dateComponents];
show.showDate = date;
[list addObject:[show autorelease]];
UPDATE
The issue was not in the code copied here. In my JKShow init method I was not retaining the date returned from the NSDate convenience method. Thanks for your help, everyone.
The date returned from dateFromComponents should be in the autorelease pool, so you are correct that your showDate property should be "retain". In fact it should be anyway (unless you specifically want "copy").
From the code you have shown it looks like you are giving ownership of your show object entirely to the list (as you're setting autorelease on them as you add them). Are you saying that the date objects are being deallocated before the show objects are coming out of the list (or the list is being deallocated)?
Also, are you using synthesised properties, or are you writing them by hand? If the latter, what is your setShowDate property method like?
You can also try logging the retainCount of the date object at different places (although I always find that autorelease really complicates that).
If showDate is a retain property that should be sufficient, given the code you have posted. Something else (probably in JKShow's implementation) may not be correct.
If you want to figure out what is going on, you can use Instruments to see examine the objects lifespan. You need to run it with the allocation tool set to remember retains and releases. By default it is set up that way if you run the leaks performance tool.
When you run Instruments like that it will record all object life spans, and the backtrace for every retain and release issued against them. If you look through the objects, find one of your dates, and look at all the retains and releases you should be able to determine where the spurious release is happening.
The code you showed has no premature-release problems. In fact, it will leak the array and everything in it, because it doesn't release the array.
Are you running with the garbage collector turned on?
Is list an instance variable or static variable, or is it a local variable?
I figured it out, thanks for all your help, but the problem was outside of the code I posted here. I was not retaining the NSDate I created in my init method. Unfortunatly the crash didn't occur until after I had created the two new NSDate objects, so I was totally barking up the wrong tree.