Where to put iVars in "modern" Objective-C?
Asked Answered
R

5

83

The book "iOS6 by Tutorials" by Ray Wenderlich has a very nice chapter about writing more "modern" Objective-C code. In one section the books describes how to move iVars from the header of the class into the implementation file. Since all iVars should be private this seems to be the right thing to do.

But so far I found 3 ways of doing so. Everyone is doing it differently.

1.) Put iVars under @implementantion inside a block of curly braces (This is how it is done in the book).

2.) Put iVars under @implementantion without block of curly braces

3.) Put iVars inside private Interface above the @implementantion (a class extension)

All these solutions seems to work fine and so far I haven't noticed any difference in the behavior of my application. I guess there is no "right" way of doing it but I need to write some tutorials and I want to choose only one way for my code.

Which way should I go?

Edit: I am only talking about iVars here. Not properties. Only additional variables the object needs only for itself and that should not be exposed to the outside.

Code Samples

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end
Rancho answered 26/11, 2012 at 14:26 Comment(4)
No comment on "right" but another option -- which I seem to be moving toward -- is dumping iVars entirely and making everything a property (mostly within a class extension in the .m file). Consistency saves me from thinking about the implementation details of state.Peart
This question would really benefit others if you updated the question to show real code examples demonstrating each of the 3 options. I personally only use option 1, have seen option 3, but I'm not familiar with option 2.Handful
Thanks maddy. I added some sample code.Rancho
Sorry, just noticed the update. Option 2 isn't valid. That doesn't create ivars. That creates file global variables. Every instance of that class will share that one set of variables. You could them class variables, not instance variables.Handful
I
162

The ability to put instance variables in the @implementation block, or in a class extension, is a feature of the “modern Objective-C runtime”, which is used by every version of iOS, and by 64-bit Mac OS X programs.

If you want to write 32-bit Mac OS X apps, you must put your instance variables in the @interface declaration. Chances are you don't need to support a 32-bit version of your app, though. OS X has supported 64-bit apps since version 10.5 (Leopard), which was released over five years ago.

So, let's assume you are only writing apps that will use the modern runtime. Where should you put your ivars?

Option 0: In the @interface (Don't Do It)

First, let's go over why we don't want to put instance variables in an @interface declaration.

  1. Putting instance variables in an @interface exposes details of the implementation to users of the class. This may lead those users (even yourself when using your own classes!) to rely on implementation details that they should not. (This is independent of whether we declare the ivars @private.)

  2. Putting instance variables in an @interface makes compiling take longer, because any time we add, change, or remove an ivar declaration, we have to recompile every .m file that imports the interface.

So we don't want to put instance variables in the @interface. Where should we put them?

Option 2: In the @implementation without braces (Don't Do It)

Next, let's discuss your option 2, “Put iVars under @implementantion without block of curly braces”. This does not declare instance variables! You are talking about this:

@implementation Person

int age;
NSString *name;

...

That code defines two global variables. It does not declare any instance variables.

It's fine to define global variables in your .m file, even in your @implementation, if you need global variables - for example, because you want all of your instances to share some state, like a cache. But you can't use this option to declare ivars, because it doesn't declare ivars. (Also, global variables private to your implementation should usually be declared static to avoid polluting the global namespace and risking link-time errors.)

That leaves your options 1 and 3.

Option 1: In the @implementation with braces (Do It)

Usually we want to use option 1: put them in your main @implementation block, in braces, like this:

@implementation Person {
    int age;
    NSString *name;
}

We put them here because it keeps their existence private, preventing the problems I described earlier, and because there's usually no reason to put them in a class extension.

So when do we want to use your option 3, putting them in a class extension?

Option 3: In a class extension (Do It Only When Necessary)

There's almost never a reason to put them in a class extension in the same file as the class's @implementation. We might as well just put them in the @implementation in that case.

But occasionally we might write a class that's big enough that we want to divide up its source code into multiple files. We can do that using categories. For example, if we were implementing UICollectionView (a rather big class), we might decide that we want to put the code that manages the queues of reusable views (cells and supplementary views) in a separate source file. We could do that by separating out those messages into a category:

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

OK, now we can implement the main UICollectionView methods in UICollectionView.m and we can implement the methods that manage reusable views in UICollectionView+ReusableViews.m, which makes our source code a little more manageable.

But our reusable view management code needs some instance variables. Those variables have to be exposed to the main class @implementation in UICollectionView.m, so the compiler will emit them in the .o file. And we also need to expose those instance variables to the code in UICollectionView+ReusableViews.m, so those methods can use the ivars.

This is where we need a class extension. We can put the reusable-view-management ivars in a class extension in a private header file:

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

We won't ship this header file to users of our library. We'll just import it in UICollectionView.m and in UICollectionView+ReusableViews.m, so that everything that needs to see these ivars can see them. We've also thrown in a method that we want the main init method to call to initialize the reusable-view-management code. We'll call that method from -[UICollectionView initWithFrame:collectionViewLayout:] in UICollectionView.m, and we'll implement it in UICollectionView+ReusableViews.m.

Interchange answered 26/11, 2012 at 21:12 Comment(4)
Option 3: also usable when 1) you want to enforce using properties for your ivars, even within your own implementation (@property int age), and 2) you want to override a readonly public property (@property (readonly) int age) in your header to allow your code to access the property as readwrite in the implementation (@property (readwrite) int age). Unless there are other ways to do this, @rob mayoff?Fastback
@Fastback If you want to override a readonly property to privately be readwrite, you need to use a class extension. But the question was about where to put ivars, not where to put property declarations. I don't see how using a class extension can prevent the implementation from accessing ivars. To do that, you'd need to put your method implementations in a category, because the compiler must see all ivar-declaring class extensions before it sees the @implementation.Interchange
@rob mayoff, true and true - you make valid points. Holli specified that properties weren't at question here, and "enforce" was a bit strong on my part regarding their use. Regardless, if one wants to use properties to access their ivars, instead of doing so directly, then that would be the way to do it. And of course you can still access the ivars directly using the '_', (_age = someAge). I added the comment because I thought property use worth mentioning when discussing ivars, since many people use them together, and this would be the way to do it.Fastback
That is “Option 0: In the @interface (Don't Do It)”.Interchange
C
5

Option 2 is flat out wrong. Those are global variables, not instance variables.

Options 1 and 3 are essentially identical. It makes absolutely no difference.

The choice is whether to put instance variables in the header file or the implementation file. The advantage of using the header file is that you have a quick and easy keyboard shortcut (Command + Control + Up in Xcode) to view and edit your instance variables and interface declaration.

The disadvantage is that you expose the private details of your class in a public header. That's not desirable is some cases, particularly if you're writing code for others to use. Another potential problem is that if you're using Objective-C++, it's good to avoid putting any C++ data types in your header file.

Implementation instance variables are great option for certain situations, but for most of my code I still put the instance variables in the header simply because it's more convenient for me as a coder working in Xcode. My advice is to do whatever you feel is more convenient for you.

Candlewick answered 26/11, 2012 at 20:11 Comment(0)
F
4

Largely it has to do with the visibility of the ivar to subclasses. Subclasses will not be able to access instance variables defined in the @implementation block.

For reusable code that I plan to distribute (e.g. library or framework code) where I prefer not expose instance variables for public inspection, then I'm inclined to place the ivars in the implementation block (your option 1).

Fujimoto answered 26/11, 2012 at 14:31 Comment(0)
I
3

You should put instance variables in a private interface above the implementation. Option 3.

The documentation to read on this is the Programming in Objective-C guide.

From the documentation:

You Can Define Instance Variables without Properties

It’s best practice to use a property on an object any time you need to keep track of a value or another object.

If you do need to define your own instance variables without declaring a property, you can add them inside braces at the top of the class interface or implementation, like this:

Iridotomy answered 26/11, 2012 at 14:30 Comment(1)
I agree with you: If you were going to define ivar, the private class extension is the best place. Curiously, though, the documentation you reference not only doesn't take a position on where ivars should be defined (it merely lays out all three alternatives), but it goes further and recommends that one shouldn't be using ivars at all, but rather "it's best practice to use a property." That is the best advice I've seen so far.Continent
S
1

Public ivars should really be declared properties in the @interface (likely what you're thinking of in 1). Private ivars, if you're running the latest Xcode and using the modern runtime (64-bit OS X or iOS), can be declared in the @implementation (2), rather than in a class extension, which is likely what you're thinking of in 3.

Salomon answered 26/11, 2012 at 14:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.