I have an NSTreeController (supplying content to an NSOutlineView). I'd like the top-level objects to be of one class, and all other objects (so, children at any level) to be of another. What's the best way to go about this?
I'll need to somehow change the behavior of at least add, addChild, insert, and insertChild, I suppose. I was hoping, though, to find a simple way to account for this in only one location, rather than changing four separate methods.
It seems to me that you could just create an attribute in your objects to differentiate which objects should use your modified methods and which don't. Then just put a simple if statement to test for that attribute in the subclassed methods. If your object doesn't have the attribute then let the super class tree controller handle it otherwise your changed behavior.
This worked, and I didn't have to rewrite any functionality:
- (void)insertChild:(id)sender
{
if ([self selectionIndexPath])
{
[self setObjectClass:[IRGroup class]];
[super insertChild:sender];
}
else
{
[self setObjectClass:[IRFloor class]];
[super insertChild:sender];
}
}
It wasn't easy; I tried overriding newObject, because Apple's docs claim it is called when inserting siblings and children, but my testing reveals it is only called when inserting siblings.
Related
I have been wondering why is NSProxy class so important. Why does an object need to keep its instance variables inside other objects? I need examples to understand when to use it. Thanks!
NSProxy is useful when there is a need for delegate interception, let's say you have some styled UISearchBar across your app, in which you remove the search icon when user starts typing, it means you need to listen UISearchBarDelegate method -searchBar:textDidChange: but this method is already listened by ViewController which performs searching, to avoid code duplications you don't want to copy-paste hiding icon logic in every your ViewController. To solve this problem you can create NSProxy which will have reference to your ViewController as originalDelegate and your hiding search icon helper as middleMan, then in your NSProxy instance you need implement following methods:
- (void)forwardInvocation:(NSInvocation *)invocation
{
if ([self.middleMan respondsToSelector:invocation.selector])
{
//Note: probably it's better to provide a copy invocation
[invocation invokeWithTarget:self.middleMan];
}
if ([self.originalDelegate respondsToSelector:invocation.selector])
{
[invocation invokeWithTarget:self.originalDelegate];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
id result = [self.originalDelegate methodSignatureForSelector:sel];
if (!result) {
result = [self.middleMan methodSignatureForSelector:sel];
}
return result;
}
And set your proxy instance to searchBar delegate: searchBar.delegate = proxy
Example A: Imagine you'd be writing an object persistence layer (like CoreData, but much better of course ;) ).
Let's say you can fulfill a query for thousands of items in your database really quick by just looking at the index-tree, without the cost of reading and initializing the complete item.
You could use NSProxy to implement lazy-loading. Use your index table to locate the primary key of the object, but instead of creating that object, return an NSProxy that knows the primary key of the real object.
Only when another database lookup is required, the proxy object creates the item and redirect all future messages to it. The calling code would only deal with the NSProxy item, and never now about the lazy-loading performed under the hood.
Example B (this is OS X, sorry): NSOutlineView behaves really odd, when you have the same item in the outline hierarchy twice. Very common problem when you have a smart group feature in your app. The solution: use different proxies in the outline view, pointing to the same object.
Let's say I have a two subclasses of UIViewController called MasterViewController and DetailViewController.
DetailViewController has a property of type NSNumber called level and a UILabel called levelLabel.
MasterViewController has a segue to DetailViewController called ToDetail. MasterViewController's prepareForSegue is like so
- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToDetail"]) {
DetailViewController *detailVC = (DetailViewController *)segue.destinationViewController;
detailVC.level = [NSNumber numberWithInt:10]; // never mind the literal...pretend there was some algorithm for it
}
}
So then, in DetailViewController we implement the setter for levelLabel like so:
- (void)setLevelLabel:(UILabel *)levelLabel
{
if (levelLabel) {
_levelLabel = levelLabel;
_levelLabel.text = level.stringValue;
}
}
Is this good code design? Also, could you critique my code writing style? I pretty much wrote all this code on the fly so this is pretty much how I write code for the most part.
I thought of this question while showering because this is how I implement the setting of almost all the label texts that depend on a segue.
What follows is my own way of thinking about such relationships. Italics applies to your question.
You have the thing being controlled (the label) the controller (destination view controller) and the context it is being controlled within (the source view controller). This can also be expressed as model-view-controller, but I think thinking about a context can apply to much more specific and localised situations.
You should generally try to keep information flow going in one direction, from the context downwards. Objects should not have to be aware of the context in which they exist, ie they shouldn't have to ask for any information, they should be told everything they need to operate. So the source view controller should push the level to the destination view controller, the destination view controller should push this information to the label. This is what you already have, sort-of.
To build upon the above, not only should information flow in one direction, but I also try to ensure the relationships are causal, ie pushing information from one object to another should cause it to subsequently be pushed to the next object. Your code is not doing this which is probably why you have a bad feeling about it.
A more appropriate thing to do is set the text property of the label within the level setter, so that when you set or change the level, the label will update subsequently. The label may or may not be loaded so you will have to check whether it is using -isViewLoaded; -viewDidLoad is the appropriate place to set the text property upon first load.
(When I say 'push' that's just my way of thinking about setting properties or passing arguments because it implies directionality. It is really dependency injection. An example of pulling information would be delegates and data sources. But note here still the object isn't aware of any context, delegates and data sources are clearly defined as protocols, not classes, and usually within the same header file, and are themselves pushed onto the object from a surrounding context. So yes the object is asking for information, but on its own terms and from a system it has no knowledge of.)
Re coding style:
That's exactly how I write code but note Apple reserves the use of underscore prefixes
I have a data model which has two entities in a relationship of one to many.
Each entity has a class that is subclassed from NSManagedObject.
I get the relation set for the one entity and walk it casting each set member to the specific subclass as I enumerate the collection.
When I do
NSLog(#"My Entity: %#", myEntityInstance);
It logs but does not call my subclass's method for:
- (NSString*) description
It does call if I send:
NSLog(#"My Entity: %#", [myEntityInstance description]);
Any ideas what is being called and why description has to be manually called?
Thanks!
If a class instance responds to descriptionWithLocale:, then NSLog will use that instead. Although descriptionWithLocale: does not appear in NSManagedObject's list of instance methods, it could possibly still be implemented.
Try overriding descriptionWithLocale: and see if that makes a difference.
- (NSString *) descriptionWithLocale:(id) locale
{
return #"my description";
}
I've never seen that. I don't think it's a NSManagedObject behavior. You might log the class before making the call to make sure your instance is of the class you think it is.
Might be two years or so late to the game, but for the benefit of others, I had this problem tonight. The cause was that, while I had made NSManagedObject subclasses for my entities, one of the entities in the CoreData Modler had it's "Class" set back to NSManagedObject instead of the custom subclass.
It didn't matter what I put into -description in my subclass files, because the objects were coming out of the Context as NSManagedObjects instead of my custom subclass.
Putting the subclass name back in the Entity Inspector in the Xcode Coredata Model Editor fixed it.
Can you refer to the sender of a message without passing the sender as a parameter?
This is simplified code for the sake of discussion:
// mainTableViewController.m
[dataModel loadData]; //Table is requesting data based on user input
// dataModel.m
-(void) loadData{
// I want to store the sender for later reference
sendingTableViewController = ???? ;
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
// Web data is loaded. Ask the sending tableViewController to
// reload it's data.
[sendingTableViewController.tableView reloadData];
}
I'm still getting used to how to refer to methods and properties that are the responsibility of another object. I want to send a message to dataModel to load some data using NSURLConnection. But I don't just want to return the data because I don't want to sit around waiting for the data to load. I want to send a message to the mainTableViewController once connectionDidFinishLoading is called.
Since the loadData method may be called from any number of tableViewControllers I can't just say [mainTableViewController reloadData].
Follow-Up Question
Great Information! I love the no-judgement nature of StackOverflow.
So the mainTableViewController would be the Delegate of the dataModel?
Would it be correct to say that the dataModel class defines the informal protocol?
I currently instantiate my dataModel class from within my mainTableViewController. So I could change my code like this:
// mainTableViewController.m
dataModel *myDataModel = [[dataModel alloc] initWithDelegate:self ];
// Does this method need to be defined in the mainTableViewController header file
// since I will already have defined it in the dataModel header file?
-(void) dataDidFinishLoading {
[self.tableView reloadData];
}
// dataModel.m
-(id) initWithDelegate:(id)aDelegate{
self.delegate = aDelegate;
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.delegate dataDidFinishLoading];
}
Is it bad that my TableViewController is instantiating my dataModel, cause then my dataModel is owned by the TableViewController? Should I really instantiate the dataModel from the AppDelegate instead?
Thank You!
I daresay this isn't the correct way to think about the problem. Architecturally, by giving the data model knowledge of the table view controller, you are coupling your model layer to your controller layer. Violating separation of concerns is a bad thing.
In Cocoa, the use of delegate objects is used all over the place. A delegate object is an object that implements a particular protocol with callback methods that can be called when things or events (such as data loading from a remote location, in your case) occur. I recommend that you create a delegate property in your data model, have an interface that mainTableViewController (or any other class, really) implements, and assign that class as the delegate. Then, when the data is finished loading, call the appropriate method on self.delegate. In that callback method, you could then call [tableView reloadData].
Again, you do not want your data model to be coupled (meaning aware of) the existence of your controller classes.
Edit
I just re-read the last part of your question, about having multiple table controllers needing to listen for notification of the data being finished loading. For that, I suggest you use the Observer pattern in Cocoa by using NSNotificationCenter. You use use the notification center in the data model to send notifications to observers (you don't care who is observing; the notification center handles those details) and you'd also use it in your table controllers to subscribe to the notification. Delegates are a nice, simple solution if you only need one object to be directly called when something happens. Notifications are more complex and have more overhead, but give you the flexibility to have an arbitrary number of objects "listening" for a notification to be posted.
Follow-Up Response
A class doesn't define an informal protocol; the developer does. You could also define a formal protocol in a separate .h file and have the controller implement it if you want an enforceable contract. With a formal protocol, you can also use #optional on methods that don't have to be implemented by a class conforming to the protocol.
It is also not at all bad to instantiate the data model from within the table view controller. In fact, this is one very correct way to do it. Since the data model exists to encapsulate data that (presumably) a controller will want to display later, you can think of the controller as owning the data model. You may even consider making an instance variable (and perhaps a property, too) to store your data model. Besides that, your rewritten code looks good to me!
Do you mean the keyword self?
-(void)canHazCheeseburger:(BOOL)canHaz
{
if (canHaz) {
self.cheeseBurger = [[[CheeseBurger alloc] init] autorelease];
[cheeseBurger onNomNom];
}
}
Is there a way to observe changes in derived properties? For example, I want to know when a CALayer has been added as a sublayer so that I can adjust its geometry relative to its (new) parent.
So, I have a subclassed CALayer, say CustomLayer, and I figured I could register an observer for the property in init:
[self addObserver:self forKeyPath:#"superlayer" options:0 context:nil]
and implement observeValueForKeyPath:ofObject:change:context. Nothing ever happens because, presumably, superlayer is a derived property (the attr dictionary stores an opaque ID for the parent). Similarly, I can't subclass setSuperlayer: because it is never called. In fact, as far as I can tell there are no instance methods called or public properties set on the sublayer when a parent does [self addSublayer:aCustomLayer].
Then I thought, OK, I'll subclass addSublayer like this:
- (void)addSublayer:(CALayer *)aLayer {
[aLayer willChangeValueForKey:#"superlayer"];
[super addSublayer:aLayer];
[aLayer didChangeValueForKey:#"superlayer"];
}
but still nothing! (Perhaps it's a clue that when I make a simple standalone test class and use the will[did]ChangeValueForKey: then it works.) This is maybe a more general Cocoa KVO question. What should I be doing? Thanks in advance!
Well, superlayer is defined as a readonly property, which means that there's no setSuperlayer: method. (If there is, it would be private, and you probably shouldn't use it.) If I had to make a guess, it would be that the superlayer property just isn't KVO-compliant. And, aside from that, I generally don't think it's a good idea for classes to observe themselves.
Maybe there's another way of doing this. When a layer is added to a superlayer, the onOrderIn action takes place. Now, actionForKey: is an instance method that gives a layer an opportunity to customize the default animations for certain properties. You could override actionForKey: to detect when the onOrderIn action takes place, do your thing, then call super's implementation.
I consider this a pretty messy hack, too, though. But it should be a bit more "self-contained" than having to use custom layers for everything and messing with KVO messages.
[self addObserver:self forKeyPath:#"superlayer" options:0 context:nil]
Don't observe yourself through KVO. Change your accessors instead.
Similarly, I can't subclass setSuperlayer: because it is never called.
I take it you tried this and added NSLog and found that it wasn't called?
Then I thought, OK, I'll subclass addSublayer like this:
- (void)addSublayer:(CALayer *)aLayer {
[aLayer willChangeValueForKey:#"superlayer"];
[super addSublayer:aLayer];
[aLayer didChangeValueForKey:#"superlayer"];
}
And the parent layer is also a CustomLayer, right? If the parent layer is a plain CALayer, anything you do in CustomLayer will have no effect.