I detach a new NSThread withObject:self so that the thread can callback the main thread as a delegate. However, I also need the new thread to be able to read some values in the parent. With NSThread, I can only pass one object withObject, and I'm using that to pass self because of the delegate methods. Is there a way my new thread can read values from it's parent? Perhaps through the self object that is passed to it?
Here's where I launch the thread:
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] init];
[NSThread detachNewThreadSelector:#selector(doWorkWithDelegate:)
toTarget:multicastDaemon
withObject:self];
I want to pass a multicast IP address and port number to the daemon, so he knows what to listen on, but I'm not sure how to get those values to multicastDaemon.
How can multicastDaemon access those values?
Yes, you can access the variables by making them properties and then doing something like this (you don't say what the class is that this call is made from, so I've called it MyClass):
#implementation MulticastDaemon
-(void) doWorkWithDelegate:(MyClass*) cls
{
cls.value1 = 12;
...
}
...
#end
EDIT: Corrected implementation.
You'd better use the subclass of NSOperation and then add it to the NSOperationQueue. You can add any additional parameters to that operation subclass.
There is also another advantage of NSOperation over NSThread. NSOperation and NSOperationQueue are build on top of the GCD and threading is far more optimal then NSThread.
But you can also simply add some properties to your MulticastDaemon.
You can change your MulticastDaemon's interface slightly so that you set the delegate before creating the new thread. Then you free up the withObject: slot to pass something else along. This avoids accessing variables across threads.
Either:
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] initWithDelegate:self];
[NSThread detachNewThreadSelector:#selector(doWorkWithInformation:)
toTarget:multicastDaemon
withObject:operatingInfo];
Or
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] init];
[multicastDaemon setDelegate:self];
Otherwise, you'll have to create a method that the daemon can call on its delegate that gathers and packages up the information to pass back. In that case, you'll probably have to start worrying about thread safety.
Related
I've been having problems with my multi-threaded Core Data enabled app, and I figured I should take a hard look at what I'm doing and how. Please let me know if the following should work.
I have a singleton DataManager class that handles the Core Data stuff. It has a property managedObjectContext that returns a different MOC for each thread. So, given NSMutableDictionary *_threadContextDict (string thread names to contexts) and NSMutableDictionary *_threadDict (string thread names to threads), it looks something like this:
-(NSManagedObjectContext *)managedObjectContext
{
if ([NSThread currentThread] == [NSThread mainThread])
{
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
return delegate.managedObjectContext; //MOC created in delegate code on main thread
}
else
{
NSString *thisThread = [[NSThread currentThread] description];
{
if ([_threadContextDict objectForKey:thisThread] != nil)
{
return [_threadContextDict objectForKey:thisThread];
}
else
{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
[_threadContextDict setObject:context forKey:thisThread];
[_threadDict setObject:[NSThread currentThread] forKey:thisThread];
//merge changes notifications
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification object:context];
return context;
}
}
}
}
In the mergeChanges method, I merge the changes from the incoming notification to all contexts except the one that generated the notification. It looks like this:
-(void)mergeChanges:(NSNotification *)notification
{
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = delegate.managedObjectContext;
[context performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification)
withObject:notification waitUntilDone:YES];
for (NSString *element in [_threadContextDict allKeys])
{
if (![element isEqualToString:[[NSThread currentThread] description]])
{
NSThread *thread = [_threadDict objectForKey:element];
NSManagedObjectContext *threadContext = [_threadContextDict objectForKey:element];
[threadContext performSelector:#selector(mergeChangesFromContextDidSaveNotification)
onThread:thread withObject:notification waitUntilDone:YES];
}
}
}
Whenever I save changes on a MOC, it's done with a call to a saveContext method on this shared DataManager, which calls save on a context obtained from the aforementioned property:
-(void)saveContext
{
NSManagedObjectContext *context = self.managedObjectContext;
NSError *err = nil;
[context save:&err];
//report error if necessary, etc.
}
Given my understanding of the Core Data multithreading rules, I feel like this should work. I'm using a separate context for each thread, but the same persistent store for all of them. But when I use this, I get a lot of merge conflicts, even though my threads aren't working on the same objects (NSManagedObject subclasses). I'm just downloading data from the network, parsing the results, and saving them to Core Data.
Am I doing something wrong? I've tried using NSLock instances to lock around some things, but then I just get hangs.
UPDATE/RESOLUTION: I was able to make this work by adding one simple thing: a way to remove a thread/MOC pair from my dictionary when I'm finished with it. At the end of each block in each call to dispatch_async where I do Core Data stuff, I call [self removeThread], which removes the current thread and its MOC from the dictionary. I also only merge changes to the main thread MOC. Effectively, this means that every time I do work on a background thread, I get a fresh new MOC.
I also distinguish threads by adding a number to userInfoDict, instead of calling description. The number is obtained by a readonly property on my class that returns a higher number each time it's called.
With all due respect, your approach is a nightmare, and it should be even worse to debug it to solve anything if there is a problem with it. First problem is this:
I have a singleton DataManager
Do not have a singleton object that manages core data manipulation with different entities on different threads. Singletons are tricky to deal with, especially on multithreading environment, and is even a worse approach to use it with core data.
Second thing, do not use NSThread to work on multithreading. There are more modern APIs. Use Grand central dispatch or NSOperation/NSOperationQueue. Apple has encouraged people to move from NSThread since the introduction of blocks (iOS 4). And for future reference, do not use the description of an object the way you are using it. Descriptions are usually/mostly used for debugging purposes. The information there should not be used to compare. Not even the pointer value (which is why you should use isEqual instead of ==).
This is what you need to know about core data and multithreading:
Create one context per thread. The core data template has already created a main thread context for you. At the start of the execution of the background thread (inside the block, or on the main method of your NSOperation subclass), initialize your context.
Once your context is initialize, and has the right persistentStoreCoordinator, listen to the NSManagedObjectContextObjectsDidChangeNotification. The object listening to the notification will receive the notification in the same thread the context was being saved. Since this is different than the main thread, do the merge call with the merging context on the thread the receiving context is being used. Let's say that you are using a context inside a thread different than the main thread, and you want to merge with the main thread, you need to call the merge method inside the main thread. You can do that with
dispatch_async(dispatch_get_main_queue(), ^{//code here});
Do not use an NSManagedObject outside the thread where its managedObjectContext lives.
With these and other, simple rules, managing core data under a multithreading environment is easier. Your approach more difficult to implement, and worse to debug. Make some changes to your architecture. Manage the context depending on the thread you are working with (instead of centralized). Do not keep references to context outside of their scope. Once your first context is created, it is not expensive to be creating contexts on your threads. You can reuse the same context, as long as it's inside the same block/NSOperation execution.
I'm searching for a nice way to perform a selector on the main thread with two parameters
I really like using
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
method, except now I have two parameters.
So basically I have a delegate which I need to notify when the image is loaded:
[delegate imageWasLoaded:(UIImage *)image fromURL:(NSString *)URLString;
But the method where I do this might be invoked in the background thread, and the delegate will use this image to update the UI, so this needs to be done in the main thread. So I really want the delegate to be notified in the main thread as well.
So I see one option - I can create a dictionary, this way I have only one object, which contains two parameters I need to pass.
NSDictionary *imageData = [NSDictionary dictionaryWithObjectsAndKeys:image, #"image", URLString, #"URLstring", nil];
[(NSObject *)delegate performSelectorOnMainThread:#selector(imageWasLoaded:) withObject: imageData waitUntilDone:NO];
But this approach does not seem right to me. Is there more elegant way to do this? Perhaps using NSInvocation?
Thanks in advance.
Using an NSDictionary to pass multiple parameters is the right way to go about it in this case.
However, a more modern method is to use GCD and blocks, this way you can send messages to an object directly. Also, it looks as if your delegate method might be doing something UI updates; which you are correctly handling on the main thread. With GCD you can do this easily, and asynchronously like this:
dispatch_async(dispatch_get_main_queue(), ^{
[delegate imageWasLoaded:yourImage fromURL:yourString;
});
Replace your performSelector:withObject call with this, and you won't have to mess around with changing your method signatures.
Make sure you:
#import <dispatch/dispatch.h>
to bring in GCD support.
Since you don't have access to GCD, NSInvocation is probably your best choice here.
NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
[invoke setTarget:delegate]; // argument 0
[invoke setSelector:selector]; // argument 1
[invoke setArgument:&arg1 atIndex:2]; // arguments must be stored in variables
[invoke setArgument:&arg2 atIndex:3];
[invoke retainArguments];
/* since you're sending this object to another thread, you'll need to tell it
to retain the arguments you're passing along inside it (unless you pass
waitUntilDone:YES) since this thread's autorelease pool will likely reap them
before the main thread invokes the block */
[invoke performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:NO];
Following method can also be used:
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
As per the docs of this method-
Invokes a method of the receiver on the current thread using the default mode after a delay.
Yes, you've got the right idea: you need to encapsulate all the data you want to pass to the delegate on the main thread into one single object which gets passed along via performSelectorOnMainThread. You can pass it along as a NSDictionary object, or a NSArray object, or some custom Objective C object.
I have two static methods/selectors in the same class, one passes the other as a callback to an external method. However, how I have it coded I get an error. This worked when both the methods were instance methods, and I've read it can work when the first method is an instance method using [self class]. However, I haven't found information when both are static, and I haven't got it to work.
+(void)Validate {
Callback *managerCallback = [[[Callback alloc] initWithTarget:self Action:#selector(Parse:)] autorelease];
...
}
+(void)Parse:(Callback *)managerCallback {
...
}
Thanks!
Callback *managerCallback = [[[Callback alloc] initWithTarget:self Action:#selector(Parse:)] autorelease];
That line of code is setup to call the instance method Parse:, not a class method as you have it defined.
Objective-C does not have static methods. It has class methods and instance methods.
As well, your methods should start with lowercase letters.
Herp-da-derp. Dave is right.
Given this:
+(void)Validate {
Callback *managerCallback = [[[Callback alloc] initWithTarget:self Action:#selector(Parse:)] autorelease];
...
}
+(void)Parse:(Callback *)managerCallback {
...
}
Some comments:
methods should start with lowercase letters
it is exceedingly odd to use a class in such a role; even if you really only ever need one of 'em, use an instance. At the least, the instance is a convenient bucket to toss state in and it'll make refactoring in the future much easier if you ever need two.
The above pattern makes the assumption (and I ASSumed) that the instance of Callback is retained. For callbacks, timers, and some other patterns, this is typical; retain the target until the target is called for the last time. Then release (or autorelease). However, notification centers do not do this. Nor are delegates retained, typically.
Turns out the code is written correctly to do what I wanted to, but because callback was set to autorelease, the object was getting released before the callback was being processed.
I am creating instances of a class FlickrImage parsing a Flickr API photos response. The class has a method getLocation that does another API call to get the geolocation:
NSLog(#"getting location for %i",self.ID);
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
OFFlickrAPIRequest *flickrAPIRequest = [[OFFlickrAPIRequest alloc] initWithAPIContext[appDelegate sharedDelegate].flickrAPIContext];
[flickrAPIRequest setDelegate:self];
NSString *flickrAPIMethodToCall = #"flickr.photos.geo.getLocation";
NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,#"api_key",self.ID,#"photo_id",nil];
[flickrAPIRequest callAPIMethodWithGET:flickrAPIMethodToCall arguments:requestArguments];
[pool release];
I have implemented the callback method that would catch the response from the API and update the FlickrImage instance with the geolocation data - but it never gets called. Here's where the instances get created:
NSDictionary *photosDictionary = [inResponseDictionary valueForKeyPath:#"photos.photo"];
NSDictionary *photoDictionary;
FlickrImage *flickrImage;
for (photoDictionary in photosDictionary) {
flickrImage = [[FlickrImage alloc] init];
flickrImage.thumbnailURL = [[appDelegate sharedDelegate].flickrAPIContext photoSourceURLFromDictionary:photoDictionary size:OFFlickrThumbnailSize];
flickrImage.hasLocation = TRUE; // TODO this is actually to be determined...
flickrImage.ID = [NSString stringWithFormat:#"%#",[photoDictionary valueForKeyPath:#"id"]];
flickrImage.owner = [photoDictionary valueForKeyPath:#"owner"];
flickrImage.title = [photoDictionary valueForKeyPath:#"title"];
[self.flickrImages addObject:[flickrImage retain]];
[flickrImage release];
[photoDictionary release];
}
The retain is there because I thought it might help solve this but it doesn't - and doesn't the NSMutableArray (flickrImages is a NSMutableArray) retain its members anyway?
EDIT I should add that the getLocation method (first code snippet) is launched in a thread:
[NSThread detachNewThreadSelector:#selector(getLocation) toTarget:self withObject:nil];
Your delegate method is never being called because the request is never being made. When you call callAPIMethodWithGET:, it sets up communications to run asynchronously on the current thread's run loop, then returns immediately. That way you can safely call it on the main thread without blocking.
Because you are calling the method from a thread you created yourself, it does not see the main run loop, but the run loop for your new thread. However, because you never execute the run loop, the messages are never sent, a response is never received, and your delegate is never called.
You could fix this by calling [[NSRunLoop currentRunLoop] run] in your new thread. That will let the work happen. But in this case would be easier to never detach a new thread in the first place. Your program won't block, and you won't have to worry about your delegate method needing to be reentrant.
I've also run into this problem when requesting and parsing XML on a different thread my solution was to do this:
while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:start] && !isFinished){
}
Where start = [NSDate dateWithTimeIntervalSinceNow:3]; this is basically a timeout so that it doesn't live forever and isFinished is set to true when my parsing has completed.
I'm not familiar with these flicker API wrappers, but in this code:
NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,#"api_key",self.ID,#"photo_id",nil];
Are you certain that both FLICKR_API_KEY, and self.ID are not nil? If either of them is nil, you'll end up with a dictionary that has less items in it than you intend.
Could you post the callback method(s) you have implemented – this could be just down to a simple typo, as it appears OFFlickrAPIRequest won’t do anything if the delegate does not implement the required callback.
Did you also implement flickrAPIRequest:didFailWithError: to see if there was an error returned from the API call?
Okay, I did solve it, with help from some of the suggestions above.
I did remove the extra retain because it did in fact create a memory leak. It did not look right from the outset, so my gut feeling about that is worth something, which is a good thing ;)
I removed the redundant threading because the API call is already asynchronous and does not require an additional thread to be non-blocking. After that, the callback method was being called but I ran into different problems concerning object retention. If interested you might want to check out that question, too
Thanks all.
The setDelegate method of OFFlickrAPIRequest does not retain the delegate like it should. This means you're stuck ensuring that your delegate is alive as long as the request is (or patching the class to properly own its own references).
I don't plan to write applications without IB, I'm just in the process of trying to learn more about programming.
How can I get a single instance of my AppController class at startup? (It's normally loaded from the nib.) And can you clear up the use of +initialize and -init? If I understand, +initialize is called on all classes at startup. How can I use this to create an instance of my AppController with instance variables that make up my interface?
Hope that makes sense, and thanks for any help.
+initalize is sent to a class the first time it or one of its subclasses receives a message for the first time. So, when you do:
instance = [[[YourClass alloc] init] autorelease];
That alloc message triggers initialize.
If you do the same thing with a subclass:
instance = [[[SubclassOfYourClass alloc] init] autorelease];
That alloc message will trigger +[YourClass initialize] the same way the other one did (prior to also triggering +[SubclassOfYourClass initialize]. But only one of these will do it—each class's initialize never gets called more than once. (Unless you call it yourself with [super initialize] or [SomeClass initialize]—so don't do that, because the method won't be expecting it.)
-init, on the other hand, initializes a new instance. In the expression [[YourClass alloc] init], you are personally sending the message directly to the instance. You may also call it indirectly, through another initializer ([[YourClass alloc] initWithSomethingElse:bar]) or a convenience factory ([YourClass instance]).
Unlike initialize, you should always send init (or another initializer, if appropriate) to your superclass. Most init methods look roughly like this:
- (id) init {
if ((self = [super init])) {
framistan = [[Framistan alloc] init];
}
return self;
}
Details differ (this method or the superclass's or both may take arguments, and some people prefer self = [super init] on its own line, and Wil Shipley doesn't assign to self at all), but the basic idea is the same: call [super init[WithSomething:…]], make sure it didn't return nil, set up the instance if it didn't, and return whatever the superclass returned.
This implies that you can return nil from init, and indeed you can. If you do this, you should [self release], so that you don't leak the failed object. (For detecting invalid argument values, an alternative is NSParameterAssert, which throws an exception if the assertion fails. The relative merits of each are beyond the scope of this question.)
How can I use this to create an instance of my AppController with instance variables that make up my interface?
The best way is to do it all in main:
int main(int argc, char **argv) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
AppController *controller = [[[AppController alloc] init] autorelease];
[[NSApplication sharedApplication] setDelegate:controller]; //Assuming you want it as your app delegate, which is likely
int status = NSApplicationMain(argc, argv);
[pool drain];
return status;
}
You'll do any other set-up in your application delegate methods in AppController.
You already know this, but for anyone else who reads this: Nibs are your friend. Interface Builder is your friend. Don't fight the framework—work with it, and build your interface graphically, and your application will be better for it.
Another solution to the problem of launching an app without a nib.
Instead of allocing your own controller, just use the extra parameters in the NSApplicationMain() method:
int retVal = NSApplicationMain(argc, argv, #"UIApplication", #"MyAppDelegate");
This takes care of all the proper linking one would need.
Then, the only other thing you'd need to remember is to make your own window and set it to visible.
A set of NIBs seem to be an unsatisfactory answer, even when represented in XML (as a XIB), because there's no easy way to compare or merge them with any standard subversion or SCM-style tool. The encoded information is fragile and not intended to be edited by mere humans. How would changes be represented by a GUI? Would I step through each attribute of each control and visually check them?
If the app's behavior is written in code, however, there is a chance that I can figure out what's going in, even if I have to keep lots of details close at hand at the same time.
A proposed solution: use a top-level NIB that the main architect coded up, but then code the rest of the app explicitly.
Anybody got a better idea?