How do you use Lumberjack logging in Swift? - swift

I'm in the process of converting an Objective-C file which uses Lumberjack Logging into Swift. It seems to be mostly working, except for the part where I declare ddloglevel.
The Objective-C way to do this:
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_INFO;
#else
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#endif
The Swift way:
#if DEBUG
let ddLogLevel = LOG_LEVEL_INFO;
#else
let ddLogLevel = LOG_LEVEL_VERBOSE;
#endif
Except I'm this compile time error: Use of unresolved identifier 'LOG_LEVEL_INFO'
Why is this happening? How can I fix it?

You can use workaround. Instead of setting logLevel globally (possible only in Objective-C), you could set logging level to all Loggers explicitly.
Example:
class LoggerFactory {
#if DEBUG
static let defaultLogLevel: DDLogLevel = DDLogLevel.All
#else
static let defaultLogLevel: DDLogLevel = DDLogLevel.Info
#endif
static func initLogging() {
DDLog.addLogger(DDTTYLogger.sharedInstance(), withLevel: defaultLogLevel)
DDLog.addLogger(DDASLLogger.sharedInstance(), withLevel: defaultLogLevel)
}

Looking at the library source, LOG_LEVEL_INFO and LOG_LEVEL_VERBOSE are #define macros, which Swift does not automatically import. Swift only sees const's.
However, I think that your approach as a whole might not make sense - it looks like you're trying to assign a value to the global Objective-C constant ddLogLevel. Swift's let is not going to do that for you - it's a completely different namespace. This is on top of not being able to use Objective-C macros in Swift.
Your best bet is to leave an Objective-C file in your project (called, say, LoggingConfig.m that just contains:
#import "DDLog.h"
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_INFO;
#else
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#endif
The library you're using relies heavily on Objective-C features, so best to just use Objective-C to configure it.
Edit: Looking at this library in more detail (I'd never heard of it before), using CocoaLumberjack at all in Swift is probably not going to work out, as its primary API is a preprocessor macro named DDLog. I don't think it's a good match.

Related

How to implement Custom Log Levels in CocoaLumberJack from Swift?

I am using CocoaLumberjack for a Swift project. I would like to implement custom log levels/flags, as I would like to use 6 rather than the default 5, and would prefer different names.
The documentation for doing this is not helpful. It is only a solution for Objective-C.
The fact that DDLogFlag is defined as NS_OPTIONS means I actually could simply ignore the pre-defined values here, create my own constants, and just write some wrapping code to convert from one to the other.
However, DDLogLevel is defined as NS_ENUM, which means Swift won't be very happy with me trying to instantiate something to say 0b111111, which isn't an existing value in the enum. If it were an NS_OPTIONS, like DDLogFlag, I could just ignore the pre-existing definitions from the library and use whatever valid UInt values I wanted to.
As far as I can tell, I just have to write some Objective-C code to define my own replacements for DDLogLevel, DDLogFlag, and write a custom function to pass this in to and access these properties on DDLogMessage. But this feels bad.
How can I use my own custom logging levels in Swift with CocoaLumberjack?
This is indeed only possible in Objective-C right now - and there also only for the #define Log Macros. Even then I could imagine that the "modern" ObjC compiler will warn about the types that are passed to DDLogMessage.
The docs are indeed a bit outdated here and stem from a time where Objective-C was closer to C that it is to Swift nowadays... :-)
Nevertheless, in the end DDLogLevel and DDLogFlag are both stored as NSUInteger. Which means it can theoretically take any NSUInteger value (aka UInt in Swift).
To define your own levels, you would simply create an enum MyLogLevel: UInt { /*...*/ } and then write your own logging functions.
Those functions can actually forward to the existing functions:
extension DDLogFlag {
public static let fatal = DDLogFlag(rawValue: 0x0001)
public static let failure = DDLogFlag(rawValue: 0x0010)
}
public enum MyLogLevel: UInt {
case fatal = 0x0001
case failure = 0x0011
}
extension MyLogLevel {
public static var defaultLevel: MyLogLevel = .fatal
}
#inlinable
public func LogFatal(_ message: #autoclosure () -> Any,
level: MyLogLevel = .defaultLevel,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: unsafeBitCast(level, to: DDLogLevel.self), flag: .fatal, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}
The unsafeBitCast here works, because in the end it's just an UInt and _DDLogMessage does not switch over the level, but instead does a bit mask check against the flag.
Disclaimer: I'm a CocoaLumberjack maintainer myself.
We don't recommend using a custom log level in Swift. There's not much benefit from it and logging frameworks like swift-log also use predefined log levels.
However, I personally could also imagine declaring DDLogLevel with NS_OPTIONS instead of NS_ENUM. The OSLog Swift overlay also uses an extensible OSLogType.
If this is something you'd like to see, please open a PR so we can discuss it with the team. We need to be a bit careful with API compatibility, but like I said it's totally doable.
On a side-note: May I ask what you need custom levels for?

Custom TEST pre-processor macros in Swift 5

In Swift, I can do:
#if DEBUG
// code
#else
// code
#endif
but making custom macros as described in #ifdef replacement in the Swift language answers doesn't work for me.
How am I supposed to do this for Swift 5 / Xcode 12?
Edit:
My problem was that I assumed testing an application would trigger the macros in the application if I defined them in the AppTest target.
Putting -DYOURMACRO in Other Swift Flags under Swift Compiler - Custom Flags does indeed work.
The way to do what I wanted is either to put this flag in the application target whenever I want to run unit tests (really don't like this solution), or using launch arguments instead (not optimal but will do).
In C, Objective-C and Metal you have to use #ifdef DEBUG (Xcode 12.5):
#import <Foundation/Foundation.h>
#ifdef DEBUG
BOOL const DEBUG_BUILD = YES;
#else
BOOL const DEBUG_BUILD = NO;
#endif
But in Swift you have to use just the following syntax (Xcode 12.5):
import Foundation
#if DEBUG
let debugBuild: Bool = true
#else
let debugBuild: Bool = false
#endif
And why you said you couldn't use a custom pre-processor macros?
#if !RELEASE
import SceneKit
#endif
func loader() {
#if DEBUG
SceneKit.SCNSphere.init(radius: 0.1)
#endif
}

Objective-C interface Named "Category" cannot be imported into swift via the bridging header

The below swift code throws this error ('Category' is ambiguous for type lookup in this context)
// ctx is the UIContext, fetchCategories is an Obj-C function that queries CoreData
//for the Categories, the cast shouldn't even be necessary but swift will not
//recognize my obj-c interface
if let cats = self.fetchCategories(ctx) {
let array = cats as! [Category] //error happens on this line
return array
}
This is the implementation in my bridging header,
there are many other imports but I removed them for this post, so note that it is a fully functional bridging header for almost 40 other .h files
#ifndef AppDataRoom_Bridging_Header_h
#define AppDataRoom_Bridging_Header_h
#import "Category.h"
#endif /* AppDataRoom_Bridging_Header_h */
Below is the interface implementation
#interface Category : _Category <QLPreviewItem> {
UIImage *squareThumb;
}
I realize that changing the name of the Category class would be the best option since this is probably due to the naming convention being common, However when I change the code to this :
if let cats = self.fetchCategories(ctx) {
let array = cats as! [<ModuleName>.Category] //error happens on this line
return array
}
Is still says it is too ambiguous to lookup. Any ideas on other things I could try besides changing the interface name, because that really isn't an option and I will spare you all the reasons why.

Can't declare a let that uses a previous let?

So, in objective C, and C/C++, and .NET, and pretty much all other languages I've used, you can declare constants that can include previous constants, like
#define PI 3.14159
#define HALFPI (PI/2)
const CGFloat BOTTOM_BAR_HEIGHT = 200;
const CGFloat BOTTOMBARCONTENTS_DY = BOTTOM_BAR_HEIGHT/2;
But this doesn't seem to work in swift
let PI=3.14159
let HALF_PI=PI/2
This is a really useful pattern if you're trying to do things like (one among many examples) layout dimensions, or really any set of constants that are interdependent. Is there any way to achieve this pattern in swift, without declaring them as vars and setting them in an initializer function (which will double the size and reduce maintainability of some really lengthy code, incur whatever low-level penalties for using vars instead of lets, and ruin my first impression of swift)? Thanks.
Possibly You can use a NSObject Class like This:
import UIKit
class ViewController: NSObject {
static let PI = 3.14
static let HALF_PI = ViewController.PI/2
}
To achieve this you can Use Static
And use it to any View controller like
print(ViewController.PI)
print(ViewController.HALF_PI)
Hope this Helps.

Calling getsectiondata from Swift

This question and answer describe how to read data from a Mach-O section with Objective-C on modern OS X/macOS versions: Crash reading bytes from getsectbyname
The described answer works. I'm trying to implement the same thing with Swift. I can't make it work.
I have the following in "Other linker flags": -Wl,-sectcreate,__LOCALIZATIONS,__base,en.lproj/Localizable.strings,-segprot,__LOCALIZATIONS,r,r.
This Swift code gets me the a pointer to the embedded data, until I try to run the code outside Xcode and ASLR breaks it:
var size: UInt = 0
let _localizationSection = getsectdata(
"__LOCALIZATIONS",
"__base",
&size)
To get around the ASLR problem, according to the above question and answer, and based on my own testing, I should be using getsectiondata instead. It works great in Objective-C, but I'm having no luck in Swift. The following is the only thing I've managed to get past the compiler, but it returns nil:
var size: UInt = 0
var header = _mh_execute_header
let localizationSection = getsectiondata(
&header,
"__LOCALIZATIONS",
"__base",
&size)
Is taking a copy of _mh_execute_header the problem and is there any way to avoid it? I need an UnsafePointer<mach_header_64>, but using &_mh_execute_header as the first parameter to getsectiondata causes a compilation error.
I'm using Swift 3.0, and running my code on macOS 10.12.
The difference between the linked-to Objective-C code
void *ptr = getsectiondata(&_mh_execute_header, ...);
and your Swift translation
var header = _mh_execute_header
let localizationSection = getsectiondata(&header, ...)
is that the latter passes the address of a copy of the global
_mh_execute_header variable to the function, and apparently that
is not accepted. If you modify the Objective-C code to
struct mach_header_64 header = _mh_execute_header;
void *ptr = getsectiondata(&header, ...);
then it fails as well (and actually crashed in my test).
Now the problem is that _mh_execute_header is exposed to Swift
as a constant:
public let _mh_execute_header: mach_header_64
and one cannot take the address of a constant in Swift. One possible
workaround is to define
#import <mach-o/ldsyms.h>
static const struct mach_header_64 *mhExecHeaderPtr = &_mh_execute_header;
in the bridging header file, and then use it as
let localizationSection = getsectiondata(mhExecHeaderPtr, ...)
in Swift.
Another option is to lookup the symbol via dlopen/dlsym
import MachO
if let handle = dlopen(nil, RTLD_LAZY) {
defer { dlclose(handle) }
if let ptr = dlsym(handle, MH_EXECUTE_SYM) {
let mhExecHeaderPtr = ptr.assumingMemoryBound(to: mach_header_64.self)
var size: UInt = 0
let localizationSection = getsectiondata(
mhExecHeaderPtr,
"__LOCALIZATIONS",
"__base",
&size)
// ...
}
}