Custom TEST pre-processor macros in Swift 5 - swift

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
}

Related

Using previewCGImageRepresentation in both Xcode 13 and Xcode 12?

I’ve run into an issue where Xcode 13b2 (iOS 15 SDK) changed the Swift return type of AVCapturePhoto.previewCGImageRepresentation(). In Xcode 12.5.1 (iOS 14 SDK), this method returns Unmanged<CGImage>. In 13b2 - 13b4, it returns CGImage?.
I need my code to compile under both Xcode versions, since Xcode 13 has other issues, and can’t be used to submit builds to the App Store. I thought I was clever writing this, but it won’t compile, because it’s not conditional code compilation check but rather a runtime check:
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
if #available(iOS 15, *) {
return self.previewCGImageRepresentation()
} else {
return self.previewCGImageRepresentation()?.takeUnretainedValue()
}
}
}
Another possibility might be to create a user-defined Xcode setting, but I don’t think that can be done conditionally based on Xcode or SDK version.
There might be some unsafe pointer histrionics one can do…
Any other ideas?
You can make a check on the swift compiler version. An exhaustive list is available on wikipedia at the time of writing.
https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
#if compiler(>=5.5)
return self.previewCGImageRepresentation()
#else
return self.previewCGImageRepresentation()?.takeUnretainedValue()
#endif
}
}

Swift errors using #if, #endif

Using #if, #endif in Swift (using Xcode) produces errors if it cuts into the flow of an operation. This screenshot says it all:
Does anyone know a solution to make this example work, without repeating the entire code block twice? There can easily be situations where the entire block can be very large.
EDIT: My sample was a bit too simple. Here is a new sample where the "else if" depends on the same define (DEBUG). The "else if" must also be within the #if and #endif. And other samples can be much more complex than this.
Ideally, limit the usage of #if as much as possible. Using preprocessor directives is always a bit of a code smell. In this case, you can simply use a boolean variable:
#if DEBUG
let debug = true
#else
let debug = false
#endif
Then simply use the variable:
var a = 0
var b = 0
...
else if debug && a == b {
}
In release mode the code will become unreachable and the optimizer will remove it anyway.
With a bit of imagination, we can find other solutions, for example, we can move the check to a function:
func isDebugCheck(a: Int, b: Int) -> Bool {
#if DEBUG
return a == b
#else
return false
#endif
}
or we can move the whole code to a separate function and replace if-else by a return (or continue, depending on you needs), e.g.:
if a == 7 {
...
return
}
#if DEBUG
if a == b {
return
}
#endif
if ...
As #user28434 notes, there is no source-level pre-processor. This has gotten rid of a lot of very tricky pre-processor problems in C (such as bizarre needs for parentheses to make things work).
However, #if is integrated well into the language, and specifically supports switch for exactly these kinds of cases.
var a = 0
#if DEBUG
let b = 0
#endif
switch a {
case 7: a += 1
#if DEBUG
case b: a += 2
#endif
case 5: a += 3
default:
break
}
You can simply achieve this case with below code:
if a == b {
#if DEBUG
a += 2
#else
a += 1
#endif
} else if a == c {
a += 3
}

"Use of undeclared identifier" in Objective-C++ when importing from another source file

I have declared a functions in a header file like this (pseudo code):
// funcs.h
#import <Foundation/Foundation.h>
NSString* func1();
void func2();
Then I have the implementations in
//funcs.mm
#import "funcs.h"
NSString* func1()
{
// do something and use C++ functions
}
void func2()
{
// do more
}
When I include the header in a third file
// AppDelegate.mm
#import "funcs.h"
....
string1 = func1();
func2();
....
Whenever I try to use these functions I get "Use of undeclared identifier" errors.
What am I doing wrong? Isn't function declaration in files in Objective-C++ the same as in normal C?
A .mm file is Objective-C++. Plain functions in a .mm file are not C functions, but C++ functions. Objective-C is the same as C, but you are not using Objective-C, you are using Objective-C++.
I would strongly recommend that you only use .mm files for interfacing with C++. For example, your AppDelegate should be a .m file and not .mm.
You need to write C functions like this:
extern "C" {
NSString* func1()
{
// do something and use C++ functions
}
}
Also, in the .h file you should declare them like this:
#if __cplusplus
extern "C" {
#endif
NSString* func1(void);
#if __cplusplus
}
#endif
The macro __cplusplus might be misspelled, google for it.
extern "C" tells the C++ or Objective-C++ compiler that a function should have C linkage.

How do you use Lumberjack logging in 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.

Mathematical functions in Swift

How do I use mathematical functions like sqrt(), floor(), round(), sin(), etc?
When doing:
_ = floor(2.0)
_ = sqrt(2.0)
I get:
error: use of unresolved identifier 'floor'
error: use of unresolved identifier 'sqrt'
As other noted you have several options. If you want only mathematical functions. You can import only Darwin.
import Darwin
If you want mathematical functions and other standard classes and functions. You can import Foundation.
import Foundation
If you want everything and also classes for user interface, it depends if your playground is for OS X or iOS.
For OS X, you need import Cocoa.
import Cocoa
For iOS, you need import UIKit.
import UIKit
You can easily discover your playground platform by opening File Inspector (⌥⌘1).
To be perfectly precise, Darwin is enough. No need to import the whole Cocoa framework.
import Darwin
Of course, if you need elements from Cocoa or Foundation or other higher level frameworks, you can import them instead
For people using swift [2.2] on Linux i.e. Ubuntu, the import is different!
The correct way to do this is to use Glibc. This is because on OS X and iOS, the basic Unix-like API's are in Darwin but in linux, these are located in Glibc. Importing Foundation won't help you here because it doesn't make the distinction by itself. To do this, you have to explicitly import it yourself:
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux) || CYGWIN
import Glibc
#endif
You can follow the development of the Foundation framework here to learn more
EDIT: December 26th, 2018
As pointed out by #Cœur, starting from swift 3.0 some math functions are now part of the types themselves. For example, Double now has a squareRoot function. Similarly, ceil, floor, round, can all be achieved with Double.rounded(FloatingPointRoundingRule) -> Double.
Furthermore, I just downloaded and installed the latest stable version of swift on Ubuntu 18.04, and it looks like Foundation framework is all you need to import to have access to the math functions now. I tried finding documentation for this, but nothing came up.
➜ swift
Welcome to Swift version 4.2.1 (swift-4.2.1-RELEASE). Type :help for assistance.
1> sqrt(9)
error: repl.swift:1:1: error: use of unresolved identifier 'sqrt'
sqrt(9)
^~~~
1> import Foundation
2> sqrt(9)
$R0: Double = 3
3> floor(9.3)
$R1: Double = 9
4> ceil(9.3)
$R2: Double = 10
You can use them right inline:
var square = 9.4
var floored = floor(square)
var root = sqrt(floored)
println("Starting with \(square), we rounded down to \(floored), then took the square root to end up with \(root)")
To use the math-functions you have to import Cocoa
You can see the other defined mathematical functions in the following way.
Make a Cmd-Click on the function name sqrt and you enter the file with all other global math functions and constanst.
A small snippet of the file
...
func pow(_: CDouble, _: CDouble) -> CDouble
func sqrtf(_: CFloat) -> CFloat
func sqrt(_: CDouble) -> CDouble
func erff(_: CFloat) -> CFloat
...
var M_LN10: CDouble { get } /* loge(10) */
var M_PI: CDouble { get } /* pi */
var M_PI_2: CDouble { get } /* pi/2 */
var M_SQRT2: CDouble { get } /* sqrt(2) */
...
For the Swift way of doing things, you can try and make use of the tools available in the Swift Standard Library. These should work on any platform that is able to run Swift.
Instead of floor(), round() and the rest of the rounding routines you can use rounded(_:):
let x = 6.5
// Equivalent to the C 'round' function:
print(x.rounded(.toNearestOrAwayFromZero))
// Prints "7.0"
// Equivalent to the C 'trunc' function:
print(x.rounded(.towardZero))
// Prints "6.0"
// Equivalent to the C 'ceil' function:
print(x.rounded(.up))
// Prints "7.0"
// Equivalent to the C 'floor' function:
print(x.rounded(.down))
// Prints "6.0"
These are currently available on Float and Double and it should be easy enough to convert to a CGFloat for example.
Instead of sqrt() there's the squareRoot() method on the FloatingPoint protocol. Again, both Float and Double conform to the FloatingPoint protocol:
let x = 4.0
let y = x.squareRoot()
For the trigonometric functions, the standard library can't help, so you're best off importing Darwin on the Apple platforms or Glibc on Linux. Fingers-crossed they'll be a neater way in the future.
#if os(OSX) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
let x = 1.571
print(sin(x))
// Prints "~1.0"