I have a Log function I use in various apps. Its convenient for this to also make Crashlytics logging calls since I use it throughout the app.
However, not every app uses Crashlytics. In Objective C you could handle this with preprocessor conditions.
How would one handle this in code? I think there are ways to make the function conditional perhaps. But how would I optionally or weak import Crashlytics?
import Foundation
//import Crashlytics
/// Debug Log: Log useful information while automatically hiding after release.
func DLog<T>(message: T, filename: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__) {
// CLSLogv("\(NSString(string: filename).lastPathComponent).\(function) line \(line) $ \(message)", getVaList([]))
print("[\(NSString(string: filename).lastPathComponent) \(function) L\(line)] \(message)")
}
You can do this now in Swift 4.1 with the new canImport() directive. This is the Swift Evolution proposal that describes how it works: https://github.com/apple/swift-evolution/blob/master/proposals/0075-import-test.md
So you could do:
#if canImport(Crashlytics)
func dLog() {
// use Crashlytics symbols
}
#endif
I would do it a different way in Swift. I would make my log function extensible. I would have an array of log closures that do actual logging and my dlog function would invoke all of them e.g.
private var logFunctions: [(String) -> ()] = [ { print($0) } ]
func dlog(message: String, filename: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__)
{
let logMessage = "[\(NSString(string: filename).lastPathComponent) \(function) L\(line)] \(message)"
for log in logFunctions
{
log(logMessage)
}
}
dlog("Hi", filename: "file", function: "function", line: 1)
print("---")
logFunctions.append{ print("New print: " + $0) }
dlog("Hi", filename: "file", function: "function", line: 2)
Output is
[file function L1] Hi
---
[file function L2] Hi
New print: [file function L2] Hi
So in any application that supports Crashlytics, in your application start up code, you add the crashlytics logger to your array of loggers i.e.
import Crashlytics // Only needed in the Swift file with app start up code
// ...
logFunctions.append{ CLSLogv($0, getVaList([])) }
Of course you should encapsulate all of the above in a class or something.
This is possible in Swift even though the Swift compiler does not include a preprocessor.
import Foundation
#ifdef DEBUG
import Crashlytics
#endif
/// Debug Log: Log useful information while automatically hiding after release.
func DLog<T>(message: T, filename: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__) {
#ifdef DEBUG
CLSLogv("\(NSString(string: filename).lastPathComponent).\(function) line \(line) $ \(message)", getVaList([]))
#else
print("[\(NSString(string: filename).lastPathComponent) \(function) L\(line)] \(message)")
#endif
}
Now, the following code is untested and MAY need some tweaking - but it can help if you want to clean up your code! #transparent will inline the body of the code, by the way.
import Foundation
#ifdef DEBUG
import Crashlytics
#transparent printfn(item: String) {
CLSLogv(item, getVaList([])
}
#else
let printfn = println
#endif
/// Debug Log: Log useful information while automatically hiding after release.
func DLog<T>(message: T, filename: String = __FILE__, function: String = __FUNCTION__, line: Int = __LINE__) {
printfn("\(NSString(string: filename).lastPathComponent).\(function) line \(line) $ \(message)")
}
Please note: you must set the "DEBUG" symbol as it is not predefined. Set it in the "Swift compiler - Custom Flags" section of the compiler, "Other Swift Flags" line. You cand efine the DEBUG symbol with the -D DEBUG entry.
Hope I was able to help!
Related
For the code below:
let services: [MyServices] = [
MyService(),
#if DEBUG
DebugService(),
#endif
]
I get compiler error:
expression failed to parse:
error: MyPlayground.playground:375:5: error: expected expression in container literal
#if DEBUG
^
error: MyPlayground.playground:376:19: error: consecutive statements on a line must be separated by ';'
DebugService(),
^
;
error: MyPlayground.playground:376:19: error: expected expression
DebugService(),
^
error: MyPlayground.playground:378:1: error: expected expression
]
But the same code works in Objective-C
NSArray *array = #[
#"11",
#if DEBUG
#"debug",
#endif
#"22"
];
Is this a compiler bug or expected behaviour ? Thank you.
Generally the body of each clause in an #if ... #else ... #endif compiler directive must surround complete statements.
But with the implementation of SE-308 #if for postfix member expressions in Swift 5.5 this directive has been expanded to be able to surround postfix member expressions.
So with a custom extension
extension Array {
func appending(_ e: Element) -> Self {
return self + [e]
}
}
one can conditionally initialize an array like this:
let services = [
MyService() ]
#if DEBUG
.appending(DebugService())
#endif
The #if / #endif constructs are not preprocessor macros like in C. They are compiler directives. They are part of the language.
In C, it runs the preprocessor on your source file, which results in a modified source file. That modified source file gets compiled.
Not so in Swift. In Swift, the compiler parses the source file and attempts to compile it.
Edit:
To understand why what you tried doesn't work, think of the #if / #endif like a regular if statement. Would the following compile?
let debug = true
let services: [MyServices] = [
MyService(),
if debug {
DebugService(),
}
]
(Answer: No. That's not legal. You can't insert an if statement in the middle of the parameters of an array initializer. Same goes for an #if / #elseif statement.)
You can’t use an #if / #endif block to assemble fragments of a statement together.
You would have to create complete statements in both cases:
#if debug
let services: [MyServices] = [
MyService(),
DebugService(),
]
#else
let services: [MyServices] = [
MyService()
]
#endif
(Or you could make services a var, and append DebugServices() to it inside an #if / #endif.)
Edit #2:
Better yet, use a "postfix" .appending() as in #MartinR's excellent answer.
Swift 5.5 (Using Swift Playgrounds)
import SwiftUI
struct ContentView: View {
let service: MyService = {
#if DEBUG
return DebugService()
#else
return MyService()
#endif
}()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
}
}
class MyService: MyServices {
func resultHandler() {
print("RELEASE")
}
}
#if DEBUG
class DebugService: MyServices {
func resultHandler() {
print("DEBUG")
}
}
#endif
protocol MyServices {
func resultHandler()
}
Please help me with the following issue. I am using the swift argument parser library https://github.com/apple/swift-argument-parser
Suppose I have this swift code in some swift file:
#objc public class Math: NSObject, ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "A utility for performing maths.",
subcommands: [Add.self],
defaultSubcommand: Add.self)
}
extension Math {
struct Add: ParsableCommand {
static var configuration = CommandConfiguration(abstract: "Print the sum of the values.")
mutating func run() {
print("0")
}
}
}
` and I want to call to an objective-c++ function named "func" with an argument which is of type Math.Add like so from main.swift file:
var status: Int32 = 0
do {
// try to parse application arguments
var args = try Math.parseAsRoot()
switch args {
case let args as Math.Add:
status = func(args)
default:
try args.run()
}
} catch {
//Some code...
}
What should be the signature of the function and how the bridging-header files should look like? I made the signature be:
extern "C" int func(Math.Add *args);
And my bridging header is like so:
#ifndef __BRIDGING_HEADER_H__
#define __BRIDGING_HEADER_H__
#class Math;
#ifdef __cplusplus
extern "C" {
#endif
int func(Math.Add *args);
#ifdef __cplusplus
}
#endif
#endif /* __BRIDGING_HEADER_H__ */
but is doesn't work and the bridging header doesn't compile (Xcode writes the error: Interface type 'Math' cannot be passed by value; did you forget * in 'Math'?
Objective-C(++) doesn't support nested types, what you can do if you want to keep the nested structure on Swift, is to export a non-nested type for Objective-C:
extension Math {
#objc(Math_Add) // or MathAdd, or whatever name you like
struct Add: ParsableCommand {
, which can then be referenced in your header:
int func(Math_Add *args);
As a side note, I would also change the name of the func function to something that doesn't collide with Swift keywords. Even if you'll be able to call it, it will be confusing for other readers of your code.
I have a macOS app in swift that crashes only when archived (not run from XCode).
It crashes on the following code:
private static func myFunc(originalValue: String) {
DLog("Starting myFunc on: '\(originalValue)'") // CRASHES when app is exported
}
Here is the DLog function (when exported, the DEBUG flag is not set, so it will not execute the NSLog):
func DLog(_ message: String, function: String = #function) {
#if DEBUG
NSLog("%#, %#", function, message)
#endif
}
The originalValue that makes the app crash is a String containing basic characters (letters or numbers), no %# or other format string stuff.
Now the funniest part, if I print (using Swift.print) originalValue before calling DLog, it won't crash:
private static func myFunc(originalValue: String) {
print("Starting myFunc on: '\(originalValue)'") // prevents the crash
DLog("Starting myFunc on: '\(originalValue)'") // DOES NOT crash because of the print before
}
Any logical explanation to this strange behavior?
Thanks!
With this code:
func externalFunc() {
println("How can I know which object/class is calling me?")
}
class Test {
func callExternalFunc() {
externalFunc()
}
}
In the Objective-C runtime objc_msgSend passes two hidden parameters to every message we send. They are self and _cmd. (Source)
In the above example, is there any way to know who is calling externalFunc?
I'm not sure if there is a way to obtain this automatically, but you can get this info if you add a default param of type String to the function and set it to #function.
For example...
func externalFunc(callingFunctionName: String = #function) {
println("Calling Function: \(callingFunctionName)")
}
Then you would call it without the added default param...
let test = Test()
test.callExternalFunc()
And it would print the following...
"Calling Function: callExternalFunc()"
If you are willing to modify the method signature you could do something like below:
func externalFunc(file: String = #file, line: Int = #line) {
print("calling File:\(file) from Line:\(line)")
}
From apple's swift blog
Swift borrows a clever feature from the D language: these identifiers
(__FILE__ & __LINE__ ) expand to the location of the caller when
evaluated in a default argument list.
Note that __FILE__ and __LINE__ have been depreciated in Swift 2.2 and have been removed in Swift 3. They are replaced by #file, and #line.
Here's a great utility class I found on github:
https://github.com/nurun/swiftcallstacktrace
Use like this:
let callingMethodInfo = CallStackAnalyser.getCallingClassAndMethodInScope(false)
if let callingMethodInfo = callingMethodInfo {
NSLog("class: %#", callingMethodInfo.0)
NSLog("method: %#", callingMethodInfo.1)
}
In your question you are mentioning self and _cmd.
self is accessible in Swift exactly in the same way as in Obj-C (which is logical).
_cmd (selector of the current method) is not accessible. There is no reason for it to be accessible, Swift doesn't use selectors outside Obj-C contexts (in pure Swift you cannot call selectors dynamically). The only use case for it is to print the name of the current function for debugging purposes. The same can be achieved in Obj-C (or C) using __FUNCTION__ macro. The same can be achieved in Swift:
func getCurrentFunctionName(functionName: StaticString = #function) -> String {
return String(functionName)
}
func externalFunc() {
print("Function name: \(getCurrentFunctionName())") // prints "externalFunc"
}
Note that in your example externalFunc is a function, not a method. Even in Obj-C neither self or _cmd wouldn't be available for it.
If you want to know who has called your method (and I really suppose you want to know it for debugging purposes), then you can inspect your call stack:
func externalFunc() {
let stackSymbols = NSThread.callStackSymbols()
stackSymbols.forEach {
let components = $0.stringByReplacingOccurrencesOfString(
"\\s+",
withString: " ",
options: .RegularExpressionSearch,
range: nil
).componentsSeparatedByString(" ")
let name = components[3]
let demangledName = _stdlib_demangleName(name)
print("\(demangledName)")
}
}
which prints (for my project called SwiftTest):
SwiftTest.externalFunc () -> ()
SwiftTest.Test.callExternalFunc () -> ()
main
start
0x0
Does Swift currently support macros, or are there future plans to add support? Currently I'm scattering:
Log.trace(nil, function: __FUNCTION__, file: __FILE__, line: __LINE__)
In various places throughout my code.
In this case you should add a default value for the "macro" parameters.
Swift 2.2 and higher
func log(message: String,
function: String = #function,
file: String = #file,
line: Int = #line) {
print("Message \"\(message)\" (File: \(file), Function: \(function), Line: \(line))")
}
log("Some message")
Swift 2.1 and lower
func log(message: String,
function: String = __FUNCTION__,
file: String = __FILE__,
line: Int = __LINE__) {
print("Message \"\(message)\" (File: \(file.lastPathComponent), Function: \(function), Line: \(line))")
}
log("Some message")
This is what fatalError and assert functions do.
There are no other macros except the conditional compilation already mentioned in another answer.
The Apple docs state that:
Declare simple macros as global constants, and translate complex macros into functions.
You can still use #if/#else/#endif - but my feeling is that they will not introduce macro functions, the language simply doesn't need it.
Since XCode 7.3, the __FILE__ __FUNCTION__ and __LINE__ compile-time constants have become the nicer-looking #file #function and #line respectively.
Here is an updated Swift 2 answer.
func LogW(msg:String, function: String = #function, file: String = #file, line: Int = #line){
print("[WARNING]\(makeTag(function, file: file, line: line)) : \(msg)")
}
private func makeTag(function: String, file: String, line: Int) -> String{
let url = NSURL(fileURLWithPath: file)
let className = url.lastPathComponent ?? file
return "\(className) \(function)[\(line)]"
}
Example of use:
LogW("Socket connection error: \(error)")
lastPathComponent needs an NSURL, so I changed the above code to this:
func log(message: String,
function: String = __FUNCTION__,
file: String = __FILE__,
line: Int = __LINE__) {
let url = NSURL(fileURLWithPath: file)
print("Message \"\(message)\" (File: \(url.lastPathComponent ?? "?"), Function: \(function), Line: \(line))")
}
log("some message")
There is way to use macros on swift (but this used in Mixed of objective c and swift)
declare your macros into Project-name-Bridging-Header.h
#define YOUR_MACRO #"Description"
or create separate header file for macros "macros.h"
import this header "macros.h" in to your Bridging-Header.h file..
now just save your project your macros will came in swift file ..
if you don't wanna object c code on your swift project... just create dummy cocoa touch classes it will create bridging header then use my way...
Macros are evil, but sometimes you just need them. For example, I have
struct RegionEntity {
var id: Int!
}
And I want to place instances of this struct to Set. So I have to conform it to Hashable protocol.
extension RegionEntity: Hashable {
public var hashValue: Int {
return id
}
}
public func ==(first: RegionEntity, second: RegionEntity) -> Bool {
return first.id == second.id
}
Great. But what if I have dozens of such structs and the logic is the same? Maybe I can declare some protocol and conform it to Hashable implicitly. Let's check:
protocol Indexable {
var id: Int! { get }
}
extension Indexable {
var hashValue: Int {
return id
}
}
func ==(first: Indexable, second: Indexable) -> Bool {
return first.id == second.id
}
Well, it works. And now I'm gonna conform my struct to both protocols:
struct RegionEntity: Indexable, Hashable {
var id: Int!
}
Nope. I can't do that, because Equatable requires == operator with Self and there is no == operator for RegionEntity.
Swift forces me to copy-paste confirmation code for each struct and just change the name. With macro I could do that with only one line.
A macro proposal is going through Swift Evolution right now. This would allow you to define a custom type that could create a macro that can be evaluated at compile time, like the C stringify macro, for example.
https://forums.swift.org/t/se-0382-expression-macros/62090