String substitution with variable constants [duplicate] - objective-c

This question already has answers here:
Objective C Macro append to string
(3 answers)
Closed 6 years ago.
In my project, I define my urls like such:
#define TERMSURL #"http://127.0.0.1:8000/terms/"
#define PRIVACYURL #"http://127.0.0.1:8000/privacy/"
...
Since the root url (http://127.0.0.1:8000/) is always the same, is there a way to set it as a constant, and then use string substitution for the remaining pieces?
For example, in the other files, I could do something like this:
NSString *devBaseUrl = #"http://127.0.0.1:8000/";
NSString *url1 = [NSString stringWithFormat:#"%#terms/", devBaseUrl];
Is there a way to do that for my current approach?

shared.h
#define TERMSURL #"http://127.0.0.1:8000/terms/"
#define PRIVACYURL #"http://127.0.0.1:8000/privacy/"
#define URL_BASE #"http://127.0.0.1:8000/"
yourClass.m
NSString * stringUrlBase = URL_BASE;
NSString *url1 = [NSString stringWithFormat:#"%#terms/", stringUrlBase];

Sure, you can do that. I have however seen both a #define and an NSString const * const being used before. Defines are easier, and you're probably not going to save that much memory by having constants instead of individual immutable instances of NSString all over the place.
Some advice is to think about how you export the NSString constants. You'll probably want EXTERN_PRIVATE instead of EXTERN, but my sample code will allow all clients of your header to read the string constants you've declared therein.
What you can do:
Create a new .m/.c file with a header in Xcode
In the .m/.c file, declare and initialise your constants
Export the constant as necessary so other compilation units can access it
constants.h
#ifndef constants_h
#define constants_h
// Export the symbol to clients of the static object (library)
#define EXTERN extern __attribute__((visibility("default")))
// Export the symbol, but make it available only within the static object
#define EXTERN_PRIVATE extern __attribute__((visibility("hidden")))
// Make the class symbol available to clients
#define EXTERN_CLASS __attribute__((visibility("default")))
// Hide the class symbol from clients
#define EXTERN_CLASS_PRIVATE __attribute__((visibility("hidden")))
#define INLINE static inline
#import <Foundation/Foundation.h>
EXTERN NSString const * _Nonnull const devBaseUrl;
#endif /* constants_h */
constants.m
#include "constants.h"
NSString const * _Nonnull const devBaseUrl = #"http://127.0.0.1:8000/";
main.m
#import <Foundation/Foundation.h>
#import "constants.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSLog(#"Constant value: %#", devBaseUrl);
// Prints: Constant value: http://127.0.0.1:8000/
}
return 0;
}

Related

How should I handle logs in an Objective-C library?

I’m writing an Objective-C library and in some places I’d like to log some information. Using NSLog is not ideal since it’s not configurable and has neither level support nor tag support. CocoaLumberjack and NSLogger are both popular logging libraries supporting levels and contexts/tags but I’d prefer not to depend on a third party logging library.
How can I produce logs in a configurable way that doesn’t force a specific logging library upon my users?
TL;DR Expose a log handler block in your API.
Here is a suggestion to make logging configurable very easily with a logger class as part of your public API. Let’s call it MYLibraryLogger:
// MYLibraryLogger.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, MYLogLevel) {
MYLogLevelError = 0,
MYLogLevelWarning = 1,
MYLogLevelInfo = 2,
MYLogLevelDebug = 3,
MYLogLevelVerbose = 4,
};
#interface MYLibraryLogger : NSObject
+ (void) setLogHandler:(void (^)(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line))logHandler;
#end
This class has a single method that allows client to register a log handler block. This makes it trivial for a client to implement logging with their favorite library. Here is how a client would use it with NSLogger:
[MYLibraryLogger setLogHandler:^(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) {
LogMessageRawF(file, (int)line, function, #"MYLibrary", (int)level, message());
}];
or with CocoaLumberjack:
[MYLibraryLogger setLogHandler:^(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) {
// The `MYLogLevel` enum matches the `DDLogFlag` options from DDLog.h when shifted
[DDLog log:YES message:message() level:ddLogLevel flag:(1 << level) context:MYLibraryLumberjackContext file:file function:function line:line tag:nil];
}];
Here is an implementation of MYLibraryLogger with a default log handler that only logs errors and warnings:
// MYLibraryLogger.m
#import "MYLibraryLogger.h"
static void (^LogHandler)(NSString * (^)(void), MYLogLevel, const char *, const char *, NSUInteger) = ^(NSString *(^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line)
{
if (level == MYLogLevelError || level == MYLogLevelWarning)
NSLog(#"[MYLibrary] %#", message());
};
#implementation MYLibraryLogger
+ (void) setLogHandler:(void (^)(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line))logHandler
{
LogHandler = logHandler;
}
+ (void) logMessage:(NSString * (^)(void))message level:(MYLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line
{
if (LogHandler)
LogHandler(message, level, file, function, line);
}
#end
The last missing piece for this solution to work is a set of macros for you to use through your library.
// MYLibraryLogger+Private.h
#import <Foundation/Foundation.h>
#import "MYLibraryLogger.h"
#interface MYLibraryLogger ()
+ (void) logMessage:(NSString * (^)(void))message level:(MYLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line;
#end
#define MYLibraryLog(_level, _message) [MYLibraryLogger logMessage:(_message) level:(_level) file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__]
#define MYLibraryLogError(format, ...) MYLibraryLog(MYLogLevelError, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define MYLibraryLogWarning(format, ...) MYLibraryLog(MYLogLevelWarning, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define MYLibraryLogInfo(format, ...) MYLibraryLog(MYLogLevelInfo, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define MYLibraryLogDebug(format, ...) MYLibraryLog(MYLogLevelDebug, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
#define MYLibraryLogVerbose(format, ...) MYLibraryLog(MYLogLevelVerbose, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
Then you just use it like this inside your library:
MYLibraryLogError(#"Operation finished with error: %#", error);
Notice how the log message is a block returning a string instead of just a string. This way you can potentially avoid expensive computations if the defined log handler decides not to evaluate the message (e.g. based on the log level as in the default log handler above). This lets you write one-liner logs with potentially costly log messages to compute with no performance hit if the log is discarded, for example:
MYLibraryLogDebug(#"Object: %#", ^{ return object.debugDescription; }());

Convert percentage (i.e., %XY) code into utf-8 code with Objective C

I have my own private protocol handler "open://" written in Objective-C on Mac.
The issue is that the open://노자1강 in the web browser is changed into open://%EB%85%B8%EC%9E%901%EA%B0%95 when it's fed into the handler, and I need to transform that into the UTF-8 character back.
How can I do the code transform with Objective-C?
This is the code that does the transformation:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSString *str = #"open:///%EB%85%B8%EC%9E%901%EA%B0%95/index.md";
str=[str stringByReplacingOccurrencesOfString:#"%u" withString:#"\\u"];
NSString *convertedStr=[str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"converted string is %# \n",convertedStr);
}
}
The hint is from IOS Convert URL string to NSString?

objective-c constants.h static const

I googled a lot of similar questions, but none of them had a really good answer about what i needed.
Im wondering about the best way to write constants in some .h file.
my constants file is just a clear file with no imports, just clear .h file. I import it in prefix file.
when i use
static NSInteger someInteger = 1;
or
static BOOl someBool = YES;
The compiler compiles okay but gives me a warning that this variable is unused even though im using it multiple times in different classes.
But if i use
static NSString* someString = #"";
there are not any warnings.
also if i use
const NSInteger someInteger = 1;
Compiler compiles okay for a real device, but when running on a simulator it does not compile with an error duplicate symbols for architecture i386
also what is the difference between
const NSString* someString = #"";
const NSInteger someInteger = 1;
and
NSString* const someString = #"";
NSInteger const someInteger = 1;
I ended up using static const NSInteger someInteger =1;, but i wonder if this is a right option.
So really my question is: what words should i use to successfully create a constants.h file?
For all types (both primitive or otherwise) then you need to provide a single implementation of it somewhere:
constants.h:
extern const int someValue;
extern NSString * const someString;
constants.m:
const NSInteger someValue = 1;
NSString * const someString = #"Some string";
You never want to use static variables in header files as you will end up with multiple copies of the "constant" in every implementation file that includes that header (they may not upset the linker, and cause a link error, but they are there).

Is there a way to allocate an Objective-C NSArray with objects contained in a C-style macro?

What I'd like to do is something like this:
NSArray *someArray = [[NSArray alloc] initWithObjects: C_MACRO, nil];
With the C_MACRO part being an outlying file that uses a #define to list some number of NSStrings, so when I need to change the string objects that populate the array I can conveniently do in one file. So far, it seems this is impossible. But then again, I'm a novice.
Anyone care to enlighten me?
No problem doing this... A macro is just text-replacement.
/* foo.h */
#define C_MACRO #"foo", #"bar"
/* bar.m */
#import "foo.h"
NSArray * someArray = [ [ NSArray alloc ] initWithObjects: C_MACRO, nil ];
Note you can use macros inside another macro. So
#define FOO_STR #"foo"
#define BAR_STR #"bar"
#define STR_LIST FOO_STR, BAR_STR
Another way is to have your strings allocated in a .m file, and declared as extern in a public header file. Handy if unique instances of the same object has to be shared.
/* foo.h */
extern NSString * const fooStr;
extern NSString * const barStr;
/* foo.m */
NSString * const fooStr = #"foo";
NSString * const BarStr = #"bar";
Why does it have to be a macro? The following works just as well:
NSString *words[] = {#"Hello", #"World!"}; // this can be a global
...
NSArray *array = [NSArray arrayWithObjects:words count:sizeof(words)/sizeof(*words)];

Enumerations in Objective-C

I'm making the jump from Java to Objective-C. I'm wondering if there is a concept analogous to Java's enums, that supports implementations of methods. I realize Objective-C has plain old C enumerations, but those are really just ints.
I'm looking to prevent switch-yards -- Java enums would be perfect.
Objective-C is just C with some additional markup for objects, no new types have been added.
That means, no.
For mutual exclusive flags, Apple uses strings.
header.h
extern NSString * const kNSSomeFlag;
extern NSString * const kNSOtherFlag;
extern NSString * const kNSThirdFlag ;
code.m
NSString * const kNSSomeFlag = #"kNSSomeFlag";
NSString * const kNSOtherFlag = #"kNSOtherFlag";
NSString * const kNSThirdFlag = #"kNSThirdFlag";
…
void myFunction(NSString *flag)
{
if (flag == kNSSomeFlag) {
// the code
}
}
An example of this can be found in the NSDistributedNotificationCenter.