Swift macros when creating Array from literals - swift

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()
}

Related

Has Swift compilation condition for SwiftUI preview environment?

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"

Objective-C++ and Swift - nested structs in bridging header

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.

Conditional Imports in Swift

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!

Is it possible to disable some lines of code in Swift?

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

Macros in Swift?

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