Objective C has preprocessor, Swift has compilation conditions that allows to use different code for different environments, for example for debug or release build:
#if DEBUG
print("debug message")
doDebugAction()
#else
doReleaseAction()
#endif
Can I add code that compiles only for SwiftUI preview? Something like:
#if targetEnvironment(swiftUIPreview)
static func mock() -> SomeStruct {
// fill random data
}
#endif
Unfortunately it doesn't exist any compilation condition afaik, but you can use ProcessInfo:
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
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()
}
I can use #if directly in my codes and it works just fine, however I want make a reusable function and call it in other places as well, I made this function:
func runOniOS(action: () -> Void) {
#if os(iOS)
action()
#endif
}
And I am using it in my multi platform project for macOS like this:
runOniOS(action: {
let redColor: UIColor = .red
})
But Xcode take it as an error and say:
Cannot find type 'UIColor' in scope
How can I make a reusable function?
While doing some research on ncurses and how to use it in Swift, I took a look at IOGUI.
It wouldn't compile properly, so I began clearing warnings and errors.
That's when I came across what may be some Swift subtleness that I'm unaware of.
Note: I'm not discussing compilation errors below. This question is specific to the parsing done by Xcode to indicate warnings and errors in the code editor.
Referring to the following struct:
public struct MenuWidget {
var widgetRows: Int
// CUT
private var startRow: Int
#if os(Linux)
public init(startRow: Int, widgetSize: Int, choices: [GUIMenuChoices], delegate: #escaping MenuChoicesSelectionDelegate, mainWindow: UnsafeMutablePointer<WINDOW>) {
self.startRow = startRow
// CUT
initWindows()
}
#else
public init(startRow: Int, widgetSize: Int, choices: [GUIMenuChoices], delegate: #escaping MenuChoicesSelectionDelegate, mainWindow: OpaquePointer) {
self.startRow = startRow
// CUT
initWindows()
}
#endif
// CUT
} // END STRUCT
Within the #if os(Linux) section, self.startRow is marked as inaccessible from that scope.
Yet in the #else, there is no error indicated. widgetRows is accessible in both the #if and #else
If I remove private from the startRow declaration, then it's fine in both scopes. But that makes the variable internal and makes it accessible outside of the struct, which is incorrect.
It could also just be the behavior of the Swift #if construct that I'm not familiar with.
I've searched the usual places, including here at SO. Nothing similar deals with this particular scenario, at least not with the search terms I used.
Any hints or tips would be appreciated.
P.S. If you want to check out the IOGUI source, I'm specifically referring to lines 41 and 78-100 (inclusive) of MenuWidget.swift.
This is not an if construct; it's an #if construct. That means that only one of the #if / #endif blocks even compiles. The other could be complete nonsense for all the Swift compiler cares; the compiler never even sees it. That is what conditional compilation means. So, if you are not on Linux, you have no way of knowing how the code inside the #if part behaves. The code would be just the same if it said this:
#if os(Linux)
public init(startRow: Int, widgetSize: Int, choices: [GUIMenuChoices], delegate: #escaping MenuChoicesSelectionDelegate, mainWindow: UnsafeMutablePointer<WINDOW>) {
zampabalooie and a hot-cha-cha
}
#else
public init(startRow: Int, widgetSize: Int, choices: [GUIMenuChoices], delegate: #escaping MenuChoicesSelectionDelegate, mainWindow: OpaquePointer) {
self.startRow = startRow
// CUT
initWindows()
}
#endif
If you are not on Linux, that will compile just fine — and that doesn't entitle you to start drawing conclusions about Swift's knowledge of some amazing "zampabalooie" or its sudden use of spaces between terms.
And conversely, if you are on Linux, you have no way of knowing how the code inside the #else part behaves.
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!
I trying to make my code compilable with both Swift 1.2 and 2.0, without creating a new branch, by using macro.
Macro in Swift that you defined in custom Swift Compiler flags doesn't allow you to check the conditions as freely as Obj-C.
For example, in function declaration, if it like Obj-C. I can do something like this.
class ThisIsAClass
{
#if SWIFT_TWO_POINT_O
func foo(bar bar: String)
#else
func foo(#bar: String)
#endif
{
// Do some common code
// ...
// ...
#if SWIFT_TWO_POINT_O
print("Do 2.0 things")
#else
print("Do 1.2 things")
#endif
}
}
Macro condition checking within the function is fine.
But the condition checking for declaring function will failed.
Is there a way I can achieve something like this.
Or separate between Swift 1.2 and 2.0 branches is the only way.
Yes, you can define compiler flags and check them to conditionally compile parts of your source, as noted in the docs.
However, there's an important caveat (emphasis added):
In contrast with condition compilation statements in the C preprocessor, conditional compilation statements in Swift must completely surround blocks of code that are self-contained and syntactically valid. This is because all Swift code is syntax checked, even when it is not compiled.
So you can't do:
#if SWIFT_ONE
func foo(/* swift 1 params */)
#else
func foo(/* swift 2 params */)
#endif
{
// ... function body ...
}
...because func foo(params) is not a syntactically complete element. (A syntactically complete function declaration includes the function body.) Ditto for, say, trying to #if around a class declaration but not its contents, etc.
So what can you do instead?
in this particular case, func foo(bar bar: String) is perfectly valid Swift 1.x syntax. The # was merely a shorthand for it... so just use the longhand and you won't have to worry about language-version differences. (Feel free to post about #foo and #bar on Twitter, though.)
more generally, you can have multiple separate functions and dispatch to them:
func foo() {
#if SWIFT_ONE
fooForSwift1()
#else
fooForSwift2()
#endif
}
or for classes or other types, you can use type aliases:
class Foo1 { /* ... */ }
class Foo2 { /* ... */ }
#if SWIFT_ONE
typealias Foo = Foo1
#else
typealias Foo = Foo2
#endif