SORRY FOR THE LENGTH OF THIS POST; IT IS MEANT TO DOCUMENT MY JOURNEY WITH THIS PROBLEM.
I have a question about a shared object in a Cocoa app that needs to change from time to time and how best to store it so that it's accessible from a few different places. Bear with me.
Class Implementation
The shared object is implemented as a Class Cluster (i.e., https://stackoverflow.com/a/2459385/327179) that looks like the following (note that Document
is merely a class name; it is not necessarily indicative of what my actual class does):
In Document.h
:
typedef enum {
DocumentTypeA,
DocumentTypeB
} DocumentType;
@interface Document : NSObject {}
- (Document *) initWithDocumentType:(NSUInteger)documentType;
- (void) methodA;
- (void) methodB;
@end
In Document.m
:
@interface DocumentA : Document
- (void) methodA;
- (void) methodB;
@end
@interface DocumentB : Document
- (void) methodA;
- (void) methodB;
@end
@implementation Document
- (Document *)initWithDocumentType:(NSUInteger)documentType;
{
id instance = nil;
switch (documentType) {
case DocumentTypeA:
instance = [[DocumentA alloc] init];
break;
case DocumentTypeB:
instance = [[DocumentB alloc] init];
break;
default:
break;
}
return instance;
}
- (void) methodA
{
return nil;
}
- (void) methodB
{
return nil;
}
@end
@implementation DocumentA
- (void) methodA
{
// ...
}
- (void) methodB
{
// ...
}
@end
@implementation DocumentB
- (void) methodA
{
// ...
}
- (void) methodB
{
// ...
}
@end
How The User Interacts with a Document
Via a menu item, the user can switch between DocumentA and DocumentB at will.
What Happens When A "Switch" Occurs
When the user switches from, say, DocumentA
to DocumentB
, I need two things to happen:
- My primary
NSViewController
(MainViewController
) needs to be able to use the new object. - My
AppDelegate
needs to update anNSTextField
that happens to be located in the content border of the main window. (FWIW, I can only seem to assign an outlet for theNSTextField
in theAppDelegate
)
The Question(s)
I've seen singletons mentioned quite a bit as a way to have a global reference without cluttering up one's AppDelegate
(primarily here and here). That said, I've not seen much info on overwriting such a singleton (in our case, when a user switches from DocumentA
to DocumentB
[or vice versa], this global reference would need to hold the new object). I'm not an expert on design patterns, but I do remember hearing that singletons are not meant to be destroyed and recreated...
So, given all this, here are my questions:
- How would you store my Class Cluster (such that
MainViewController
andAppDelegate
can access it appropriately)? - Am I mixing concerns by having both
MainViewController
(who usesDocument
heavily) andAppDelegate
(who manages the primary window [and thus, myNSTextField
]) have knowledge ofDocument
?
Feel free to let me know if I'm thinking about this problem incorrectly; I want this implementation to be as orthogonal and correct as possible.
Thanks!
Status Update #1
Thanks to advice from @JackyBoy, here's the route I've taken:
Document
is the one that, upon "switching", "notifies"AppDelegate
andMainViewController
by passing them the newly created instance.- Both
AppDelegate
andMainViewController
can update theDocument
object via the Singleton instance as necessary.
Here are my new files (dumbed down so that y'all can see the crux of the matter):
In Document.h
:
#import <Foundation/Foundation.h>
@class AppDelegate;
@class MainViewController;
typedef enum {
DocumentTypeA,
DocumentTypeB
} DocumentType;
@interface Document : NSObject
@property (weak, nonatomic) MainViewController *mainViewControllerRef;
@property (weak, nonatomic) AppDelegate *appDelegateRef;
+ (Document *)sharedInstance;
- (id)initWithParser:(NSUInteger)parserType;
@end
In Document.m
:
#import "AppDelegate.h"
#import "Document.h"
#import "MainViewController.h"
@interface DocumentA : Document
// ...
@end
@interface DocumentB : Document
// ...
@end
@implementation Document
@synthesize appDelegateRef;
@synthesize mainViewControllerRef;
+ (Document *)sharedInstance
{
static XParser *globalInstance;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
// By default, I return a DocumentA object (for no particular reason).
globalInstance = [[self alloc] initWithDocumentType:DocumentA];
});
return globalInstance;
}
- (id)initWithDocumentType:(NSUInteger)documentType
{
Document *instance = nil;
switch (parserType) {
case DocumentTypeA:
instance = [[DocumentA alloc] init];
break;
case DocumentTypeB:
instance = [[DocumentB alloc] init];
break;
default:
break;
}
// QUESTION: Is this right? Do I have to store these references
// every time a new document type is initialized?
self.appDelegateRef = (AppDelegate *)[NSApp delegate];
self.mainViewControllerRef = self.appDelegateRef.mainViewController;
[self.appDelegateRef parserSwitchedWithParser:instance];
[self.mainViewControllerRef parserSwitchedWithParser:instance];
return instance;
}
@end
@implementation Xparser_NSXML
// ...
@end
@implementation DocumentA
// ...
@end
Should I be bothered by the fact that Document
has knowledge of the existence of AppDelegate
and MainViewController
? Additionally, should I be bothered by the fact that when the Document
object updates, it re-notifies both AppDelegate
and MainViewController
(even though one of those initiated the update)?
As always, I appreciate everyone's eyeballs on this as my quest for the ideal implementation continues. :)
Status Update #2
A comment from @Caleb helped me understand that an NSNotification
-based setup would be a lot less unwieldy for this particular problem.
Thanks, all!