Right now I have a view controller that handles a lot of network requests. They are each a subclass of a NetworkRequest class and this view controller is the delegate of all of them. It implements one callback function, networkRequestDidFinish.
The problem is that all these network requests are separate objects, and they will all call that same function. What is the proper way to design this? Right now I go through a bunch of if statements in networkRequestDidFinish to see what kind of network request returned. It feels wrong though, but I am not sure what is conventional to do in this case.
Thanks.
One useful pattern here is to be sure that the delegate methods pass self to the view controller. It sounds like you might already be doing this - if you're using a series of if statements, you probably have a pointer to the relevant NetworkRequest. If you aren't, or are not sure, read on.
You see this pattern pretty much wherever delegation is used. As an arbitrary example, take the UITableViewDelegate protocol. The first argument of each of the delegate methods is a UITableView. For example:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
When a UITableView instance calls this delegate method, it passes self as that first argument. It does something like:
[self.delegate tableView:self heightForRowAtIndexPath:0];
Then, the delegate knows which UITableView it's dealing with, because it has a pointer dropped in its lap, as the argument tableView.
In your case, I would start by adding a parameter to the delegate method networkRequestDidFinish, changing its signature to:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest
That way you can tell which instance of NetworkRequest has called the delegate method.
Already had that, or that's not good enough? Well, the next thing I'd say would be to consider whether you really need to perform different actions based on the actual class of the NetworkRequest instance that's calling the delegate method. If you're just passing along the data, the answer is probably no. For example:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest {
[self processData:networkRequest.data];
}
That method doesn't care what class networkRequest really is. But you seem to care, since you're doing "a bunch of if statements." Then I would say that it might be a mistake to have them all hitting one delegate method. Instead, you might want to get rid of a delegate on NetworkRequest, and instead add a protocol to each of the subclasses of that class, specific to the subclass.
What?
Let's look at an example.
Imagine that one of the subclasses of NetworkRequest is FooNetworkRequest which, of course, requests foos. Its header might look like this:
// stuff...
#protocol FooNetworkRequestDelegate
- (void)fooNetworkRequestDidFinish:(FooNetworkRequest *)fooNetworkRequest;
#end
#interface FooNetworkRequest : NetworkRequest
#property (weak, nonatomic) id<FooNetworkRequestDelegate> delegate;
// stuff...
#end
You apply a similar treatment to all the other subclasses of NetworkRequest. Then, your view controller would adopt each of these protocols, and have a separate method for each subclass of NetworkRequest.
That still seems kind of dirty, right? It does to me. Maybe this is a hint that your view controller is trying to handle too many things at once. You should consider trying to spread out the responsibility for all these NetworkRequest subclasses to multiple view controller or model classes.
If that's not an option, you can at least make your view controller's source a little easier to read by using one or more categories. Put your view controller's main behavior in its .m file, as usual, and then create a category on that view controller that adopts the proper protocol(s) and handles the requests.
There are generally 2 nice procedures.
You can use block instead of the delegate. That means you can send a block to your request class either when instancing it or when you make the request.
Use a target/selector pair system to make it look kind of like adding a target to an UIButton. NSInvocation should do the trick.
Related
As the title implies, I'm asking if it would be okay to link a single delegate to two different UIViewControllers in my project. I'm trying to link to two different VCs in my project but it's making the first VC's act weird so I'm wondering if I'm doing it wrong?
Sorry if this is a noob question, still new to this.
Nothing wrong with this at all.
A delegate protocol is just a protocol. An object can conform to multiple protocols at the same time (e.g. UITableViewDelegate and UITableViewDatasource).
You might have two view controllers like MyPersonViewController and MyAnimalViewController and they will have delegate protocols like MyPersonViewControllerDelegate and MyAnimalViewControllerDelegate.
You can then just do...
self.personViewController.delegate = self;
self.animalViewController.delegate = self;
The only thing to make sure of is that when you get the call backs from each VC that you don't confuse them. The best way to do this is to use the same pattern as UITableViewDatasource and prefix the methods like...
- (void)personViewController:(MyPersonViewController *)controller gotSomeResults:(NSArray *)results
Or something. Anyway, then you have a completely different set of delegate methods for each controller.
Subclass of UIView
I have a subclass MyView of UIView.
This subclass has a #property UIView * realView.
What I want to do
Whenever a message is sent to MyView, I want to "forward it" to self.realView, excepted for few messages.
For instance, in the implementation of MyView, I would have this override:
- (void)setBackgroundColor:(UIColor *)color
{
[self.realView setBackgroundColor:color] ;
}
Instead of overriding explicitly all the methods, can I do it automatically, at the runtime?
Exceptions
For some methods, I want to have an explicit control. For instance:
- (void)setFrame:(CGRect)frame
{
/* do stuff */
[super setFrame:frame] ;
}
Instead of overriding explicitly all the methods, can I do it automatically, at the runtime?
You implement the -forwardInvocation: method to send any unrecognized messages to the other object. -forwardInvocation is called whenever an object doesn't implement the selector that's passed to it as a sort of second chance to handle a message. You can override it to send the message to another object (which is pretty much what NSProxy does), log the messages, etc.
As #cobbal points out below, -forwardInvocation will help you with methods not implemented in your superview, but it won't handle methods that are implemented int the superview because your MyView inherits implementations of those. For example, if you want to use a UIView as a proxy for a UIButton, all the methods specific to UIButton can be handled by -forwardInvocation:, but those defined by UIView cannot. In order to get a behavior other than the inherited method, you will of course need to override. In some situations you can get around that by deriving MyView from NSObject or UIResponder instead of from UIView, thus avoiding the inherited UIView implementations, but if MyView needs to be a real view you're stuck with overriding each method.
If you think about it, it's hard to imagine how your goal could be met without explicitly overriding each inherited method. You say that you only want to forward most messages, but how can the poor runtime tell which ones you do want to forward and which ones you don't? All it can do is look for a method for the given selector and call it if it finds one, or take some action (like calling -forwardInvocation:) if it doesn't.
Update: #robmayoff points out -forwardingTargetForSelector:, which didn't occur to me but is probably a better solution in your case. It still doesn't handle the situation where you need to redirect methods that you inherit from a superclass, though.
It's entirely possible.
First you need WZProtocolIntercepter. Then use the intercepter as the normal UIView:
WZProtocolInterceptor* fakeView = [[WZProtocolInterceptor alloc]
initWithInterceptedProtocol:#protocol(TheMethodsForTheMiddleManToHandle)];
fakeView.receiver = self.realView;
fakeView.middleMan = self;
[someViewController.view addSubview:fakeView];
Put the methods you want to control in TheMethodsForTheMiddleManToHandle:
#protocol TheMethodsForTheMiddleManToHandle
- (void)setFrame:(CGRect)frame;
#end
I has many UITableViewController subclasses in my app.
Now i just needed to modify them all to add +1 row in all cases, and one simple equal row in all.
I do not want to modify all of them by hand, better way seem's to replace UITableViewDataSource method to modify values in way like:
+(void)load {
[[self class] jr_swizzleMethod:#selector(tableView:numberOfRowsInSection:) withMethod:#selector(swizzledTableView:numberOfRowsInSection:) error:nil];
}
- (NSInteger)swizzledTableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self swizzledTableView:tableView numberOfRowsInSection:section] + 1;
}
But it replaces superclass function, that does not called in subclasses, so this is not working. Is there method to do that what i want, without modifying all subclasses?
You'd need to swizzle every subclass specifically. You can find them by introspecting the class hierarchy at runtime with objc_getClassList, but I can't begin to describe how dangerous and fragile this approach is. You're trying to apply this to every tableview in the system, which you hope is just the tableviews you mean it to be (i.e. your tableviews). But what about tableviews that might be used by the system or from third-party libraries? You're modifying them, too. And when you try to understand the crash this causes, the stack trace will be unintelligible because of the swizzle.
In order for this to work, tableView:cellForRowAtIndexPath: also needs to correctly handle this extra row, so it's hard to see how every table view controller in the system is going to be implemented correctly without knowing about this +1.
Either subclass your table view controller (and have them call super), or use a separate object that all of them call to add the extra row if it's needed. This other object (or superclass) is also where you should handle the cell for this extra row.
I have little experience in swizzling. But I have two possible solutions to your problem.
First:
Create a subclass: YouBaseTableView: UITableView, and add a row in YouBaseTableView. And inherit all your table view classes from YouBaseTableView.
Second:
Create an extension for UITableView, and write your row in this extension.
I'm probably late for the train...
But for future reference, a solution for the problem would be to swizzle setDataSource of UITableView and replace it with an NSProxy instance.
Usually nobody overrides the setDelegate / setDataSource methods, and that would allow you to swizzle those and intercept all calls to these delegates and exchange the implementation.
Check this out for more info: https://developer.apple.com/documentation/foundation/nsproxy
You are going into two different areas that need full understanding to be used correctly and are highly dangerous: Performing code in the +load method, and using method swizzling. I would never dare doing anything in +load. +initialize is ok if you know what you are doing, but +load is something you mustn't even think of touching if you ask questions here.
Now ask yourself first: What is "self" in a class method, and what is "[self class]"? Do you think this has even a chance of working?
I'd also recommend that you google for "swizzle" and pick up some other code for method swizzling. It looks quite dubious to me. And writing it as a category instead of a plain C function feels just horrible.
I am going to have several uiimageview interactive sulasses that need a delegate. when each one of them is touched, the view controller must respond by changing something.
Is it good common practice to have one method , that is the delegate method get called by all of the uiimageview subclass instances?
The delegate will be the view controller.
Overall, is that good practice?
One method is better. This one will be a good option - (void)imageViewTouched:(UIImageView *)sender
You should follow DRY (Do not Repeat Yourself) principle and eliminate code duplcication in your app. So you should have separate methods if they have separate logic. You should have one method if the same type of logic executed for various senders.
Absolutely. Pass in the sender so you can make decisions based on who sent you the message (like tableView:cellForRowAtIndexPath:), and you have a good implementation of the delegation pattern.
I am learning to program the iphone and I wanted to do some drawing. I followed some example code and subclassed the viewcontroller and it worked fine. Now as I wanted to expand the program I came upon a design question that I could use a little help on.
I subclass myviewcontroller with mynewview. If I have any code in the myviewcontroller how do I call or reference it in mynewview and vice versa? I am not sure if I am asking this right but I am trying to understand the relationship between the class and subclass.
Objective-C objects benefit from inheritance. All classes are subclasses of NSObject, therefore you can call init on any object. If you created a custom class and gave it a method doSomethingAwesome, you are free to then implement doSomethingAwesome in any subclass of your custom class. However, declaring a method in a subclass does not add that method to the superclass. As an aside, I rarely find myself subclass sing my own custom classes. I believe that it is encouraged to maintain what is called a shallow object hierarchy. Usually I subclass the stock cocoa classes, customize to my needs and if I need custom methods in more than one subclass I will declare a category on the superclass rather than relying on inheritance to provide my custom behavior
The messaging system in Objective-C is dynamic. Every object includes a struct with information that the runtime use for introspection. Here the runtime will find a list of methods the object is able to respond. So, let's say you message an instance like this:
[mynewview someMethod];
The runtime will first check the object information to trying to find some method that will be able to respond the message. If nothing is found, then will query the super class, and so on. In fact, the runtime is much more complex, and will give any object more opportunities to respond (that's the dynamic part. For instance, mynewview might not have any method called someMethod and yet, might be able to satisfy the call, but that's something you might not want to worry right now).
From a child class you can call the superclass implementation of a given method with the keyboard super, so if mynewview is a subclass of myviewcontroller you can call myviewcontroller implementation from mynewview with:
[super someMethod];
If someMethod is both present in myviewcontroller and in mynewview, the runtime will automatically only call the child implementation, you have to call the parent implementation (if you have to) from the child implementation.