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; }());
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?
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).
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)];
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.