20

I’d like to declare a public immutable property:

@interface Foo
@property(strong, readonly) NSSet *items;
@end

…backed with a mutable type in the implementation file:

@interface Foo (/* private interface*/)
@property(strong) NSMutableSet *items;
@end

@implementation
@synthesize items;
@end

What I want is a mutable collection in the implementation that gets cast into an immutable one when accessed from the outside. (I don’t care that the caller can cast the instance back to NSMutableSet and break the encapsulation. I live in a quiet, decent town where such things don’t happen.)

Right now my compiler treats the property as NSSet inside the implementation. I know there are many ways to get it working, for example with custom getters, but is there a way to do it simply with declared properties?

zoul
  • 102,279
  • 44
  • 260
  • 354
  • How do you access `items` in your implementation? `self.items`? And try `@property(strong, readwrite) NSMutableSet *items;` – Nekto Oct 10 '11 at 08:53
  • And why don't you want to declare `@property(strong, readonly) NSMutableSet *items;`? I think compiler won't allow to add or remove objects from it as it will be `readonly` – Nekto Oct 10 '11 at 08:55
  • I access the property simply as `items`. If you declare the public property as mutable and read-only, the compiler won’t allow setting a new value for the whole property (`[foo setItems:…]`), but won’t keep callers from changing the property contents (`[foo items] removeAllObjects]`). – zoul Oct 10 '11 at 09:08
  • 2
    @Netko: not so. If you make it readonly, you will not be able to change that ivar, but since the array is still mutable, you will be able to remove and add objects to it. – D.C. Oct 10 '11 at 09:08
  • Answered best here: http://stackoverflow.com/a/21109372/287403 – Bob Spryn Jan 22 '14 at 19:08
  • That’s just providing a custom getter. See the last paragraph of the question – I was trying to get it working with declared properties, with no custom getters or setters. – zoul Jan 23 '14 at 07:28
  • What exactly did you mean when you wrote "compiler treats the property as NSSet inside the implementation"? Are you referring to getting "No visible @interface for 'NSSet' declares the selector ..." errors when calling mutation methods of `NSMutableSet` (such as `addObject`)? When you wrote "access the property simply as `items`", do you mean as a property of `self` using dot notation? All told, something like `[self.items addObject:anObject]`? – outis Feb 07 '19 at 10:21

3 Answers3

9

The easiest solution is

@interface Foo {
@private
    NSMutableSet* _items;
}

@property (readonly) NSSet* items;

and then just

@synthesize items = _items;

inside your implementation file. Then you can access the mutable set through _items but the public interface will be an immutable set.

edsko
  • 1,628
  • 12
  • 19
  • The @synthesize is no longer needed in general, in later versions of iOS 4 (at least). Is it required in this specific usage? – Basil Bourque Aug 14 '13 at 09:00
  • Without the call to @synthesize you don't get to pick the name of the attribute (in this case, `_items`), so you cannot refer to it (except by guessing what the compiler picks). – edsko Aug 15 '13 at 08:59
  • 2
    No need to guess: Add an underscore. As of Xcode 4.4 with LLVM Compiler 4.0, we have the new feature "Default synthesis of @property instance variables and accessor methods". No need to call `@synthesize`. The default synthesis (autosynthesis) uses the name of the variable plus a prefix of an underscore character. So, for property `foo` you should be able to refer directly to `_foo`, as in `self->_foo = …whatever…;`. I'm no expert, but this seems to be working for me. Article about default synthesis: http://useyourloaf.com/blog/2012/08/01/property-synthesis-with-xcode-4-dot-4.html – Basil Bourque Aug 15 '13 at 10:39
  • If the compiler *guarantees* that behaviour, across versions, then that's an okay solution. But if that just happens to be what it picks now, but it might pick something else in the future, not such a good solution. – edsko Aug 19 '13 at 15:23
  • 2
    The compiler does guarantee it. It is the correct way. Synthesize shouldn't be used unless absolutely necessary anymore. – powerj1984 Nov 06 '14 at 20:54
8

You have to declare the property in the public interface as readonly in order the compiler not to complain. Like this:

Word.h

#import <Foundation/Foundation.h>

@interface Word : NSObject
@property (nonatomic, strong, readonly) NSArray *tags;
@end

Word.m

#import "Word.h"

@interface Word()
@property (nonatomic, strong) NSMutableArray *tags;
@end

@implementation Word
@end
Mr. G
  • 405
  • 4
  • 6
2

You could just do it yourself in your implementation:

@interface Foo : NSObject
    @property(nonatomic, retain) NSArray *anArray;
@end

--- implementation file ---

@interface Foo()
{
    NSMutableArray *_anArray;
}
@end

@implementation Foo

- (NSArray *)anArray
{
    return _anArray;
}

- (void)setAnArray:(NSArray *)inArray
{
     if ( _anArray == inArray )
     {
         return;
     }
     [_anArray release];
     _anArray = [inArray retain];
}

- (NSMutableArray *)mutablePrivateAnArray
{
    return _anArray;
}

@end
D.C.
  • 15,340
  • 19
  • 71
  • 102