Reputation: 276
I have the following header and implementation files where NSURLMyObject
is a category of NSURL
, and contains a readonly class property myString
:
@interface NSURL (NSURLMyClass)
@property(class, readonly) NSString* myString;
@end
@implementation MyObject
static NSString* _myString;
+ (NSString*) myString
{
if (_myString == nil) {
_myString = @"Hello";
}
return _myString;
}
@end
I have seen other threads where either:
readwrite
in the implementation and then synthesisedI believe such class readonly properties are roughly equivalent to static constexpr
member variables in modern C++, where initialising them is a one-liner in the header:
#include <string>
using std::literals;
struct MyClass
{
static constexpr auto myString = "Hello"s;
};
// access with, for example, `std::println("{}", MyClass::myString);`
Now on to my questions:
Can MyClass.myString
be initialised more compactly, similar to C++ without having to declare the static
underscore-prefixed variable, the getter, and the test for nullity?
Should I declare an + (instancetype) init
method and then initialise myString
inside it? Would this not override the provided +init
method that NSURL
already has?
Is there some other way altogether, i.e. subclass rather than category? I'm not intimately familiar with Objective-C semantics so I'm not sure if an NSURL*
returned by some Foundation class can be straightforwardly cast to MyClass*
where @interface MyClass : NSURL
.
Upvotes: 1
Views: 34
Reputation: 29918
tl;dr:
If you don't need myString
to be associated with the NSURL
class specifically, and myString
is a string literal, the most succinct way to express this would be
// MyString.h
extern NSString * const myString;
// MyString.mm
NSString * const myString = @"Hello";
// OtherFile.mm
NSString *someThing = myString; // accessed like a global var
If you do want or need this to be associated with NSURL
and myString
is a string literal, you can avoid the intermediate storage:
+ (NSString *)myString {
return @"Hello";
}
If myString
is not a string literal, or would otherwise need to be computed, then the existing pattern you've seen with _myString
storage and initialization is the most common and succinct way to do it
I believe such class readonly properties are roughly equivalent to
static constexpr
member variables in modern C++, where initialising them is a one-liner in the header:
This isn't quite right. In Objective-C, properties are a way to succinctly synthesize methods, and offer an alternative syntax for calling those methods. In general, a property called foo
:
foo
getter method and a setFoo:
setter method, whichx.foo
≍ [x foo]
and x.foo = f
≍ [x setFoo:f]
This is the same for both instance properties and class properties — though the storage semantics are a little different for class properties by default.
The approximate C++ equivalent to your example would be generating something like
#include <string>
using std::literals;
struct MyClass
{
static constexpr auto myString() {
return "Hello"s;
}
};
and the very rough equivalent to your Objective-C code would be something like
struct MyClass
{
static auto myString() {
if (!myString_) {
myString_ = "Hello";
}
return *myString_;
}
private:
static const char *myString_;
};
Depending on what you're actually trying to do here, this can both be wasteful, and unnecessary.
Can MyClass.myString be initialised more compactly, similar to C++ without having to declare the static underscore-prefixed variable, the getter, and the test for nullity?
Depending on what you're trying to do, you may be able to avoid a property altogether. For example, does myString
necessarily need to be associated with NSURL
? If not, this is perfectly valid in Objective-C (and the common way to declare string constants):
// MyString.h
extern NSString * const myString;
// MyString.mm
NSString * const myString = @"Hello";
If you do want to reference this data via NSURL.myString
, and if the string is a constant, then you can also avoid having to store it altogether:
+ (NSString *)myString
{
return @"Hello";
}
Literal NSString
s are already stored in your binary in a constant location; if myString
never changes, there's no need to add static
storage and store a pointer to it there. If the value of myString
were computed then it would make sense to store it to avoid recreating the value on every call, but that doesn't sound like what you're looking for.
With more details, it'll be possible to give a more specific recommendation here.
Should I declare an + (instancetype) init method and then initialise myString inside it? Would this not override the provided +init method that NSURL already has?
This would be atypical:
+init
would declare a method named init
on your class, and init
is typically only called on instances of a type to initialize them; calling [NSURL init]
would be very out of place+init
and try to access the value?Is there some other way altogether, i.e. subclass rather than category? I'm not intimately familiar with Objective-C semantics so I'm not sure if an NSURL* returned by some Foundation class can be straightforwardly cast to MyClass* where @interface MyClass : NSURL.
This is likely straying even further from what you're looking for — you could subclass NSURL
, but subclassing is a mechanism for affecting existing behavior on subtypes, but this doesn't win you anything here. You're not looking to customize anything about NSURL
or its instances, just add an additional bit of data to the type.
Since you're looking to add a class method, instances wouldn't come into play here: if a method returns an NSURL
object to you, you won't be able to call myString
on it anyway, because myString
belongs to the class, not the instance.
Upvotes: 1