I'm new to the concept of delegates and selectors when used with notifications. So my first question is,
1) Let's say you have a button that has a delegate that implements some doWork method. If you want the same functionality that's in the method, is it 'ok' to just call that method? I didn't know if that was considered good coding practices and/or if you should do that, or do something different in getting that type of functionality. Like if that is ok architecture?
2) Similarly, with NSNotificationCenter, I see some code that posts a notification. Then there's a HandleSegmentedControl:(NSNotification *)notification method. If I want to manually have that functionality, but without pressing the segment control, is it 'ok' to just take that functionality out of that method and put it in a new method so it would look like this:
Original:
- (void)HandleSegmentedControl:(NSNotification *)notification {
NSDictionary *dict = [userInfo notification];
// do stuff with the dictionary
}
New:
- (void)HandleSegmentedControl:(NSNotification *)notification {
NSDictionary *dict = [userInfo notification];
[self newMethod:dict];
}
- (void)newMethod:(NSDictionary *)dict {
// do stuff with the dictionary
}
- (void)myOtherMethodThatNeedsTheSameFunctionality {
NSDictionary *dict = // create some dictionary
[self newMethod:dict];
}
Sorry if these are basic questions. I'm not sure what the best practices are for things like this and wanted to start the right way. Thanks.
If the delegate protocol implements the doWork method as a required method, then yes. However, if it is an optional method, or if you want to be extra safe, you should use
if ([delegate respondsToSelector:#selector(doWork)]) {
[delegate doWork];
}
Sure, that seems like a reasonable thing to do. It is a common way to make your code more robust. The notification's userInfo is there so you can send data as you like. However, I think in your code you meant [notification userInfo] not [userInfo notification].
Related
I am converting my application from Syncronous to Asyncronous HTTP requests and have ran into a problem that looks like it will require quite a big reworking of how the application handles its data. Let me try to explain
Previously it was like this:
-Class1, Class2 and Class3 were all subclasses of UIViewController
-Helper class
-Content display class
They do broadly different things but the common trait is their interaction with the helper class. They gather details of a request in a number of different ways from a user and then eventually send a request to the helper class.
When it was done syncronously the helper class would return the data. Each class would then interpret the data (XML files) and pass them on to the Content display class via a segue
So something broadly like this:
Class1:
//Get user input
SomeData *data = [helperclass makerequest];
id vcData = [data process];
[self performSegueWithIdentifier:#"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
Content display class:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.data presentdata];
}
Now it looks like this
I dealt with this problem by first making it work with Class1 with a view to deploying the fix to class2 and class3. So class1 and helper now interact like this
Class1:
//Get user input
SomeData *data = [helperclass makerequestWithSender:self];
id vcData = [data process];
[self performSegueWithIdentifier:#"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
destination.data = vcData;
}
Now the biggest problem I am facing is how to get the data from helperclass back to Class1. I managed to get it to work by doing
(void)makeRequestWithSender:(Class1*)sender
{
[NSURLConnection sendAsynchronousRequest:...
{
[sender sendData:data];
}
}
However, when I have came to roll this out to the other 2 GUI classed which will compose the request I am having difficulty with. My first thought was to set sender:(id) but that fails at the line [sender sendData:data] telling me that id does not have an method sendData: or similar.
Hopefully I wasn't too vague here and you guys can help. If required I will be able to post code snippets but for now can anyone help with a better suggestion about how to structure the code for this request?
You basically want to use the 'observer pattern' or a (maybe) slightly changed setup, so you can use delegation.
Observer pattern
You gain the mechanic via the NSNotificationCenter and NSNotifications. Your 3 different UIViewController subclasses each subscribe to a specific NSNotification and you notify them via posting a notification via the NSNotificationCenter.
The following code is an example of how you can approach the problem in your viewcontroller subclasses:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// subscribe to a specific notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomethingWithTheData:) name:#"MyDataChangedNotification" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// do not forget to unsubscribe the observer, or you may experience crashes towards a deallocated observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
...
- (void)doSomethingWithTheData:(NSNotification *)notification {
// you grab your data our of the notifications userinfo
MyDataObject *myChangedData = [[notification userInfo] objectForKey:#"myChangedDataKey"];
...
}
In your helper class, after the data changed you have to inform the observers, e.g.
-(void)myDataDidChangeHere {
MyDataObject *myChangedData = ...;
// you can add you data to the notification (to later access it in your viewcontrollers)
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDataChangedNotification" object:nil userInfo:#{#"myChangedDataKey" : myChangedData}];
}
via #protocol
Presuming all your UIViewController subclasses reside in a parent viewcontroller, you can implement a protocol in your helper class and make the parent viewcontroller the delegate. Then the parent viewcontroller may inform the child uiviewcontrollers via passing a message.
Your helper class declaration could look like this (presuming ARC):
#protocol HelperDelegate;
#interface Helper : NSObject
#property (nonatomic, weak) id<HelperDelegate> delegate;
...
#end
#protocol HelperDelegate <NSObject>
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data;
#end
In the helper implementation you would inform the delegate via:
...
if ([self.delegate respondsToSelector:#selector(helper:dataDidChange:)]) {
[self.delegate helper:self dataDidChange:myChangedDataObject];
}
...
Your parent viewcontroller would need to be the delegate of the helper class and implement its protocol; a rough sketch, in the declaration
#interface ParentViewController : UIViewController <HelperDelegate>
and for the implementation in short version
// you alloc init your helper and assign the delegate to self, also of course implement the delegate method
-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data {
[self.myCustomChildViewController doSomethingWithTheNewData:data];
}
Besides..
You might ask yourself which method to prefer. Both are viable, the main difference is that via the observer pattern you get more objects to be informed 'at once', whereas a protocol can only have one delegate and that one has to forward the message if needed. There are a lot of discussions around about pros and cons. I'd suggest you read up on them once you made up your mind (sorry ain't got enough reputation to post more than two links, so please search on stackoverflow). If something is unclear, please ask.
Some reasonable ideas here. To elaborate/add my opinion:
First, which object ought to tell the downloader (HelperClass) to begin downloading? My practice is to do this in the view controller that will present the data. So I generally start network requests after a segue (like in viewWillAppear: of the presented vc), not before.
Next, when one class needs to execute code provided for another, I first think about if it makes sense to do it using a block. Very often (not always) blocks make more sense and provide more readable code than, say, delegate, notification, KVO, etc. I think NSURLConnection completion, for example, is better suited to blocks than delegate. (and Apple kind of agrees, having introduced + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler).
So my pattern for your app would be this:
// Class1.m
// when user has completed providing input
...
// don't do any request yet. just start a segue
[self performSegueWithIdentifier:#"ToContentDisplayClass" sender:self];
...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// don't do a request yet, just marshall the data needed for the request
// and send it to the vc who actually cares about the request/result
if ([segue.identifier isEqualToString:#"ToContentDisplayClass"]) {
NSArray *userInput = // collect user input in a collection or custom object
ContentDisplayClass *vc = segue.destinationViewController;
vc.dataNeededForRequest = userInput;
}
...
Then in ContentDisplayClass.m
// this is the class that will present the result, let it make the request
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
HelperClass *helper = [[HelperClass alloc]
initWithDataNeededForRequest:self.dataNeededForRequest];
// helper class forms a request using the data provided from the original vc,
// then...
[helper sendRequestWithCompletion:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// interpret data, update view
self.label.text = // string we pulled out of data
} else {
// present an AlertView? dismiss this vc?
}
}];
This depends on HelperClass implementing the block form of NSURLConnection
// HelperClass.m
- (id)initWithDataNeededForRequest:(id)dataNeededForRequest {
// standard init pattern, set properties from the param
}
- (void)sendRequestWithCompletion:(void (^)(NSURLResponse *, NSData *, NSError *))completion {
NSURLRequest *request = ...
// the stuff we need to formulate the request has been setup in init
// use NSURLConnection block method
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:completion];
}
Edit - there are several rationale's for making the VC transition before starting the network request:
1) Build the standard behavior around the success case: unless the app is about testing network connections, the success case is that the request works.
2) The cardinal principal for an app is to be responsive, to do something sensible immediately upon user actions. So when the user does something to initiate the request, an immediate vc transition is good. (what instead? a spinner?). The newly presented UI might even reduce the perceived latency of the request by giving user something new to look at while it runs.
3) What should an app do when a request fails? If the app doesn't really need the request to be useful, then doing nothing is a good option, so you'd want to be on the new vc. More typically, the request is necessary to proceed. The UI should be "responsive" to request failure, too. Typical behavior is to present an alert that offers some form of "retry" or "cancel". For either choice, the place the UI wants to be is on the new vc. Retry is more obvious, because that's where it always is when it tries to fetch the data. For cancel, the way to be "responsive" to cancel is to go back to the old vc, a vc transition back isn't ugly, it's what the user just asked for.
I'm not 100% clear on how you're handling the data now, but to change your data to asynchronous calls, I would use blocks. For instance your current synchronous code like this:
//Get user input
data = [helperclass makerequest]
sendData = [data process]
would turn into something like this:
//Get user input
data = [helperclass makerequestWithSuccess:^{
sendData = [data process]
}];
Using a success block will allow you to wait to process the data until the makerequest was finished.
Your new makerequest function would now look like this:
-(void)makerequestWithSuccess:(void (^)(void))success{
// Put your makerequest code here
// After your makerequest is completed successfully, call:
success();
}
Hope this helps!
I'm not sure that I understood your problem correctly, but if it's sort of:
Start task A asynchronously.
When task A finished successfully, get its result and start task B whose input is result A.
When task B finished successfully, get its result and start task C whose input is result B.
...
When finished successfully, be happy, otherwise print error.
A code example would look like this:
typedef (void)(^completion_block_t)(id result);
-(void) asyncTaskA:(completion_block_t)completionHandler;
-(void) asyncTaskBWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncTaskCWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler
{
[self asyncTaskA:^(id resultA){
if (![resultA isKindOfClass:[NSError class]]) {
[self asyncTaskBWithInput:resultA completion:^(id resultB){
if (![resultB isKindOfClass:[NSError class]]) {
[self asyncTaskCWithInput:resultB completion:^(id resultC) {
completionHandler(resultC);
}];
}
else {
completionHandler(resultB); // error;
}
}];
}
else {
completionHandler(resultA); // error
}
}];
}
And you use it like:
[self asyncSomethingWithCompletion:^(id result){
if ([result isKindOfClass:[NSError class]]) {
NSLog(#"ERROR: %#", error);
}
else {
// success!
self.myData = result;
}
}];
The "continuation" and error handling makes this a bit confusing (and Objective-C syntax doesn't really add for more readability).
Another example with a third party library support:
The same logic can be written as this:
-(Promise*) asyncTaskA;
-(Promise*) asyncTaskBWithInput;
-(Promise*) asyncTaskCWithInput;
-(Promise*) asyncSomething;
- (Promise*) asyncSomething
{
return [self asyncTaskA]
.then(id^(id result) {
return [self asyncTaskBWithInput:result];
}, nil)
.then(id^(id result) {
return [self asyncTaskCWithInput:result];
}, nil);
}
And it is used as follows:
[self asyncSomething]
.then(^(id result) {
self.myData = result;
return nil;
},
^id(NSError* error) {
NSLog(#"ERROR: %#", error);
return nil;
});
If you like the latter more, the "Promise" framework is available on GitHub: RXPromise - I'm the author ;)
I'm not sure if what I've done in the past is relevant to your problem, but what I've done is create a download class that has a delegate protocol with a single method: -(void)downloadFinished:(id) data.
Any class that needs to get asynchronous data, creates an instance of this download class, and sets itself as the delegate. I call downloadFinished: from both connection:didFailWithError: and connectionDidFinishLoading:. Then, in the implementation of that method in the delegate, I check whether the data's class is NSData or NSError, and evaluate that data however is appropriate for that class.
I have a NSString called fontType
and I am trying to have a custom setter for it:
- (void) setFontType:(NSString *) fType
{
if (self.fontType != fType){
[fontType release];
self.fontType = [fType retain];
//some more custom code
}
}
Is there any issue with this?
A few things that stand out for me:
do not use self. inside of custom accessors. access the variable directly
it's better use copy semantics for properties of a type that has a
mutable subtype
be careful with whatever is // some more custom code
My personal style preferences are like so:
-(void)setFontType:(NSString *)fontType_ {
if (fontType == fontType_) return; // get out quick, flatten the code
[fontType release];
fontType = [fontType_ copy];
// some more code
}
Cocoa with Love has a good article on this topic. It's worth a read.
When you do self.fontType = newFontType, you are doing [self setFontType:newFontType] (just with a different syntax), this means you are calling the method inside itself.
This is called recursion, but in this case, you don't have a base case in which the method will stop calling itself, so my guess is that this method will call itself until the app crashes. In short, recursion is not something you want here.
Just replace self.fontType = [fType retain] with fontType = [fType retain] (Assuming the var linked to the fontType property is called fontType as well).
PS. At the end of the question you asked
Is there any issue with this?
If you didn't try this, then you shouldn't even be asking that here on StackOverflow, and if you did try it, then you should have realized that this method didn't work, so that last line is pretty meaningless. Just saying.
I am trying to set up an object to control all of my data so it can set things up in the background to it appears my tableviews load faster than they do now etc.
This is what I am trying to achieve.
I am setting a variable in the NSObject from the secondVC when the tableviewcell is selected like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Access selected cells content (cell.textLabel.text)
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//Parent view logic (sends info back to the correct cell in parent view)
if (parentViewSelectedIndexPath.section == 0)
{
if (parentViewSelectedIndexPath.row == 0)
{
//Predicates restrict the values that will be returned from the query
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"MANUFACTURER",cell.textLabel.text];
NSArray *filterArray = [myDataArray filteredArrayUsingPredicate:predicate];
//[[self delegate] setManufactureSearchFields:filterArray withIndexPath:indexPath]; //This is where I pass the value back to the mainview
//Using Object
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
[vehicleControllerNSObject setFirstCell:filterArray];
}
//etc
At the end there you can see the method that is getting set up in the VechicleControllerNSObject which looks like this.
-(void)setFirstCell:(NSArray *)array{
manufactureSearchObjectStringFVC = [[array valueForKey:#"MANUFACTURER"] objectAtIndex:0];
NSLog(#"%#", manufactureSearchObjectStringFVC); // this prints the correct value to the console
}
As you can see this prints the correct output fine.
however I have no idea how to call manufactureSearchObjectStringFVC and pass the value it holds into the uitableviewcell that I would like to pass it in on my firstviewcontroller.
This is what I have for testing atm.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
manufactureSearchObjectString = vehicleControllerNSObject.manufactureSearchObjectStringFVC;
NSLog(#"%#", vehicleControllerNSObject.manufactureSearchObjectStringFVC);
}
That nslog prints null..
I have three questions
1, how do I get the correct value into the first valuecontroller.
2, should I be using viewDidAppear like this?.. I think not.. how can I do this better
3, Do you think this is a good way of doing this type of thing, as in the future i would like to use the NSObjectClass to parse info, cache etc all behind the senses leaving the views to just display when the data is ready hopefully helping performance..
Any help would be hugely appreciated as I really want to learn this stuff as i know its important for me to know.
Your question is so beautifully and clearly formatted and diagrammed that it seems a shame to ask you to do a search. But here it is:
Search for Sharing Data between View Controllers
You'll find many good discussions about sharing data between view controllers.
Briefly, though, I can tell you why your code isn't working. In your tableView:didSelectRowAtIndexPath: method, you are creating (alloc/init) a new instance of your VehicleControllerNSObject class each time. Then back in your first view controller on viewDidAppear:, again you are creating (alloc/init) a whole new instance each time.
So you have multiple objects coming and going and they have nothing to do with each other. It's a bit like giving some important information to one person at a bus station and then later randomly picking some other person out and trying to retrieve that same information from her.
So one quick idea would be to create just once instance of your VehicleControllerNSObject (just an aside, that's a bit of a strange name for a class since generally all objective-c objects are descendants of NSObject anyway. I'm just going to call that VehicleController for now)
So let's say you wanted a 'sharedInstance' of VehicleController. You could add a class method to VehicleController to give you a way to easily get that one sharedInstance:
+(VehicleController*)sharedInstance {
static VehicleController *sharedInstance_ = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance_ = [[VehicleController alloc] init];
});
return sharedInstance_;
}
So to get that instance in methods in other classes you can just do something like :
VehicleController *sharedController = [VehicleController sharedInstance];
sharedController.someProperty = someValue;
// and then back in your first view controller, similarly:
VehicleController *sharedController = [VehicleController sharedInstance];
id someValue = sharedController.someProperty;
Again, check the search, many people have had good discussions on this. This is just one approach. I hope it at least makes sense why your code wasn't working.
Hope that helps.
To answer question 3. No.
I think that the best way to do something like this would be to use Core Data and it's NSManagedObject.
A combination of UITableViewController and NSFetchedResultsController that is feed from a Core Data sqlite backing store, if well set would feed and keep your UITableView updated.
It would be to long to describe all in here. So I will stop there.
If you don't want to go with that there is always the possibility to use a shared pointers to a mutable object or to use a singleton object to communicate information between UIViewController.
Help, I'm deperate..i can't continue my app because of this:
I have setup one message between 2 of my cocoa objective-c clases,
the main appdelegate is messaging a view through NSNotificationCenter
the view receives indeed the notification but somehow it can't update the controls visible on the view.. it's like it can't find the proper instance or something.
here's my code:
mainapp (where the appdelegate messages my view):
helloKey = #"0";
helloString = #"mymessage";
values = [NSMutableDictionary dictionaryWithObject:helloString forKey:helloKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewMessageNotification" object:self userInfo:values];
the function is defined in myuiviewcontoler
- (void)NewMessageNotification:(NSNotification *)notification
{
// key associated with string "Hello String"
helloKey = #"0";
// [notificaiton userInfo] returns "values"
helloFromMain = [[notification userInfo] objectForKey:helloKey];
NSLog(#"newmess=%#",helloFromMain; //this outputs mymessage , perfect right?? not quite..here's the problem
//then if I'm trying to update something visible ..it won't do IT!!!! :( :(
//tested with the debugger and it reaches this line...when the messaging occurs
[self.mybutton setTitle:#"sdsdsdasd" forState:UIControlStateNormal];
//this won't work, not it will give an error!!!
}
So what could it be the problem? nothing that is visible can be modified programaticaly when using this, and I kid of need that..
If I do a IBAction and assign it to a button of this view and do the same :
[self.mybutton setTitle:#"sdsdsdasd" forState:UIControlStateNormal];
it will update my display without no problem... modifying the title...
So what's the difference between the function that is called by the NSnotificationcenter and a function that belongs to the same view...
I tried to see the address of the self variable in both cases,, it results to be the same...
so ..basically uses the same pointer right?
still it doesn't work...
I've put another post back a few days ago..but no-one answered
Programatically created labels and images don't show on screen
It sounds very much like your NewMessageNotification: method is not being called on the main thread; UI updates must only be done from the main thread, or things won't work correctly.
The usual idiom for a situation like this is something along these lines:
- (void)NewMessageNotification:(NSNotification *)notification
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(NewMessageNotification:) withObject:notification waitUntilDone:NO];
return;
}
// Existing code goes here
}
It sounds like you probably have two different instances of your class, one of which is hooked up to all the stuff in the nib and one of which is registering for the notification. Try logging self in that method and you'll probably see different object addresses when it's triggered by the notification versus the button.
I'm fairly new to Objective-C, and it would be really helpful if someone could help me with the following task:
I have a class TheController that has a method DoTask. The goal of DoTask is to reach out to a MasterUtility (also a custom made class) and get Data, and then send it back when it is done (it uses a thread). Specifically, I want it to send it to dataReceiver in ReportsViewController. I think I need to use #selector or something like that. Here is some code:
#implementation ReportsViewController
-(void)doTask {
MasterUtilities *mu = [[MasterUtilities alloc] init];
[mu getDataAndSendTo:[WHAT GOES HERE]]
}
-(void)dataReceiver:(NSArray *)data {
NSLog(#"data: %#",data);
}
#end
Here is MasterUtilities
#implementation MasterUtilities
- (void)getDataAndSendTo:[WHAT GOES HERE] {
NSArray *data = [[NSArray init] alloc];
....getting data here....
[WHAT GOES HERE? HOW DO I CALL THE METHOD (dataReceiver) IN ReportsViewController?]
}
#end
Can anyone fill in the areas that indicate "WHAT GOES HERE"? Thank you!!
You could use a block:
typedef void (^Callback)(NSArray*);
[somebody doSomethingAndPerform:^(NSArray *data) {
// do whatever you want with the data
}];
- (void) doSomethingAndPerform: (Callback) callback
{
NSArray *data = …;
callback(data);
}
This is very flexible, but maybe too complex. If you want something simpler, you can always just pass the selector and target, just as you thought:
[somebody doSomethingAndCall:#selector(dataReceiver:) on:self];
- (void) doSomethingAndCall: (SEL) selector on: (id) target
{
NSArray *data = …;
[target performSelector:selector withObject:data];
}
Or you can use a protocol:
#protocol DataConsumer
- (void) handleData: (NSArray*) data;
#end
// this class has to implement DataConsumer
[somebody doSomethingAndNotify:self];
- (void) doSomethingAndNotify: (id <DataConsumer>) consumer
{
NSArray *data = …;
[consumer handleData:data];
}
This solution is a bit heawyweight, but the advantage is that the compiler catches some errors for you. There’s also more coupling, but it’s far from being a problem.
You have to use the Target-Action design pattern, which is widely used in Cocoa.
Good luck!
You may wish to reconsider how you approach this problem.
Rather than trying to get your MasterUtilities instance to send the data to your other method, why not have your getData method return the data from the method and then have your ReportsViewController pass the data to dataReciever: ?