"extern const" vs "extern" only - objective-c

I've seen 2 ways of creating global variables, what's the difference, and when do you use each?
//.h
extern NSString * const MyConstant;
//.m
NSString * const MyConstant = #"MyConstant";
and
//.h
extern NSString *MyConstant;
//.m
NSString *MyConstant = #"MyConstant";

the former is ideal for constants because the string it points to cannot be changed:
//.h
extern NSString * const MyConstant;
//.m
NSString * const MyConstant = #"MyConstant";
...
MyConstant = #"Bad Stuff"; // << YAY! compiler error
and
//.h
extern NSString *MyConstant;
//.m
NSString *MyConstant = #"MyConstant";
...
MyConstant = #"Bad Stuff"; // << NO compiler error =\
in short, use const (the former) by default. the compiler will let you know if you try to change it down the road - then you can decide if it was a mistake on your behalf, or if the object it points to may change. it's a nice safeguard which saves a lot of bugs/headscratching.
the other variation is for a value:
extern int MyInteger; // << value may be changed anytime
extern const int MyInteger; // << a proper constant

Related

Using constants in Objective-C results in errror

In my application I want to use some constant strings.
I've created Constant.h and put some values inside:
extern NSString * const CAR_PLAY;
extern NSString * const CONNECTED_TO_CAR_PLAY;
extern NSString * const DISCONNECTED_FROM_CAR_PLAY;
Then, I've created Constants.m file and put the actual value inside:
NSString * const CAR_PLAY = #"CarPlay";
NSString * const CONNECTED_TO_CAR_PLAY = #"Conencted_To_CarPlay";
NSString * const DISCONNECTED_FROM_CAR_PLAY = #"Disconnected_from_CarPLay";
In my AppDelegate I imported Constants.h:
#import "Constants.h"
When I tried to use one of the values for example:
[string isEqualToString:CONNECTED_TO_CAR_PLAY];
I got an error saying Undefined symbol: _CONNECTED_TO_CAR_PLAY.
What is wrong with my implementation? What is the correct way to use constatns?
This seems to work for me:
% cc -framework Foundation -o constants constants.m appdelegate.m
% cat constants.h
extern NSString * const CONNECTED_TO_CAR_PLAY;
% cat constants.m
#import <Foundation/Foundation.h>
NSString * const CONNECTED_TO_CAR_PLAY = #"Conencted_To_CarPlay";
% cat appdelegate.m
#import <Foundation/Foundation.h>
#import "constants.h"
int main() {
NSLog(#"now: %s", [CONNECTED_TO_CAR_PLAY UTF8String]);
return 0;
}
% ./constants
2022-09-14 18:31:58.518 constants[94914:1189470] now: Conencted_To_CarPlay

ObjectC-Why can't I get the properties correctly using the class_copyPropertyList function?

macOS 11.5.2
Xcode 13.2.1
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <iostream>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"NSString");
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
const char* name = property_getName(properties[i]);
std::cout << name << std::endl;
}
free(properties);
}
return 0;
}
I will take some snippets of the output:
hash
superclass
description
debugDescription
hash
superclass
description
debugDescription
vertexID
sha224
NS_isSourceOver
hash
superclass
description
debugDescription
...
From the output, we can find that properties such as hash, description, superclass, etc. will appear repeatedly several times, while some properties (such as UTF8String) do not appear in the result list.
How should I get the list of properties correctly?
I would appreciate it.
The reason you're not seeing UTF8String come up as a property is that it's not declared as a property in the main declaration of NSString, but rather in a category. On macOS 12.2.1/Xcode 13.2.1, the declaration of NSString boils down to this:
#interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
#property (readonly) NSUInteger length;
- (unichar)characterAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
#end
All other properties and methods on NSString are declared in categories immediately afterwards:
#interface NSString (NSStringExtensionMethods)
#pragma mark *** Substrings ***
/* To avoid breaking up character sequences such as Emoji, you can do:
[str substringFromIndex:[str rangeOfComposedCharacterSequenceAtIndex:index].location]
[str substringToIndex:NSMaxRange([str rangeOfComposedCharacterSequenceAtIndex:index])]
[str substringWithRange:[str rangeOfComposedCharacterSequencesForRange:range]
*/
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;
// ...
#property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER; // Convenience to return null-terminated UTF8 representation
// ...
#end
When a property is declared in a category on a type like this, it doesn't get emitted as an actual Obj-C property because categories can only add methods to classes, and not instance variables. When a category declares a property on a type, it must be backed by a method and not a traditional property.
You can see this with a custom class, too — on my machine,
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface MyClass: NSObject
#property (nullable, readonly) const char *direct_UTF8String NS_RETURNS_INNER_POINTER;
#end
#interface MyClass (Extensions)
#property (nullable, readonly) const char *category_UTF8String NS_RETURNS_INNER_POINTER;
#end
#implementation MyClass
- (const char *)direct_UTF8String {
return "Hello, world!";
}
- (const char *)category_UTF8String {
return "Hi there!";
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"MyClass");
printf("%s properties:\n", class_getName(clazz));
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
printf("%s\n", property_getName(properties[i]));
}
free(properties);
puts("-----------------------------------------------");
printf("%s methods:\n", class_getName(clazz));
Method *methods = class_copyMethodList(clazz, &count);
for (uint32_t i = 0; i < count; i++) {
SEL name = method_getName(methods[i]);
printf("%s\n", sel_getName(name));
}
free(methods);
}
return 0;
}
outputs
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
category_UTF8String
If you remove the actual implementations of the *UTF8String methods from the class, the property remains declared, but the category method disappears (because it doesn't actually have a synthesized implementation because of how categories work):
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
As for how to adjust to this: it depends on what purpose you're trying to fetch properties for, and why you might need UTF8String specifically.
NSString declares in its interface it implements methods, but it does not actually implement them, that is why when you print at runtime a list of the its methods it does not print what you expect.
The methods are implemented by other private classes, and when you initialize a new instance of NSString, instead of getting an instance of NSString you get an instance of that private class that have the actual implementation.
You can see that by printing the class type of a string, the following prints NSCFString or NSTaggedPointerString, not NSString:
NSString* aString = [NSString stringWithFormat: #"something"];
NSLog(#"%#", [aString class]);
And this prints __NSCFConstantString:
NSLog(#"%#", [#"a constant string" class]);
This pattern is called a class cluster pattern.
If you modify to dump the methods of the NSCFString you will get a "redactedDescription", it seems you are prevented to query these classes.

Static var, method or singleton?

i was wondering how to access "static" vars from a model from different ViewControllers.
Should I go for:
static vars (if yes, how?)
static method
singleton + method
singleton + variable (custom getters)
#import "CategoryModel.h"
#implementation CategoryModel
-(NSArray*) allSelected {
return [[NSArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:SELECTEDCATEGORIES_DEFAULTSKEY]];
}
-(NSString*) allSelectedAsUrlParams {
NSMutableString *categoryList = [NSMutableString string];
for (NSArray *category in self.allSelected) {
[categoryList appendString:[category valueForKey:#"value"]];
if(![[self.allSelected lastObject] isEqual:category]) {
[categoryList appendString:#","];
}
}
return categoryList;
}
#end
*Edit (working) *
// header
#import <Foundation/Foundation.h>
#interface CategoryModel : NSObject
+ (NSString*)allSelectedAsUrlParams;
#end
// implementation
#import "CategoryModel.h"
#implementation CategoryModel
+ (NSString*)allSelectedAsUrlParams {
return #"somethingGreat";
}
#end
// How to use
CategoryModel.allSelectedAsUrlParams
This is really going to be a matter of architectural preference, but if more than one class is going to need the static vars, this is one approach:
Constants.h
extern BOOL const TEST_MODE;
extern NSString * const SOME_STRING;
Constants.m
BOOL const TEST_MODE = YES;
NSString * const SOME_STRING = #"SomeString";
As an alternative, you can just add the extern NSString * const SOME_STRING; to the header of class that it is specific to (and the corresponding NSString * const SOME_STRING = #"SomeString"; in the .m) and then call that var directly by importing the header of the class with the constant you need and using SOME_STRING to get at it.

Referencing a static NSString * const from another class

In class A I have this:
static NSString * const kMyConstant = #"my constant string";
How can I reference this from class B?
You should extern your string in the header, and then define the string in the implementation.
//ClassA.h
extern NSString * const kMyConstant;
//ClassA.m
NSString * const kMyConstant = #"my constant string";
//ClassB.h/m
#import "ClassA.h"
...
NSLog(#"String Constant: %#", kMyConstant);
You need to remove the static -- that specifies that kMyConstant is only visible in files linked with this one.
Then, declare (as opposed to defining) the string in Class A's header:
extern NSString * const kMyConstant;
and import that header wherever you want to use this string. The extern declaration says that there exists an NSString * const by the name kMyConstant whose storage is created in some other place.
If the static definition is already in the header, you need to move it elsewhere (usually the implementation file). Things can only be defined once, and if you try to import a file which defines a variable, you'll get a linker error.
If it's static, you can't (that's what the static keyword is for).
If you simply declare it as a global variable, however, you can do something like this:
// ClassA.m
NSString *const str = #"Foo";
// ClassB.m
extern NSString *const str;
NSLog(#"str is: %#", str);

"sending 'const NSString *' to parameter of type 'NSString *' discards qualifiers" warning

I have Constants NSString, that I want to call like:
[newString isEqualToString:CONSTANT_STRING];
Any wrong code here?
I got this warning:
sending 'const NSString *' to parameter of type 'NSString *' discards qualifiers
How should these be declared?
You should declare your constant string as follows:
NSString * const kSomeConstantString = #""; // constant pointer
instead of:
const NSString * kSomeConstantString = #""; // pointer to constant
// equivalent to
NSString const * kSomeConstantString = #"";
The former is a constant pointer to an NSString object, while the latter is a pointer to a constant NSString object.
Using a NSString * const prevents you from reassigning kSomeConstantString to point to a different NSString object.
The method isEqualToString: expects an argument of type NSString *. If you pass a pointer to a constant string (const NSString *), you are passing something different than it expects.
Besides, NSString objects are already immutable, so making them const NSString is meaningless.
just to put all on one place which found on various post on stackoverflow and works for me , #define is bad because you cannot benefit from variable types, basically the compiler replaces all occurrence when compiles (import Constants.h whenever you need) :
// Constants.h
#import <Foundation/Foundation.h>
#interface Constants : NSObject
extern NSString *APP_STATE_LOGGED_IN;
extern NSString *APP_STATE_LOGGED_OUT;
#end
// Constants.m
#import <Foundation/Foundation.h>
#import "Constants.h"
#implementation Constants
NSString *APP_STATE_LOGGED_IN = #"APP_STATE_LOGGED_IN";
NSString *APP_STATE_LOGGED_OUT = #"APP_STATE_LOGGED_OUT";
#end
spare few minutes to read this. A goodread on pointers hell on constants and vice-versa.
http://c-faq.com/decl/spiral.anderson.html