I have kind of an abstract class for my UIViewControllers (lets call it MyViewController) which overrides some basic methods like viewDidLoad or viewDidDisappear. In this methods some preparations are made, like setting up colors for the navigation bar, or preparing the bar buttons or something like that.
Now I want this basic behaviour for my UITableViewControllers also. So I made a new class that inherits UITableViewController (lets call it MyTableViewController) and copied 99% of the code from MyViewController.
In this image you see my current architecture. Listed are the overriden methods, in which other private methods are called. Again, MyViewController and MyTableViewController share 99% codebase (only difference is the name of the class and the super class).
For obvious reasons this is crap.
Is there an elegant solution to make MyTableViewController a subclass of both MyViewController and UITableViewController?
This is one suggestion, but I don't know how useful it is because I don't know your code.
You could implement a bunch of methods in a category for the UIViewcontroller class. For example:
#implementation UIViewController (myCategory)
- (void)setupColors;
...
#end
Since both MyViewcontroller and MyTableViewcontroller inherit from UIViewController, they would inherit your methods.
The only thing that you would copy in both implementation is the invocation of those functions, but the duplicate code would be much less.
- viewDidLoad...
{
[self setupColors];
}
Just be careful if you override methods, because you can't call [super ... ] on a category as you can in an inherited class
Shared implementation for your common methods could be done either with a category or with composition. Since a category can't be used to directly override existing interface (e.g. viewDidLoad:) you would need to put your private methods into the category and call them from your subclass overrides. Another option would be to put your shared implementation in a separate class (which might be a singleton) and compose an instance of that as a property of both MyViewController and MyTableViewController, e.g.:
#interface MyViewController : UIViewController
#property (nonatomic, strong) MyControllerStyler *controllerStyler;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
[controllerStyler viewDidLoad];
}
...etc...
The composed class's methods could reference the controller if needed, e.g.:
#interface MyControllerStyler : NSObject
- (void)viewDidLoadInController: (UIViewController *)controller;
...etc...
While a category seems perfectly fine for this example, if your extensions collectively represent a meaningful unit of your design (such as a collection of visual styling attributes) that might argue for a separate object (or objects) to better represent your intent.
Related
This is a common topic but, in my case there is one thing I don't understand that I can't find explained in the other asked questions.
Here is the gist of what I'm trying to do:
User clicks a button and something like this is called:
#implementation FirstClass
-(void)clickedButton
{
[SecondClass changeText];
}
And then in SecondClass is:
#implementation SecondClass
- (void)changeText {
[myLabel setText:#"text"];
}
So when the user clicks the button, the text property in myLabel in SecondClass changes to "text".
The only problem I have with this is calling [SecondClass changeText] on the existing instance of SecondClass. Since I'm not initializing the CCNodes programmatically (they are all automatically loaded upon running the app), I don't know where or how SecondClass is initialized. I'm using SpriteBuilder to build this project.
Any help would be appreciated. Thanks!
So, you have two instaces -- one with a button, and one with a label. I'm assuming they are both descendants of NSViewController or otherwise manage underlying views.
The problem is, you found no way to address second instance containing label from the method of first instance.
You need to define a property in first instance's class:
#property(weak) SecondClass *secondInstance;
And then in button clicked method:
-(void)clickedButton
{
[self.secondInstance changeText];
}
There is one issue left: who is responsible to set first instance's property that we defined? This depends on who did create both of them, probably just app delegate or enclosing controller, you know that better.
UPD: If both of the controllers are created by AppDelegate:
#import "FirstClass.h"
#import "SecondClass.h"
#interface AppDelegate ()
// case A - manual
#property(strong) FirstClass *firstInstance;
#property(strong) SecondClass *secondInstance;
// case B - declared in xib
//#property(weak) IBOutlet FirstClass *firstInstance;
//#property(weak) IBOutlet SecondClass *secondInstance;
#end
#implementation AppDelegate
...
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Create them
self.firstInstance = [[FirstClass alloc] init...];
self.secondInstance = [[SecondClass alloc] init...];
// Or maybe they are declared in MainMenu.xib, then you do not create them
// by hand, but must have outlets for both. See case B above.
// Connect them
self.firstInstance.secondInstance = self.secondInstance;
...
}
Note that class is not the same as an object (instance). Class is a named collection of methods, mostly for the instance. In Objective-C, class is not just a name, but an object too, so you can call a method on it (i.e. send an message to the class object). But here we always talk about objects (instances), so forget about classes – we hold objects via strong properties or weak outlets, depending on how they were created, and operate on objects, never on classes.
In objective C, the methods are either instance methods or class methods. As the name suggests, the instance methods require an instance of the class to work, whereas the class methods can be used with just the name of the class. What you need here is a class method. Just change the following line in your code:
#implementation SecondClass
- (id)changeText {
to
#implementation SecondClass
+ (id)changeText {
This will change the method from an instance method to a class method.
I would like to override awakeFromFetch and awakeFromInsert.
As I leave the auto-generated NSManagedObject subclasses unchanged and put my custom code in categories, my question is:
Where do I put awakeFromFetch and awakeFromInsert in order that these methods get called correctly?
If your managed object subclass files are generated by Xcode, then you can also put the methods in a category of the managed object subclass, so that the code is not overwritten when you re-generate the class files in Xcode.
MyEntity+Extensions.h
#import "MyEntity.h"
#interface MyEntity (Extensions)
#end
MyEntity+Extensions.m
#import "MyEntity+Extensions.h"
#implementation MyEntity (Extensions)
- (void)awakeFromFetch
{
}
- (void)awakeFromInsert
{
}
#end
You might want to also consider mogenerator. It's a command-line tool which generates two classes for each of your managed objects and ensures your custom code is never overwritten when your model changes.
According to NSManagedObject class reference you should put it in a subclass - calling super implementation is necessary:
Important: Subclasses must invoke super’s implementation before performing their own initialization.
You have to implement them in your subclasses. If the code is the same for all of your subclasses and you want to avoid copy-pasting them into each of them, I would suggest to write one subclass of NSManagedObject that implements them and then make your specific entity-classes subclasses of that class.
//MyManagedObject.h
#interface MyManagedObject : NSManagedObject
//...
#end
//MyManagedObject.m
#implementation
- (void)awakeFromFetch
{
//...
}
- (void)awakeFromInsert
{
//...
}
#end
//OneOfMyEntities.h
#interface OneOfMyEntities : MyManagedObject
//...
I feel making an extension is better option than subclass because in subclass you need to change the parent class again n again whenever you generate... i hope thats the better approach....
I have several subclasses of UIViewController that I want to use a single category to give them all a couple of methods. The thing is I only want my classes not the base UIViewController to have that categories methods.
Say I have:
PanelAViewController
PanelBViewController
...
That I want to implement and respond to a class:
PanelAnimations
-(void)animateIn;
-(void)animateOut;
I could use a protocol and insert the methods each time but they use the same methods and values, so wouldn't a category suit?
I am just not sure how to define the category for these custom classes.
Why you didn't consider subclassing, instead of using categories or protocols? Here here you can simply create an AbstractViewController class (which heritates from UIViewController) that defines the panel animations methods, and then derive your own concrete controllers (PanelAViewController, PanelBViewController, etc.) from the abstract one.
The abstract class will define the methods and eventually some stubs in the implementation (is up to you if you want that PanelA and PanelB should call super or not). This depends on the abstraction degree you want to give to the abstract class. See the code example below.
Sometimes it is not clear if it is better to use a protocol or subclass or delegate mechanism. Most of the times the boundary is not clear and the final decision is more dependent on the programmer preference than a "codified" architectural rule. Typically you use protocol when you want different objects to have a common behavior for certain tasks (e.g.: you have a complex set of entities and one of these entities should be used as a map annotation: in such case you must simply provide this specific entity the MKAnnotation protocol compatibility); delegate is mostly used when you want to extend a class without subclassing it or without given the final user the possibility to subclass it. In your case I think subclassing is the most appropriate choice as all classes are strictly part of the same class hierarchy, they share a common code (or common interface) and provide each a specialized implementation.
//
// AbstractViewController.h
//
#import
#interface AbstractViewController : UIViewController
-(void)doAnimate;
-(void)didAnimate;
#end
//
// AbstractViewController.m
//
#import "AbstractViewController.h"
#interface AbstractViewController ()
#end
#implementation AbstractViewController
-(void)doAnimate {
NSLog(#"Abstract do animate");
}
-(void)didAnimate {
NSLog(#"Abstract did animate");
}
//
// ConcreteViewController.h
//
#import "AbstractViewController.h"
#interface ConcreteViewController : AbstractViewController
#end
//
// ConcreteViewController.m
//
#import "ConcreteViewController.h"
#interface ConcreteViewController ()
#end
#implementation ConcreteViewController
-(void)doAnimate {
[super doAnimate];
NSLog(#"Subclass do animate");
}
Seems like your best bet would be to derive a custom class from UIViewController, call it say "MyUIViewControllerBase". Add your custom methods to that class, then derive the rest of your view controllers from it. No category needed and it solves your problem.
Suppose you have control over all your custom ViewControllers, the easiest way would be creating a subclass of NSViewController - like MyViewController - and subclass every other of your own ViewControllers from it. This way you would not even have to write a category for them. You could simply implement your wanted features within the MyViewController.
NSViewController
|
MyViewController -- implement your shared methods here
/ \
MyViewControllerA MyViewControllerB
#interface MySuperclass : NSObject {
}
#end
#interface MySuperclass (MyCategory)
- (void)myMethod;
#end
#interface MySubclass : MySuperclass {
}
#end
#interface MySubclass (MyOtherCategory)
- (void)myMethod;
#end
Is it defined which implementation of -myMethod will be called?
Kochan states in Programming in Objective-C that:
If more than one category declares a method with the same name for the same class, it is not defined which method will be executed when invoked.
But I am not sure whether or not a category on a superclass is considered to be a category on the same class in this context.
Although I can't find a reference, I think it's clear that the implementation in MySubclass (MyOtherCategory) takes precedence. The category's methods are added to that particular class, so the "MyOtherCategory" implementation will belong to your subclass, and it will be looked up before going on to the superclass during message dispatch.
As pointed out in the Objective-C manual, a lot of methods in Cocoa are placed in categories. If the order of look-up wasn't well defined, you couldn't override any of those methods in categories on subclasses, which would make categories almost useless.
My gut says you'll be fine, but the only way to know is to try. I believe the uncertainty is only within the same class, not the super/subclass relationship.
I have a protocol.
MyProtocol.h:
#protocol MyProtocol
#property(nonatomic, retain) NSString* someString;
- (void)doesSomethingWithSomeString;
#end
And 2 classes that implement the same protocol. For some reason the 2 classes cannot inherit from the same base class. E.g. 1 of them might need to inherit from NSManagedObject (Core Data class in Apple's Cocoa framework) while the other shouldn't.
Class1.h:
#interface Class1: NSObject<MyProtocol> {
NSString* someString;
}
//Some method declarations
#end
Class1.m
#implementation Class1
#synthesize someString;
- (void)doesSomethingWithSomeString {
//don't use property here to focus on topic
return [[self someString] capitalizedString];
}
//Method definitions for methods declared in Class1
#end
Class2.h:
#interface Class2: SomeOtherClass<MyProtocol> {
NSString* someString;
}
//Some method declarations
#end
Class2.m
#implementation Class2
#synthesize someString;
// This is exactly the same as -doesSomethingWithSomeString in Class1.
- (void)doesSomethingWithSomeString {
//don't use property here to focus on topic
return [[self someString] capitalizedString];
}
//Method definitions for methods declared in Class2
#end
How can I avoid the duplication of -doesSomethingWithSomeString? (I guess I need something like categories for multiple classes).
Update:
There has been some suggestions of a helper class and delegating calls from Class1 and Class2 to it. It might be a good approach generally, especially if the methods are long.
In this case, I am looking at Class1 inheriting from NSObject and Class2 inheriting from NSManagedObject. The latter being a base class that Class2 has to subclass from, as a model/entity within the Apple Core Data framework.
So while delegation to a third class is one way to do this, there needs to be a lot of boilerplate delegation wrapper code for what amounts to many short 1-2 methods in the 3rd class. i.e. high boilerplate delegation code to actual code ration.
Another point is, as this is a model class, the common code mostly acts on ivars/properties, the delegation class will end up written almost like global C functions..
You can create a helper class an then use it from Class1 and Class2, and then only the call to the method on the helper class will be duplicated
This situation indicates that your Class1 and Class2 are not fully factored into classes that handle just one concern. The fact that you have a common implementation indicates that there should be a third class that provides that implementation and to which Class1 and Class2 can delegate that concern. In other words, this is a case for composition instead of inheritance.
Update
If it doesn't make sense to delegate to a class, don't forget that Objective-C is a superset of C. There's nothing stoping you from implementing a library of C functions that you can call from both classes to encapsulate the common behavior. If you're committed to conveniences like NSAssert et al., you can always implement them as class methods on a utility class or category on NSObject.
Personally I think this should be duplicated. You will likely need to customise one of them eventually and then you will be annoyed at all the work you did to prevent the duplication. Anything large can go into categories on the objects you are working with similar to whats going on inside capitalizedString.