99

I have declared a readonly property in my interface as such:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

Maybe I'm misunderstanding properties, but I thought that when you declare it as readonly, you can use the generated setter inside of the implementation (.m) file, but external entities cannot change the value. This SO question says that's what should happen. That is the behavior I'm after. However, when attempting to use the standard setter or dot syntax to set eventDomain inside of my init method, it gives me an unrecognized selector sent to instance. error. Of course I'm @synthesizeing the property. Trying to use it like this:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

So am I misunderstanding the readonly declaration on a property? Or is something else going on?

Community
  • 1
  • 1
Alex
  • 64,178
  • 48
  • 151
  • 180

7 Answers7

122

You need to tell the compiler that you also want a setter. A common way is to put it in a class extension in the .m file:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Wayne
  • 59,728
  • 15
  • 131
  • 126
Eiko
  • 25,601
  • 15
  • 56
  • 71
  • 1
    Besides manually defining a setter without exposing it in the header file, AFAIK this is the only other way. – BoltClock Jan 03 '11 at 17:14
  • Ah! Add a category! This is exactly what I am after. You rock! As soon as I can accept the answer...:) – Alex Jan 03 '11 at 17:14
  • 23
    It isn't a category. It is a class extension (as Eiko said). They are distinctly different, though used for similar purposes. You couldn't, for example, do the above in a true category. – bbum Jan 03 '11 at 17:18
  • 2
    I noticed that a class that inherits from a class that has class extension properties, will not see them. i.e inheritance only see the external interface. confirm? – Yogev Shelly Jul 02 '12 at 10:54
  • 5
    That's not quite fair bbum. It may be different, but it is known as the "Anonymous category" – MaxGabriel Oct 15 '12 at 02:19
  • 3
    Is this in addition to the op's initial declaration in the public interface? Meaning, does this class extension declaration override the initial declaration in the `.h`? Otherwise I don't see how this would expose a public setter. Thanks – Madbreaks Dec 19 '12 at 20:07
  • 4
    @Madbreaks It's additional, but it's in the .m file. So the compiler knows to make it readwrite for that class, but external usage is still limited to readonly as expected. – Eiko Dec 23 '12 at 21:16
40

Eiko and others gave correct answers.

Here's a simpler way: Directly access the private member variable.

Example

In the header .h file:

@property (strong, nonatomic, readonly) NSString* foo;

In the implementation .m file:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

That’s it, that’s all you need. No muss, no fuss.

Details

As of Xcode 4.4 and LLVM Compiler 4.0 (New Features in Xcode 4.4), you need not mess with the chores discussed in the other answers:

  • The synthesize keyword
  • Declaring a variable
  • Re-declaring the property in the implementation .m file.

After declaring a property foo, you can assume Xcode has added a private member variable named with a prefix of underscore: _foo.

If the property was declared readwrite, Xcode generates a getter method named foo and a setter named setFoo. These methods are implicitly called when you use the dot notation (my Object.myMethod). If the property was declared readonly, no setter is generated. That means the backing variable, named with the underscore, is not itself readonly. The readonly means simply that no setter method was synthesized, and therefore using the dot notation to set a value fails with a compiler error. The dot notation fails because the compiler stops you from calling a method (the setter) that does not exist.

The simplest way around this is to directly access the member variable, named with the underscore. You can do so even without declaring that underscore-named variable! Xcode is inserting that declaration as part of the build/compile process, so your compiled code will indeed have the variable declaration. But you never see that declaration in your original source code file. Not magic, just syntactic sugar.

Using self-> is a way to access a member variable of the object/instance. You may be able to omit that, and just use the var name. But I prefer using the self+arrow because it makes my code self-documenting. When you see the self->_foo you know without ambiguity that _foo is a member variable on this instance.


By the way, discussion of pros and cons of property accessors versus direct ivar access is exactly the kind of thoughtful treatment you'll read in Dr. Matt Neuberg's Programming iOS book. I found it very helpful to read and re-read.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    Perhaps you should add that one should never access a member variable directly (from without its class)! Doing so is a violation of OOP (information hiding). – HAS Aug 14 '13 at 11:20
  • 2
    @HAS Correct, the scenario here is *privately* setting the member variable not seen from outside this class. That is the entire point of the original poster's question: Declaring a property as `readonly` so that no *other* class may set it. – Basil Bourque Aug 14 '13 at 11:32
  • I was looking at this post for how to create a readonly property. This does not work for me. It does allow me to assign the value in the init, but I can also change the _variable later with an assignment. it does not raise a compiler error or warning. – netskink Mar 09 '16 at 16:08
  • @BasilBourque Thank you very much for the helpful edit on that "persistence" question! – GhostCat May 08 '17 at 05:55
36

Another way I've found to work with readonly properties is to use @synthesize to specify the backing store. For example

@interface MyClass

@property (readonly) int whatever;

@end

Then in the implementation

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Your methods can then set m_whatever, since it is a member variable.


Another interesting thing I have realized in the past few days is you can make readonly properties that are writable by subclasses like such:

(in the header file)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Then, in the implementation

@synthesize myProperty = m_propertyBackingStore;

It will use the declaration in the header file, so subclasses can update the value of the property, while retaining its readonlyness.

Slightly regrettably in terms of data hiding and encapsulation though.

yano
  • 4,095
  • 3
  • 35
  • 68
  • 1
    The first way you described is easier to deal with than the accepted answer. Just an "@synthesize foo1, foo2, foo3;" in the .m, and all is good. – sudo Apr 01 '14 at 03:10
20

See Customizing Existing Classes in the iOS Docs.

readonly Indicates that the property is read-only. If you specify readonly, only a getter method is required in the @implementation. If you use @synthesize in the implementation block, only the getter method is synthesized. Moreover, if you attempt to assign a value using the dot syntax, you get a compiler error.

Readonly properties only have a getter method. You can still set the backing ivar directly within the property's class or using key value coding.

Jason McCreary
  • 71,546
  • 23
  • 135
  • 174
Jonah
  • 17,918
  • 1
  • 43
  • 70
9

You are misunderstanding the other question. In that question there is a class extension, declared thus:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

That is what generates the setter only visible within the class's implementation. So as Eiko says, you need to declare a class extension and override the property declaration to tell the compiler to generate a setter only within the class.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
5

The shortest solution is:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
  • 2,629
  • 2
  • 16
  • 14
2

If a property is defined as readonly, that means that there effectively wont be a setter that can be used either internally to the class or externally from other classes. (i.e.: You'll only have a "getter" if that makes sense.)

From the sounds of it, you want a normal read/write property that's marked as private, which you can achieve by setting the class variable as private in your interface file as such:

@private
    NSString* eventDomain;
}
John Parker
  • 54,048
  • 11
  • 129
  • 129