5

I know it's not a great idea to try and place properties in a category. Can I access a class' instance variables from within a category that extends it? Or is it necessary to expose an accessor on the class being extended?

For example, let's say I have a class called "Person" and its implementation looks like this:

#import "Person.h"

@interface Person()
{
    NSMutableArray *_friends;
}
@end

@implementation Person

- (instancetype)init
{
    self = [super init];
    if (self) {
        _friends = [NSMutableArray array];
    }
    return self;
}

-(instancetype)initWithFirstname:(NSString *)firstName lastname:(NSString *)lastName
{
    self = [self init];
    if (self) {
        _firstName = firstName;
        _lastName = lastName;
    }
    return self;
}

-(NSString *)getFullName{
    return [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
}

@end

Notice the ivar _friends. Let's say (for some reason or other) I wanted to segregate all operations dealing with a person's friends into a category, like so:

#import "Person.h"

@interface Person (Friends)
-(NSArray *)getFriends;
-(void)addFriend:(Person *)person;
-(void)removeFriend:(Person *)person;
@end

In the category, Person(Friends), the compiler will not know about Person's ivar _friends.

i.e.

//Person.h 

@interface Person
@property(nonatomic, strong) NSMutableArray *friends;
...
@end

It would be preferable to not expose this.

jscs
  • 63,694
  • 13
  • 151
  • 195
Nick
  • 19,198
  • 51
  • 185
  • 312
  • 1
    "it's not a great idea to try and place instance variables in a category" It's not even possible. Categories can't declare storage, only methods. Your code seems to answer your question already. Can you be more clear about what you're asking? – jscs Mar 16 '14 at 19:24
  • @JoshCaswell I meant properties in categories. Which is possible. Sorry. I want to know how a category should access member data. So, from my example, I would like to be able to add/remove objects from Person's "_friends" ivar. But I would rather not expose _friends in Person public header. Does that make sense? – Nick Mar 16 '14 at 19:30
  • You're allowed and more than welcome to [post your own answer](http://meta.stackexchange.com/questions/120215/ended-up-solving-my-own-problem-question-what-to-do-with-the-post?lq=1). Please do that instead of adding it to the question body. You can also mark your answer as accepted if you think it's the best (and you won't hurt my feelings or anything by changing the checkmark). – jscs Mar 16 '14 at 20:07

3 Answers3

7

In general, categories can't access ivars; synthesized ivars and ivars from class extensions are private and invisible outside the main implementation.

You can, however, do what you want by declaring the ivar in an extension which is in its own private header, and importing that header into the category's implmentation file. Be sure to also import the private header into the class's main implementation file.

jscs
  • 63,694
  • 13
  • 151
  • 195
2

Who have told you that the compiler will not know about Person's _friends? It knows. Just declare _friends in the class @interface, not in an extension.

@interface Person : NSObject
{
@protected
      NSMutableArray *_friends;
}
@end

With @protected _friends will not be accessible for other objects.

Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
  • This is wrong; synthesized ivars are not visible in categories. – jscs Mar 16 '14 at 19:31
  • 1
    @JoshCaswell, if you define them in extension, then yes - invisible, if you define them in class defenition, then no - visible. – Cy-4AH Mar 16 '14 at 19:32
  • That's correct, however, public-`@interface`-declared ivars are not good practice, and OP specifically says that he doesn't want to do that. – jscs Mar 16 '14 at 19:35
  • Right. In my post _friends is declared in a class continuation. And that ivar is not visible to a category. I don't want to put _friends into the public interface. It's internal to Person shouldn't be accessed by other objects. – Nick Mar 16 '14 at 19:38
  • `@protected` is already the default; declaring an ivar protected doesn't change anything. – jscs Mar 16 '14 at 19:39
  • @JoshCaswell, ok, may be `@private` will suit Nick better. – Cy-4AH Mar 16 '14 at 19:41
  • @Nick, why my solution doesn't suit you? It's visible for categories and not accessible for other objects. – Cy-4AH Mar 16 '14 at 19:44
  • @cy-4AH No it doesn't. Notice in my original post that "_friends" is declared in the implementation's interface (in the .m file) not in the public interface (.h file). If you attempt to access an ivar declared in the implementation in a category it will not compile. – Nick Mar 17 '14 at 14:49
  • @Nick, Yes you defined it in extension. But what obstruct you move it in class defenition? In that case it will be visible for categories and not accessible for other objects. – Cy-4AH Mar 17 '14 at 14:53
  • 1
    There's a lot of bickering here— this solution works as long as the coder doesn't have a problem with exposing the existence of _(not to be confused with direct access to)_ the ivars.  @JoshCaswell's solution may be more common but isn't far removed— the ivars still need to be exposed in an `@interface Person …`, in a `.h` file.  JoshCaswell's has the benefit that it would be easier to hide that `.h` file from other-project access when this is all intended for a shared library, but to each their own. – Slipp D. Thompson May 12 '15 at 20:43
0

If you've got a lot of protocols, delegates, dataSources etc. on your e.g. MainViewController and you wanna outsource their callbacks to separate files (categories) like

"MainViewController+DelegateCallbacks.h"
"MainViewController+DelegateCallbacks.m"

but at the same time still wanna be able to access all the controller's private @properties from these categories without having to expose them in the public interface

"MainViewController.h"

the most elegant solution is still to create a private interface (extension) in a separate header file like

"MainViewController_PrivateInterface.h"

BUT - instead of the ivars - like Josh Caswell's already explained above, put all the @properties (that these outsourced delegates need to access) in that extension, too. That way you keep them all quasi-private hidden and nobody else gets to see them. Above all not in your public interface! And you do even have the choice to access your @properties' backing store ivars directly in code (instead of the convenience dot notation) just by manually creating the corresponding backing store ivars in this private external interface file. Just don't forget to import your private's interface header everywhere you wanna access these ivars (including your MainViewController ;-)


//
//  MainViewController.m
//

#import "MainViewController.h"
#import "MainViewController+DelegateCallbacks.h"

#import "MainViewController_PrivateInterface.h"


@interface MainViewController () <UICollectionViewDelegate,
                                  UICollectionViewDataSource,
                                  UICollectionViewDelegateFlowLayout,
                                  UIGestureRecognizerDelegate>

#pragma mark - <UIGestureRecognizerDelegate>
#pragma mark - <UIContentContainer>
#pragma mark - <UITraitEnvironment>

// etc.

@end

------------------------------------------------------------------------


//
//  MainViewController+DelegateCallbacks.h
//

#import "MainViewController.h"


@interface MainViewController (DelegateCallbacks)

@end

------------------------------------------------------------------------

//
//  MainViewController+DelegateCallbacks.m
//

#import "MainViewController+DelegateCallbacks.h"
#import "MainViewController_PrivateInterface.h"


@implementation MainViewController (DelegateCallbacks)

#pragma mark <UICollectionViewDataSource>
#pragma mark <UICollectionViewDelegate>
#pragma mark <UICollectionViewDelegateFlowLayout>

// etc.

@end

------------------------------------------------------------------------

//
//  MainViewController_PrivateInterface.h
//

#import "MainViewController.h"

@interface MainViewController () {
    // NSMutableArray <NSArray *> *_myArray_1;
    // NSMutableArray <UIBezierPath *> *_myArray_2;
}

@property (strong, nonatomic) NSMutableArray <NSArray *> *myArray_1;
@property (strong, nonatomic) NSMutableArray <UIBezierPath *> *myArray_2;

@property (weak, nonatomic) IBOutlet MyView *myView;
@property (weak, nonatomic) IBOutlet MyCollectionView *myCollectionView;

@property (nonatomic) CGFloat myFloat;

// etc.

@end
mramosch
  • 458
  • 4
  • 14