I want to create a class that serves as a base (or "abstract") class to be extended by subclasses. The best way I can explain what I'm talking about is with a few examples. Here's a possible interface for my superclass:
#import <Cocoa/Cocoa.h>
#import "MyViewControllerDelegate.h"
#interface MyViewController : NSViewController
#property (nonatomic, weak) id<MyViewModeControllerDelegate> delegate;
#property (nonatomic, copy) NSArray *content;
#end
Writing it like that seems nice and clean, but I can't access the ivars from my subclasses.
After doing some research, I've concluded that a good way to provide subclasses with direct access to ivars is to use the #protected directive and include any declarations in the header file so subclasses can see it:
#import <Cocoa/Cocoa.h>
#import "MyViewControllerDelegate.h"
#interface MyViewController : NSViewController {
#protected
__weak id<MyViewControllerDelegate> _delegate;
NSMutableArray *_content;
}
#property (nonatomic, weak) id<BSDViewModeControllerDelegate> delegate;
#property (nonatomic, copy) NSArray *content;
#end
I personally don't have an issue with that, and it seems to work the way I want it to (e.g. subclasses can access the ivars directly, but other classes have to use accessors). However, I read blog posts or Stack Overflow answers every day that say instance variables should just be synthesized, or "I don't even touch instance variables anymore."
The thing is, I started learning Objective-C post-ARC, so I'm not fully aware of the ways in which developers had to do things in the past. I personally like the control I have when I implement my own getters/setters, and I like being able to actually see instance variable declarations, but maybe I'm old school. I mean, if one should "just let the compiler synthesize the instance variables," how does one include any sort of logic or "side effects" without implementing a bunch of KVO?
For example, if my instance variables and getters/setters are synthesized, how do I initialize stuff lazily? For example, I sometimes like to do this:
- (NSArray *)myLazyArray
{
if ( _myLazyArray == nil ) {
self.myLazyArray = #[];
}
return _myLazyArray.copy;
}
Or how do I make sure that a value being set isn't the same as the currently set value? I'll sometimes implement a check in my mutator method like this:
- (void)setMyLazyArray:(NSArray *)array
{
if ( [array isEqualToArray:_myLazyArray] )
return;
_myLazyArray = array.mutableCopy;
}
I've read all of Apple's documentation, but half their docs date back to 2008 (or worse in some cases), so I'm not exactly sure they're the best place to get information on the matter.
I guess the gist of my question is this: Is there a preferred "modern" way of handling instance variables, variable synthesis, inheritance, scope, etc. in Objective-C? Bonus points for answers that don't include "Bro, Swift." or "You aren't using Swift?"
Any guidance would be much appreciated. Thanks for reading!
Why do your subclasses need access to your ivars? Ivars are an implementation detail and subclasses shouldn't be concerned with that. There could be all sorts of side effects if the parent class is doing logic in the property setter/getters. Therefore, always access them through the property.
Assuming this is in your subclass and you are overriding a property getter:
- (NSArray *)myLazyArray
{
if ( super.myLazyArray == nil ) {
// do what you need to do to populate the array
// assign it to yourself (or super)
self.myLazyArray = #[];
}
return super.myLazyArray;
}
And then for the setter:
- (void)setMyLazyArray:(NSArray *)array
{
if ( [array isEqualToArray:super.myLazyArray] )
return;
super.myLazyArray = array.mutableCopy;
}
Related
I have a class with the following property exposed in the .h file:
#property (readonly, nonnull) NSArray<String *> * routeContext;
As you can see this is a NSArray which is not mutable. In the implementation though I want to be able to work with this array as a mutable one (NSMutableArray) so it will be easy to add, remove objects from it.
What is the best approach to do it?
I was thinking about holder a NSMutableArray in the m file which backs the read only NSArray but it seems kinda dirty to me, is there any other suggestions?
The reason I don't want to set the property to NSMutableArray although its readonly is that readonly doesn't really make sense with NSMutableArray.
Thanks.
I would add a read-write NSMutableArray property to a class extension in the .m file. Then implement the read-only property as a method that returns a copy of the mutable property.
In the .m file:
#interface SomeClass()
#property (nonatomic, strong) NSMutableArray<NSString *> *myRouteContext;
#end
#implementation SomeClass
- (NSArray<NSString *> *)routeContext {
return [myRouteContext copy];
}
#end
In all of your implementation code you use myRouteContext. Only clients of the class use routeContext for the read-only version.
I wouldn't call this "dirty" in any way. There's still only one backing instance variable implicitly created by the myRouteContext property. No instance variables are created for the read-only routeContext property. The #property for that one is just syntactic sugar for the routeContext method you implement.
In addition to the approach suggested by HangarRash i would consider two other options:
Extending routeContext property itself in the class extension:
#interface TDWObject ()
#property (copy, nonatomic, nonnull) NSMutableArray<NSString *> *routeContext;
#end
Just introducing ivar in the class extension for the property manually (and accessing it directly in the implementation):
#interface TDWObject () {
NSMutableArray<NSString *> *_routeContext;
}
#end
Personally I would prefer the manual ivar due to the following reasons:
It doesn't introduce any redundant methods that clang would synthesise otherwise (you neither need extra getter, nor setter for a NSMutableArray *)
It's the most performant (accessing ivar directly).
I would also recommend to alter the property attributes as follows:
// The header file
#interface TDWObject : NSObject
#property (copy, readonly, nonatomic, nonnull) NSArray<NSString *> *routeContext;
#end
Here a couple of clarifications regarding the properties choice:
copy storage - technically for a readonly property storage attrbitue should not make much difference, because it predominantly denotes setter semantic (if we don't count that the value for this property can also be passed as a constructor argument). However, in our case the getter is custom and returns a copy of the internal object (instead of just reference to it). If you look through Cocoa/Cocoa Touch API, they often use copy attribute when they want to explicitly say that you deal with copies of internal data structure and any changes made to the instance obtained from the property wouldn't be tracked by the owning object. (e.g. -[NSCharacterSet invertedSet]), thus it's an important part of the interface description. Why the copy is needed at all? because otherwise the client code can easily exploit the mutability of the original data, and manage its content itself.
nonatomic atomicity - first, I don't know if there is a reason to make the property atomic, and you commonly use nonatomic properties by default (because they don't have burden of synchronisation, which slows down access/read performance). Second - clang would not be able to pair a synthesized setter with a user defined getter (if you choose to use properties approaches instead of ivar). Last, but not least - since getter is user-defined, you will have to manage the synchronisation yourself, so it doesn't come "for free".
Finally, the implementation part would look like this:
#implementation TDWObject
#pragma mark Lifecycle
- (instancetype)init {
if (self = [super init]) {
_routeContext = [NSMutableArray array];
}
return self;
}
#pragma mark Actions
- (NSArray<NSString *> *)routeContext {
return [_routeContext copy];
}
- (void)addFoo {
[_routeContext addObject:#"Foo"];
}
#end
I think I've been using Objective-C properties incorrectly. Specifically, I've been treating them like instance variables.
Here's an example of a recent interface:
// AIClass.h
#import "AIDataUtils.h"
#interface AIViewController : UIViewController
#property (strong, nonatomic) AIDataUtils *dataUtils;
#end
Then, in my implementation, I would use self.dataUtils as a way for any method in the class to easily access the same thing. No object from the outside would ever be interacting with that property.
What I'm realizing is that what I should have been doing is importing and declaring AIDataUtils in the implementation and not the interface. I think that would look like this:
// AIClass.m
#import "AIDataUtils.h"
#interface AIViewController ()
{
AIDataUtils *dataUtils;
}
#end
#implementation AIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
dataUtils = [[AIDataUtils alloc] init];
...
}
The docs say:
Avoid explicitly declaring public instance variables. Developers should concern themselves with an object’s interface, not with the details of how it stores its data.
My understanding here is if another object has no business touching AIDataUtils, don't put it in the interface. The fact that a property exists in an interface should be a hint that you're supposed to feed or do something with that property.
Am I hot or cold?
My understanding here is if another object has no business touching AIDataUtils, don't put it in the interface.
You're right, but that doesn't mean that you can't keep using properties for internal values too -- just don't declare them in your public interface. Usually, using a class extension as you've suggested is a fine way to have your properties while still keeping internal things (more or less) private.
There was a period in the evolution of Objective-C when properties were very helpful in managing memory -- if you used a property's accessors everywhere, you could worry a lot less about when to retain and when to release something because the accessors would do that for you. Now that we have ARC, the memory management aspect of properties is less important, but a lot of us are still conditioned to use properties even for internal stuff. If nothing else, internal-only properties can make your code a little more consistent-looking.
If you intend dataUtils to be a private implementation detail, then you shouldn't declare it in the #interface in the header file.
Even if you want to keep it private, you can still make it a property in the .m file:
#interface AIViewController ()
#property (strong, nonatomic) AIDataUtils *dataUtils;
#end
Whether to make it a property or just an instance variable is a matter of taste and depends on how you use it. For example, if you only want to allocate the AIDataUtils instance lazily, you might as well make it a property and do the lazy allocation in the getter.
If you decide to make it an instance variable, there's probably no reason to declare it in a class extension. You can just declare it in the #implementation:
#implementation AIViewController {
AIDataUtils *dataUtils;
}
- (void)viewDidLoad {
[super viewDidLoad];
dataUtils = [[AIDataUtils alloc] init];
...
You can learn more about where to declare instance variables in this answer.
I'm working on building an iOS 6 app.
I have a class TDBeam which inherits from superclass TDWeapon.
The superclass TDWeapon declares a #property in the TDWeapon.h file:
#interface TDWeapon : UIView
#property (nonatomic) int damage;
#end
I do not explicitly #synthesize the property, as I'm letting Xcode automatically do so.
In the subclass TDBeam I override the getter in the TDBeam.m file:
#import "TDBeam.h"
#implementation TDBeam
- (int)damage {
return _damage;
}
#end
Xcode auto-completes the getter method name, as expected. But when I attempt to reference the _damage instance variable (inherited from the superclass), I get a compiler error:
Use of undeclared identifier '_damage'
What am I doing wrong here? I've tried explicitly adding #synthesize, and changing the name of the _damage ivar, but the compiler doesn't "see" it or any other ivars from the superclass. I thought ivars were visible and accessible from subclasses?
Synthesized ivars are not visible to subclasses, whether they are explicitly or automatically created: What is the visibility of #synthesized instance variables? Since they are effectively declared in the implementation file, their declaration isn't included in the "translation unit" that includes the subclass.
If you really want to access that ivar directly, you'll have to explicitly declare it (in its default "protected" form) somewhere that the subclass can see it, such as a class extension of the superclass in a private header.
There are a lot of posts on this topic on Stack Overflow, none of which offer simple concrete advice, but this topic sums it up most succinctly, and Josh's answer is the best in any.
What he kinda stops short of saying outright, is, if this is the kind of thing you want to do, don't use #property at all. Declare your regular protected variable in your base class as he says, and write you're own setters and getters if you need them. The ivar will be visible to any subclasses who can then write their own setters/getters.
At least that's where i've landed on the issue, although I'd a total newb to subclassing.
The idea of creating private headers to host your anonymous category and re-#sythesizing your ivars in your subclass just seems wrong on so many levels. I'm also sure I've probably missed some fundamental point somewhere.
Edit
Okay after some lost sleep, and inspired by Stanford's 2013 iTunes U course, here I believe is an example solution to this problem.
MYFoo.h
#import <Foundation/Foundation.h>
#interface MYFoo : NSObject
// Optional, depending on your class
#property (strong, nonatomic, readonly) NSString * myProperty;
- (NSString *)makeValueForNewMyProperty; //override this in your subclass
#end
MYFoo.m
#import "MYFoo.h"
#interface MYFoo ()
#property (strong, nonatomic, readwrite) NSString * myProperty;
#end
#implementation MYFoo
// Base class getter, generic
- (NSDateComponents *)myProperty {
if (!_myProperty) {
_myProperty = [self makeValueForNewMyProperty];
}
return _myProperty;
}
// Replace this method in your subclass with your logic on how to create a new myProperty
- (NSString *)makeValueForNewMyProperty {
// If this is an abstract base class, we'd return nil and/or throw an exception
NSString * newMyProperty = [[NSString alloc]init];
// Do stuff to make the property the way you need it...
return newMyProperty;
}
#end
Then you just replace makeValueForNewMyProperty in your subclass with whatever custom logic you need. Your property is 'protected' in the base class but you have control over how it is created, which is basically what you are trying to achieve in most cases.
If your makeValueForNewMyProperty method requires access to other ivars of the base class, they will, at the very least, have to be be public readonly properties (or just naked ivars).
Not exactly 'over-ridding a getter' but it achieves the same sort of thing, with a little thought. My apologies if, in trying to make the example generic, some elegance and clarity has been lost.
I'd like to do the following, in an abstract way:
// .h
#interface SomeObject : NSObject
#property (readonly) NSArray myProperty;
#end
// .m
#interface SomeObject ()
#property (readwrite) NSMutableArray myProperty;
#end
#implementation SomeObject
#end
According to the section Subclassing with Properties in the Mac Developer Library it is allowed to overwrite readonly properties with readwrite. What doesn't work is using a subclass for the property type. I used NSMutableArray as an example, but it could be any other class/subclass combination.
According to inheritance rules, it should be ok though. readonly just generates the getter which also is allowed to return a subclass object.
How do you tackle such cases when you need a subclass type for some property for internal use?
An ugly way would be the following, but I'd like to avoid that as it means that I cannot use the self. getters and setters when accessing subclass methods.
// .h
#interface SomeObject : NSObject
#property (readonly) NSArray myProperty;
#end
// .m
#implementation SomeObject {
NSMutableArray _myProperty;
}
#synthesize myProperty = _myProperty;
#end
EDIT (based on your edits): Your specific case after the edit is a somewhat special and common case (if it can be both at the same time), and requires some careful consideration.
The reason this is a special is because the subclass is a mutable form of the exposed class. The caller may expect that it will not change after receiving it. But if you hand back your internal object, then it might mutate. You have several options:
Return an immutable copy. This is often the best solution for small collections. It's certainly the simplest. But if the accessor may be called often and the collection is large, it can be prohibitively expensive.
Make your internal property immutable. If requests for the property are much more common than changes to the property, it can be more efficient to recreate the object when it mutates (using arrayByAddingObject:, subarrayWithRange: and the like).
Warn the caller that the object being returned may change.... uggh... I've done this in one case where I needed the performance, but it's quite dangerous.
I've never actually done it this way, but you could also create your own copy-on-write this way: Return the mutable version directly and mark a flag that it is now "dirty." When mutation is required internally, make a mutable copy and store it in your property (letting go of the old collection). This seems a lot of complexity, but might be useful for some situations, particularly if reads and writes tend to clump separately (lots of reads followed by lots of writes).
OLD ANSWER based on NSObject vs. NSString:
I assume your goal here is to make myProperty be of some opaque type, rather than leaking the fact that it is an NSString? Perhaps so you can change your mind later on how it's actually implemented? There are a few options. The easiest is to define it of type id. Then internally just treat it as a string. id can be anything. It is usually preferred over NSObject*.
If you want more type-safety internally, then you can create a private property with another name of type NSString and return it for myProperty like this:
SomeObject.h
#interface SomeObject : NSObject
#property (readonly) id myProperty;
#end
SomeObject.m
#interface SomeObject ()
#property (readwrite) NSString *myInternalProperty;
#end
#implementation SomeObject
- (id)myProperty {
return myInternalProperty;
}
#end
Another hiding technique you can use (if hiding is very important to you) is a subclass. For example:
SomeObject.h
#class MyOpaque;
#interface SomeObject : NSObject
#property (readonly) MyOpaque *myProperty;
#end
SomeObject.m
#interface MyOpaque : NSString
#end
#implementation MyOpaque
#end
#implementation SomeObject
#end
Since the caller does not have an #interface definition for MyOpaque, he can't send messages to it without a compiler warning.
How do you tackle such cases when you need a subclass type for some
property for internal use?
Properties are explicitly not for internal use, they are members of a public interface.
If you need an internal value define a member field and override the setter of the property to set your internal value.
I would like to add a property to UITableView in a Class Extension:
#interface UITableViewController ()
#property NSString *entityString;
#end
Then I import the extension and then I use entityString property in a subclass of UITableViewController:
#implementation CustomerTableViewController
- (void)viewDidLoad {
self.entityString = #"Customer";
...
[super viewDidLoad];
}
...
Apple documentation says:
the compiler will automatically synthesize the relevant accessor
methods (...) inside the primary class
implementation.
But when I try to execute it I get this error:
-[CustomerTableViewController setEntityString:]: unrecognized selector sent to instance 0x737b670
What am I doing wrong? maybe the property cannot be accessed by subclasses?
Try using a category with Associative References instead. It is much cleaner and will work on all instances of UIButton.
UIButton+Property.h
#import <Foundation/Foundation.h>
#interface UIButton(Property)
#property (nonatomic, retain) NSObject *property;
#end
UIButton+Property.m
#import "UIButton+Property.h"
#import <objc/runtime.h>
#implementation UIButton(Property)
static char UIB_PROPERTY_KEY;
#dynamic property;
-(void)setProperty:(NSObject *)property
{
objc_setAssociatedObject(self, &UIB_PROPERTY_KEY, property, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSObject*)property
{
return (NSObject*)objc_getAssociatedObject(self, &UIB_PROPERTY_KEY);
}
#end
//Example usage
#import "UIButton+Property.h"
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button1.property = #"HELLO";
NSLog(#"Property %#", button1.property);
button1.property = nil;
NSLog(#"Property %#", button1.property);
A class extension is used to declare additional interface -- methods and properties -- whose implementation contract will be met within the class's primary #implementation.
Which is exactly why you can't add storage -- add ivars -- via a class extension. A class extension is an interface, no more, no less. #synthesize is what creates storage for #property declarations, but #synthesize of an #property can only appear in the #implementation of the class (whether explicitly or as a default behavior of the compiler).
Since you can't recompile the framework class, you can't add ivars to it.
#prashat's answer is one way to add storage to an existing class. However, going that route is generally undesirable; hanging state off of framework classes willy-nilly is a sign of poor design and will make your application significantly more difficult to maintain over time.
Far better to revisit your design, understand why you currently require attaching state to an object that can't directly contain it, and refactoring that requirement away.
The docs state:
Class extensions are like anonymous categories, except that the methods they declare must be implemented in the main #implementation block for the corresponding class.
When you use #property, it is roughly equivalent to declaring accessor methods. So this means you can only do such a thing if you are also the author of the "main" #implementation block of the class, which with UITableViewController, you are not.
Your only option here is Categories, which cannot add instance variables.
The docs link, and note the very last line of that page:
The implementation of the setValue: method must appear within the main #implementation block for the class (you cannot implement it in a category). If this is not the case, the compiler emits a warning that it cannot find a method definition for setValue:.