2

I would like to prevent the user of one of my Objective-C objects from setting a property of a property of the object. Just like it is not possible to set the origin of a frame of UIView. I need this for the exact same reason: to be able to manage some side effects on the setter of the property.

Example:

@interface Time : NSObject
@property (nonatomic) NSInteger hours;
@property (nonatomic) NSInteger minutes;
@end

@interface Watch : NSObject
@property (nonatomic) Time *time;
@end

It now should not be possible to set the minutes directly by calling:

watch.time.minutes = 15;

…rather than:

Time *time = watch.time; // [watch.time copy]?
time.minutes = 15;
watch.time = time; // Here, in setTime:, I can implement some side effects

When trying to set a UIView frame's size, the compiler complains like so: "Expression is not assignable". How could Apple’s framework developers have done this? Can this only be done with structs? How can I achieve a similar thing?

jscs
  • 63,694
  • 13
  • 151
  • 195
Travis79
  • 23
  • 4
  • Yes, `frame` works this way only because [it's a struct](http://stackoverflow.com/questions/4360068/mixing-c-structs-and-objective-c-properties), not because of any secret Apple magic. – jscs Jul 15 '14 at 19:09
  • I think this could be interesting for those who also are wondering: [Why is “expression not assignable” (UIView.frame.origin)?](https://stackoverflow.com/questions/12132757/why-is-expression-not-assignable-uiview-frame-origin). – Travis79 Jul 15 '14 at 21:40

2 Answers2

1

I see a few options:

Prohibit the change entirely by returning a copy of time from its getter. You'll have to document the fact that this happens, and it may violate the principle of least surprise.

Use KVO to allow Watch instances to react to the fact that their Time member has been modified. As long as the modification goes through the Time's setter (which it must for this situation), the Watch can do what it needs to do in observeValueForKeyPath:ofObject:change:context:

Finally, consider whether Time even needs to be mutable. Probably your code here is just an example, but Time seems to be a value type. In general in Cocoa, these are not mutable. Changing the properties of Time that you don't want modified to readonly would solve this neatly for both you and the API user.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • **Option 1**: I’d like to differ between calls from outside (where I’d return a copy) and calls from self (where I’d want the actual object). Do you see any possibility for doing so (using the getter, not the instance variable directly)? — **Option 2**: Never done any KVO coding (except for getting/setting). Is there a possibility to observe changes of the whole object, not just single properties? …I guess you’d mentioned it, if there was one, but I’d like to be sure ;) — **Option 3**: As stated in @mvadim answer, I’d prefer to keep the object mutable, but still this maybe the solution. – Travis79 Jul 15 '14 at 20:23
  • Also, thank you for your answer …the comment field was to small to be thankful in the first place ;) – Travis79 Jul 15 '14 at 20:26
  • For option 1, you could make the property private and just expose a public getter as a separate method. Cf. [`-[UIView constraints]`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW171) For option 2, you don't need `Watch` to _observe_ changes to its own `time` property; just do whatever you need to do in `setTime:` – jscs Jul 15 '14 at 20:32
  • Of course, making the property private. I think I go for gate 1 or 3 :) …thank you! – Travis79 Jul 15 '14 at 21:17
1

Just add readonly to property:

@property (nonatomic, readonly) NSInteger hours;

And then set you property to readwrite in *.m file:

@property (nonatomic, readwrite) NSInteger hours;

Add custom initialiser like:

-(id)initWithHours:(NSInteger)hours andMinutes:(NSInteger)min
mvadim
  • 152
  • 6
  • Thanks for the suggestion. So far struggled with the idea to make the object immutable. But maybe I should reconsider… – Travis79 Jul 15 '14 at 20:15