Can a category access instance variables defined in the class it extends?
Asked Answered
S

3

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.

Shareeshareholder answered 16/3, 2014 at 19:10 Comment(3)
"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?Innate
@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?Shareeshareholder
You're allowed and more than welcome to post your own answer. 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).Innate
I
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.

Innate answered 16/3, 2014 at 19:36 Comment(0)
G
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.

Griswold answered 16/3, 2014 at 19:26 Comment(10)
This is wrong; synthesized ivars are not visible in categories.Innate
@JoshCaswell, if you define them in extension, then yes - invisible, if you define them in class defenition, then no - visible.Griswold
That's correct, however, public-@interface-declared ivars are not good practice, and OP specifically says that he doesn't want to do that.Innate
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.Shareeshareholder
@protected is already the default; declaring an ivar protected doesn't change anything.Innate
@JoshCaswell, ok, may be @private will suit Nick better.Griswold
@Nick, why my solution doesn't suit you? It's visible for categories and not accessible for other objects.Griswold
@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.Shareeshareholder
@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.Griswold
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.Transit
C
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
Considerate answered 24/9, 2016 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.