Objective-C: Proper handling of shared properties between subclassed NSControl and NSActionCell? - objective-c

One of the things I've always been unsure of is how to properly handle communication between a custom NSControl subclass and an NSCell subclass. Throughout my introduction to Cocoa, I've seen it mentioned several times how the parent control provides many of the same methods, accessors, and mutators as the child cell's/cells' implementation. For example, the NSControl class and NSCell class both have -isEnabled and -setEnabled: in their header files:
NSControl.h
- (BOOL)isEnabled;
- (void)setEnabled:(BOOL)flag;
NSCell.h
- (BOOL)isEnabled;
- (void)setEnabled:(BOOL)flag;
I understand that the NSControl class provides "cover" methods for most of the properties found in NSCell. What I'm more interested in knowing is: how are they implemented? Or even better, how should one implement his/her own subclasses' shared properties? Obviously, only Apple's engineers really know what's happening on the inside of their frameworks, but I thought maybe somebody could shed some light on the best way to mimic Apple's cover method approach in a nice clean way.
I'm horrible at explaining stuff, so I'll provide an example of what I'm talking about. Say I've subclassed NSControl like so:
BSDToggleSwitch.h
#import "BSDToggleSwitchCell.h"
#interface BSDToggleSwitch : NSControl
#property (nonatomic, strong) BSDToggleSwitchCell *cell;
#property (nonatomic, assign) BOOL sharedProp;
#end
And I've subclassed NSActionCell:
BSDToggleSwitchCell.h
#import "BSDToggleSwitch.h"
#interface BSDToggleSwitchCell : NSActionCell
#property (nonatomic, weak) BSDToggleSwitch *controlView;
#property (nonatomic, assign) BOOL sharedProp;
#end
As you can see, they both share a property called sharedProp.
My question is this: What's the standard way to effectively keep the shared property synchronized between the control and the cell? This may seem like a subjective question, and I suppose it is, but I'd like to think that there is a most-of-the-time "best way" to do this.
I've used all sorts of different methods in the past, but I'd like to narrow down the ways in which I handle it, and only use the techniques which provide the best data integrity with the lowest overhead. Should I use bindings? What about implementing custom mutators which call their counterparts' matching method? KVO? Am I a lost cause?
Here are some of the things I've done in the past (some or all of which could be totally whacky or straight-up wrong):
Cocoa Bindings — I'd just bind the control's property to the cell's property (or vice versa):
[self bind:#"sharedProp" toObject:self.cell withKeyPath:#"sharedProp" options:nil];
This seems like a pretty good approach, but which object would you bind to/from? I've read all of the KVO/KVC/Bindings documentation, but I've never really picked up on the importance of the binding's directionality when the property should be the same in either case. Is there a general rule?
Send Messages from Mutators — I'd send the cell a message from the control's mutator:
- (void)setSharedProp:(BOOL)sharedProp
{
if ( sharedProp == _sharedProp )
return;
_sharedProp = sharedProp;
[self.cell setSharedProp:sharedProp];
}
Then I'd do the same thing in the cell's implementation:
- (void)setSharedProp:(BOOL)sharedProp
{
if ( sharedProp == _sharedProp )
return;
_sharedProp = sharedProp;
[self.controlView setSharedProp:sharedProp];
}
This also seems fairly reasonable, but it also seems more prone to errors. If one object sends a message to the other without a value check, an infinite loop could happen pretty easily, right? In the examples above, I added checks for this reason, but I'm sure there's a better way.
Observe and Report — I would observe property changes in each object's implementation:
static void * const BSDPropertySyncContext = #"BSDPropertySyncContext";
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ( context == BSDPropertySyncContext && [keyPath isEqualToString:#"sharedProp"] ) {
BOOL newValue = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
if ( newValue != self.sharedProp ) {
[self setSharedProp:newValue];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Once again, this seems doable, but I don't like having to write an if statement for every single shared property. Along the lines of observation, I've also sent notifications, but since the control-cell relationship is about as "one-to-one" as one can get (sweet pun intended), that just seems silly.
Again, I know this is a bit subjective, but I'd really appreciate some guidance. I've been learning Cocoa/Objective-C for awhile now, and this has bothered me since the beginning. Knowing how others handle property syncing between controls and cells could really help me out!
Thanks!

First, note that NSCell mostly exists because of performance issues from the NeXT days. There has been a slow migration away from NSCell, and you generally shouldn't create new ones unless you need to interact with something that demands them (such as working with an NSMatrix, or if you're doing something that looks a lot like an NSMatrix). Note the changes to NSTableView since 10.7 to downplay cells, and how NSCollectionViewItem is a full view controller. We now have the processing power to just use views most of the time without needing NSCell.
That said, typically the control just forwards messages to the cell. For instance:
- (BOOL)sharedProp {
return self.cell.sharedProp;
}
- (void)setSharedProp:(BOOL)sharedProp {
[self.cell setSharedProp:sharedProp];
}
If KVO is a concern, you can still hook it up with keyPathsForValuesAffectingValueForKey: (and its relatives). For instance, you can do something like:
- (NSSet *)keyPathsForValuesAffectingSharedProp {
return [NSSet setWithObject:#"cell.sharedProp"];
}
That should let you observe sharedProp and be transparently forwarded changes to cell.sharedProp.

Related

Dirty flags on Realm objects

Can anyone suggest a good pattern for implementing a dirty flag on Realm objects? Specifically, I would like every subclass of Realm Object to expose an isDirty flag that gets set whenever an instance of the class is modified and is cleared whenever the instance is written to the cloud (not the Realm). I'm working in Objective-C.
Possible solutions I can think of include the following:
Write a custom setter for every property of every objects. Set isDirty within each of those setters. Not very desirable.
Use KVO in some way. Two problems with this approach: (a) I don't fully understand how to implement this approach, and (b) Realm doesn't support KVO for managed objects (which are exactly the objects I need it for).
Use Realm notifications. Again, I don't have experience with these, and I'm not sure how to use them for this purpose.
Short of simply having a non-managed isDirty property that you manually set after performing each write transaction, KVO would be the best way to go.
Setting custom setters would indeed be incredibly messy. You'd have to have a separate one for each property you wanted to track.
Realm notifications would only work if you were tracking a set of objects and wanted to be alerted if any were changed (using collection notifications) or if anything in the Realm changed.
With KVO, you could potentially get your object subclass itself to add observers to all of its properties, which are then channeled to one method whenever any of them change, this could then be used to mark the isDirty property.
#interface MyObject: RLMObject
#property NSString *name;
#property NSInteger age;
#property BOOL isDirty;
- (void)startObserving;
- (void)stopObserving;
#end
#implementation MyObject
- (void)startObserving
{
NSArray *properties = self.objectSchema.properties;
for (RLMProperty *property in properties) {
[self addObserver:self forKeyPath:property.name options:NSKeyValueObservingOptionNew context:nil];
}
}
- (void)stopObserving
{
NSArray *properties = self.objectSchema.properties;
for (RLMProperty *property in properties) {
[self removeObserver:self forKeyPath:property.name];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
self.isDirty = YES;
}
+ (NSArray *)ignoredProperties {
return #[#"isDirty"];
}
#end
Obviously you'd want to do more checking in here than I've done (to make sure isDirty truly needs to be set), but this should give you an idea.
There's no real way to automatically know when a managed Realm object has been created, so it would be best for you to manually start and stop observing as you need it.

Can objects manage their own destruction (using ARC)?

I have a controlling parent class that needs to perform several actions, these actions typically have a very short lifespan. I'm using a Factory class to create these objects and have some questions about how to best manage their demise and if more than one can safely co-exist. Here some example pseudo code:
#interface parent
#property (strong, nonatomic) abstractChild* action;
- (abstractChild*) makeConcreteChildOfType:(Type)type;
- (void) performActionA;
- (void) performActionB;
- (void) performActionC;
...
- (void) performActionA;
{
self.action = [self makeConcreteChildOfType:A];
[self.action doYourThing];
}
- (void) performActionB;
{
self.action = [self makeConcreteChildOfType:B];
[self.action doYourThing];
}
I have quite a lot of different types of actions and each child action class knows what it has to do and when its finished doing it.
I could create separate properties for each different type of concrete action instead of having one abstract property, and have each child object inform the parent when its finished via a delegate, however that's starting to get a little messy especially when the fact that multiple actions of the same type could potentially be active at once (which would require arrays of pointers to objects of each type). Therefore I'm wondering if/how the child objects can manage themselves, specifically their own destruction.
If each object looked something like this:
#implementation ChildObjectA
-(void) doYourThing
{
[self retain];
do something
}
- (void) OnCompletion
{
[self release];
}
Then is this safe to do? - could the parent class create multiple objects of the same type and they all live for as long as each is needed and effectively are deleting themselves when done and they can exist in parallel and delete themselves when done without issue?
There is presumably no need for the action property of the parent class, but if it was kept then I'd like to confirm there's no problems with memory management that arise? I presume not as each time self.action is assigned to ARC will release the associated child object but it won't be deleted until the child object releases itself?
It's certainly possible for an object to own itself, but the cases where it's truly necessary are few and far between. I don't think this is one of them.
I think the solution that you've called "too messy" is in fact the best one. It's also, to my mind, quite simple and clean. The factory keeps a collection of action objects.* When an action object is done, it informs the factory, which cleans it up. This is similar to the activity of an NSOperationQueue -- the queue takes ownership of the operations.
#implementation Factory
{
NSMutableArray * listOfAllActions;
}
- (void) performActionA;
{
Action * newAction = [self makeConcreteChildOfType:A];
[listOfAllActions addObject:newAction];
[newAction setOwner:self];
[newAction doYourThing];
}
// And similarly for other "performAction..." methods
Each action, then, sends a simple "all done!" message to its owner when it completes:
#interface AbstractChild
#property (weak) Factory * owner;
//...
#implementation AbstractChild
#synthesize owner;
- (void)cleanupAfterCompletion
{
// Subclasses can override this to do more cleanup
// Message the action's owner, passing self so it knows which action
[[self owner] actionHasCompleted:self];
}
Then, back in Factory,
- (void)actionHasCompleted: (AbstractAction *)action
{
// Other cleanup if necessary
// Delete action
[listOfAllActions removeObjectIdenticalTo:action];
}
If for some reason you dislike the action having a direct pointer back to the factory, you could use notifications, but those are generally for times when the two objects have a much weaker relationship than this.
*That's the reality of the situation -- you are creating multiple objects. In fact, to be frank, your idea of having one property for potentially multiple objects seems messy to me.
Here is a article that might be a solution to what you are trying to do... It is basically saying to use the weak qualifier which automatically sets the object to nil if its reference zeroes...
http://thinkvitamin.com/code/ios/ios-5-automatic-reference-counting-arc/ (Credit goes to author of this article)

NSArrayController adding categories

I'm trying to add new categories to the NSArrayController class: it can select the first and the last item. I did so:
#import "NSArrayController+selectEnds.h"
#implementation NSArrayController (selectEnds)
- (void)selectFirst:(id)sender {
if (self.arrangedObjects !=nil){ BOOL ignore = [self setSelectionIndex:0];}
}
- (void)selectLast:(id)sender {
if (self.arrangedObjects !=nil){
NSUInteger lastItem = [self.arrangedObjects count]-1;
BOOL ignore = [self setSelectionIndex:lastItem];}
}
#end
I get no errors, but I would like to put this object in IB, using a blue cube and binding buttons to its "selectFirst" and "selectLast" methods.
But I'm a bit lost: which standard object to start with? A standard ArrayController? And then, which class name to choose to have the new methods listed?
Thanks for your help…
Since you didn't show NSArrayController+selectEnds.h (which is what IB actually looks at), just NSArrayController+selectEnds.m, it's hard to know exactly what you got wrong, but there's two plausible guesses.
First, if you want these new methods to be part of the interface of the class NSArrayController, you have to add them to the interface declaration, not just to the implementation.
Second, if you want Xcode (or IB) to know that these new methods are actions, you have to label them as such: in the interface, instead of marking them plain void methods, mark them IBAction methods. (In the implementation, you can do either; it doesn't matter.)
So, NSArrayController+selectEnds.h should be:
#import <Cocoa/Cocoa.h>
#interface NSArrayController (selectEnds)
- (IBAction)selectFirst:(id)sender;
- (IBAction)selectLast:(id)sender;
#end

Test type of NSNotification

I need to check whether an object is an NSNotification. It is not enough to know if it is a subclass, as I want to differentiate between whether it is an NSNotification or a subclass of NSNotification.
So to elaborate I need to differentiate between the following:
An NSConcreteNotification
A Subclass of NSNotification (But not NSConcreteNotification)
The problem is that NSNotifications are actually NSConcreteNotifications and NSConcreteNotification is a private class so I can't use it to test against.
[object isMemberOfClass: [NSNotification class]] // returns NO in both cases
[object isKindOfClass: [NSNotification class]] // returns YES in both cases
There is no reason to subclass NSNotification the way you're describing. First, NSNotification already carries a userInfo dictionary. You can put any data you want in there. You can use category methods to read and write into that dictionary if you like (I do this all the time). For example, a very common thing I want to do is pass along some object, say the RNMessage. So I create a category that looks like this:
#interface NSNotificationCenter (RNMessage)
- (void)postNotificationName:(NSString *)aName object:(id)anObject message:(RNMessage *)message;
#end
#interface NSNotification (RNMessage)
- (RNMessage *)message;
#end
static NSString * const RNMessageKey = #"message";
#implementation NSNotificationCenter (RNMessage)
- (void)postNotificationName:(NSString *)aName object:(id)anObject message:(RNMessage *)message {
[self postNotificationName:aName object:anObject userInfo:[NSDictionary dictionaryWithObject:message forKey:RNMessageKey];
}
#end
#implementation NSNotification (RNMessage)
- (RNMessage *)message {
return [[self userInfo] objectForKey:RNMessageKey];
}
As #hypercrypt notes, you can also use associated references to attach data to any arbitrary object without creating an ivar, but with NSNotification it's much simpler to use the userInfo dictionary. It's much easier to print notification using NSLog. Easier to serialize them. Easier to copy them. Etc. Associated references are great, but they do add lots of little corner cases that you should avoid if you can get away with it.
That sounds like a really bad idea. When you first receive the notification, you already know what type it is, because it's passed as an explicit argument to a notification callback method. Consider storing the notification as a strongly typed property of another object, or inserting in a dictionary under an appropriate key if you're adding it to a collection, or passing it to other methods that don't preserve the type information to make it easier to identify later.
Creating dependencies on private API (including the names of private classes) will make your code more fragile, and much more likely to break in a future release. Obviously, one of the reasons these classes are private is to make it easier for Apple's engineers to change them as they see fit. For example, the concrete subclasses used by NSArray and NSMutableArray just changed in a recent release of the SDK.
To test id object is an NSNotification use:
[object isMemberOfClass:[NSNotification class]];`
To test if it is a NSConcreteNotifications use
[object isMemberOfClass:NSClassFromString(#"NSConcreteNotifications")];
Change the string to the name of a different class as needed...
You can then combine the two check for 'A Subclass of NSNotification (But not NSConcreteNotification'.
Either:
if ([object isMemberOfClass:NSClassFromString(#"NSConcreteNotifications")])
{
// It's a NSConcreteNotifications...
}
else if ([object isKindOfClass:[NSNotification class]])
{
// It's an NSNotification (or subclass) but not an NSConcreteNotifications
}
Or
if ([object isKindOfClass:[NSNotification class]] && ![object isMemberOfClass:NSClassFromString(#"NSConcreteNotifications")])
{ /* ... */ }
If you want to add properties to NSNotifications you should look into Associative References.
The basic idea is:
static const char objectKey;
- (id)object
{
return objc_getAssociatedObject(self, &objectKey);
}
- (void)setObject:(id)object
{
objc_setAssociatedObject(self, &objectKey, object, OBJC_ASSOCIATION_RETAIN);
}
As others have pointed out, it is a bad idea to rely on the name of a private class. If you are looking for one specific subclass, you could just explicitly check for that class.
[notification isMemberOfClass:[MyNotificationSubclass class]];
You could use multiple statements to check for multiple subclasses, but that would be a little cluttered. This method also requires changes every time you add a new class to look for. It may be better to define a readonly property which indicates whether a notification supports the feature you are looking for, so you aren't relying on the class so much as the ability of the class. You could use a category on NSNotification which simply returns NO for this property, and any subclasses which have the feature would override the method to return YES.
#interface NSNotification (MyFeature)
#property (readonly) BOOL hasMyFeature;
#end
#implementation NSNotification (MyFeature)
- (BOOL)hasMyFeature {
return NO;
}
#end
In the subclasses which support it:
- (BOOL)hasMyFeature {
return YES;
}
- (void)performMyFeature {
...
}
This would also allow you to change whether or not a notification has the feature enabled by changing a flag which is returned for hasMyFeature, and your checking code would simply be:
if(notification.hasMyFeature) [notification performMyFeature];

Objective C, iOS, how do I attach a couple of int variables to UIImageView which is part of an IBOutletCollection?

I need to keep track of some image views which can be dragged around the screen and located within other images views. For example footballs in goals. I'd like to do this by having some extra properties attached to the footballs, current goal and times seen in the goal.
#property (nonatomic, retain) IBOutlet IBOutletCollection(UIImageView)
NSArray *multipleFootballs;
I figure, that I have to create a super class.
Although I'm not sure the best way to do this ?
EDIT3 : Thanks nick, but how do I then access a instance property ?
#interface FootballImageView : UIImageView {
int intCurrentGoal;
}
#property (readwrite) int intCurrentGoal;
#implementation FootballImageView
#synthesize intCurrentGoal;
-(id)init {
self = [super init];
if(self) {
// do your initialization here...
}
return self;
}
#end
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (id football in multipleFootballs) {
//if ([[touches anyObject] intCurrentGoal] == 0) {
//if (football.intCurrentGoal == 0) {
Should be easy:
Create a new subclass inheriting from UIImageView, let's call it MyImageView
Add your custom instance variables you need to the header
Choose as class for the old UIImageViews the new MyImageView in interface builder (Identity Tab)
Change IBOutletCollection(UIImageView) to IBOutletCollection(MyImageView)
-
- (id)init
{
self = [super init];
if(self) {
// do your initialization here...
}
return self;
}
Reply for edit 3
The problem you are facing is the anonymous type (id) you are using in touchesBegan. Put in a check like this:
for (FootballImageView *football in multipleFootballs) {
if(football.intCurrentGoal == 0) {
football.intCurrentGoal++;
}
}
The usual (and good) advice is that you should avoid keeping state in view objects. Attributes like times seen in goal and current goal should be part of your data model, not attached to your views. There are lots of good reasons for this, but they mostly boil down to two big points:
Mixing state with views will make things unnecessarily complicated.
Cocoa Touch is designed with an expectation that apps will adopt the MVC paradigm.
As an example of the first point, consider what happens when the user switches away from the main game screen to change settings, answer a phone call, etc. As I see it, you have three choices: a) preserve all the views even though they're not being displayed that you can maintain the app's state; b) save the state from the views before releasing the views; c) prevent the user from switching away from the game. Neither (a) nor (c) seem like good ideas, and if you take the MVC approach you get (b) for free.
Instead, create a data model that can represent all the aspects of your game. You can use standard classes like NSDictionary and NSSet, or classes of your own design like Football and Pitch, or (most likely) a combination. Then create a game controller which observes changes to the relevant model objects and makes changes in the corresponding views.