3

I want to create a class that serves as a base (or "abstract") class to be extended by subclasses. The best way I can explain what I'm talking about is with a few examples. Here's a possible interface for my superclass:

#import <Cocoa/Cocoa.h>
#import "MyViewControllerDelegate.h"

@interface MyViewController : NSViewController

@property (nonatomic, weak) id<MyViewModeControllerDelegate> delegate;
@property (nonatomic, copy) NSArray *content;

@end

Writing it like that seems nice and clean, but I can't access the ivars from my subclasses.

After doing some research, I've concluded that a good way to provide subclasses with direct access to ivars is to use the @protected directive and include any declarations in the header file so subclasses can see it:

#import <Cocoa/Cocoa.h>
#import "MyViewControllerDelegate.h"

@interface MyViewController : NSViewController {
@protected
    __weak id<MyViewControllerDelegate> _delegate;
    NSMutableArray *_content;
}

@property (nonatomic, weak) id<BSDViewModeControllerDelegate> delegate;
@property (nonatomic, copy) NSArray *content;

@end

I personally don't have an issue with that, and it seems to work the way I want it to (e.g. subclasses can access the ivars directly, but other classes have to use accessors). However, I read blog posts or Stack Overflow answers every day that say instance variables should just be synthesized, or "I don't even touch instance variables anymore."

The thing is, I started learning Objective-C post-ARC, so I'm not fully aware of the ways in which developers had to do things in the past. I personally like the control I have when I implement my own getters/setters, and I like being able to actually see instance variable declarations, but maybe I'm old school. I mean, if one should "just let the compiler synthesize the instance variables," how does one include any sort of logic or "side effects" without implementing a bunch of KVO?

For example, if my instance variables and getters/setters are synthesized, how do I initialize stuff lazily? For example, I sometimes like to do this:

- (NSArray *)myLazyArray
{
    if ( _myLazyArray == nil ) {
        self.myLazyArray = @[];
    }
    return _myLazyArray.copy;
}

Or how do I make sure that a value being set isn't the same as the currently set value? I'll sometimes implement a check in my mutator method like this:

- (void)setMyLazyArray:(NSArray *)array
{
    if ( [array isEqualToArray:_myLazyArray] )
        return;
    _myLazyArray = array.mutableCopy;
}

I've read all of Apple's documentation, but half their docs date back to 2008 (or worse in some cases), so I'm not exactly sure they're the best place to get information on the matter.

I guess the gist of my question is this: Is there a preferred "modern" way of handling instance variables, variable synthesis, inheritance, scope, etc. in Objective-C? Bonus points for answers that don't include "Bro, Swift." or "You aren't using Swift?"

Any guidance would be much appreciated. Thanks for reading!

Ben Stock
  • 1,986
  • 1
  • 22
  • 30
  • what is the problem? you can have synthesized ivars _and_ custom getters/setters – Bryan Chen Oct 07 '14 at 00:40
  • @BryanChen Yeah, I know. I guess my whole issue is that I just want to do something the "right" way. Or I should rephrase that: the "modern" way. All too often I think I'm doing something correctly, only to find out that the way I'm doing it is planned for deprecation or something similar. For example, I used to use `@synthesize myVar=_myVar`. Then all of the sudden, I no longer needed to do that. Granted, if I implement a getter *and* a setter without the directive, I get warnings left and right. Thus, I end up having to add `@synthesize` or add an ivar. It just kind of confuses me. – Ben Stock Oct 07 '14 at 04:14
  • you need `@synthesize myVar=_myVar` _only_ if you implemented _all_ accessors. (getter and setter for readwrite, getter for readonly) – Bryan Chen Oct 07 '14 at 04:24

1 Answers1

2

Why do your subclasses need access to your ivars? Ivars are an implementation detail and subclasses shouldn't be concerned with that. There could be all sorts of side effects if the parent class is doing logic in the property setter/getters. Therefore, always access them through the property.

Assuming this is in your subclass and you are overriding a property getter:

- (NSArray *)myLazyArray
{
    if ( super.myLazyArray == nil ) {
        // do what you need to do to populate the array
        // assign it to yourself (or super)
        self.myLazyArray = @[];
    }

    return super.myLazyArray;
}

And then for the setter:

- (void)setMyLazyArray:(NSArray *)array
{
    if ( [array isEqualToArray:super.myLazyArray] )
        return;
    super.myLazyArray = array.mutableCopy;
}
Jon Gilkison
  • 1,225
  • 9
  • 8
  • The only reason I generally need access to an ivar is to quickly work with a mutable collection without the overhead that comes with copying (which occurs when I get a collection via property). Am I wrong in assuming copy operations can be expensive? I've just always thought that direct access to the members of a mutable array is better than getting access to its members by copying the entire thing. I'm probably just overthinking all this. – Ben Stock Oct 07 '14 at 04:06
  • Well you shouldn't be returning copies from properties in the first place. The caller should determine if they want a copy or not. I honestly can't think of one reason why you'd return a copy from a property that jibes with proper use of properties in the first place. – Jon Gilkison Oct 07 '14 at 04:14
  • I think if you are going to return a copy, then that should be in a clearly named method. Just my opinion though :) – Jon Gilkison Oct 07 '14 at 04:16
  • Wait, I thought it was bad practice to have mutable properties because then someone else could mutate it right from under your feet. I usually have a mutable ivar backing an immutable property. Now I'm really confused. – Ben Stock Oct 07 '14 at 04:17
  • Clearly defined names: Oh, definitely. I totally agree. Ownership of most returned Foundation objects can be figured out by its name. I'm not against that at all! Edit: By the way, you'll notice that if my property returns a copy, it has the `(copy)` attribute added. – Ben Stock Oct 07 '14 at 04:19
  • Ok, so re: mutable properties, yes this is "bad", but you need to leave the responsibility to do the right thing up to the caller. If my class wants to mutate your class's array, it's up to my class to make a mutable copy, do it's thing and then re-assign it to your class's property. Your class shouldn't be making any assumptions about anything. Sorry, I just downed two caphe su da (vietnamese jet fuel coffee) so I'm a little scattered at the moment, but hopefully that makes sense. – Jon Gilkison Oct 07 '14 at 04:27
  • That being said, I almost never write custom setters that perform logic when the property is set, I leave that to methods to implement. If I really don't want an array property on my class to be messed with I make it an NSArray and readonly, leaving setting it or mutating it to methods in the class (think subViews on NSView/UIView). I think this is a better, more self documenting way of dealing with it. – Jon Gilkison Oct 07 '14 at 04:40