Active Compilation Condition

Hi


I have a Universal Swift Framework built in Xcode 10 Swift 4.2 and I want to display some messages depending on Active Compilation Condition flags that are provided by the app. For example a struct in the Framework contains the following:

public struct Person
{
  public let name: String
  public let age: Int
  
  public init(from decoder: Decoder) throws
  {
    #if LOG
      NSLog("Log A")
    #else
      NSLog("No Log")
    #endif
 
    ...
  }
}

In the app that has the Framework embeded, I would set LOG in the Active Compilation Condition for Debug (Any Architecture). So when the app is running in either simulator or on the actual device and the person struct is initialized, I would see "Log A" appear in the Console or Debug area (in Xcode).


I haven't been able to get this to work, "No Log" appears. Have I misunderstood the prupose of using Active Compilation Conditon 😕


Appreciate any help.


Thanks

This is working for me. I created a new test project and then added a framework target to it. I then put your code, with a few minor edits so that it builds, into the framework target and called it from my app. I set the

LOG
conditional in Active Compilation Conditions (
SWIFT_ACTIVE_COMPILATION_CONDITIONS
) in the framework target and, when called from the app, it prints
Log A
rather than
No Log
. This is all with Xcode 10.0.

I’m not sure what’s going on in your app but I recommend that you investigate this by creating a small test app as described above. If that works, you can then look at the differences between your app and your test app. If, on the other hand, it doesn’t work, something quite strange is happening.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

HI


Did you end up with 2 projects, one being a framework, the other being the app? Then embed the framework into the app?


The framework contains the #if LOG ... but the app project is where you would set LOG in the SWIFT_ACTIVE_COMPILATION_CONDITIONS. After reading your message, I think I've got this around the wrong way. I think I should expose a global property out of the Framework that enables the logging based on the SWIFT_ACTIVE_COMPILATION_CONDITIONS in the app. For example in the Framework:


public struct FrameworkHelper
{
  private static var _debug: Bool = false
  
  public static var showDebugInfo: Bool
  {
    get
    {
      return _debug
    }
    set
    {
      _debug = newValue
    }
  }
}

public struct Person
{
  let name: String
  let age: Int

  public init(from decoder: Decoder) throws
  {
    NSLog("\(FrameworkHelper.showDebugInfo ? "Log A" : "No Log")")
  }
}


Then in the app, I would set SWIFT_ACTIVE_COMPILATION_CONDITIONS as having a value in Debug of "SHOW_THE_LOG". Then in AppDelegate set the Framework property.


@objc class AppDeleagte: UIResponder, UIApplicationDelegate
{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Bool
    {  #if SHOW_THE_LOG
       FrameworkHelper.showDebugInfo = true
       #endif

}

Is this a better way of address the problem? Also, do you have any info on the Activity tracing for Unified logging in Swift? This was demo'd 2 years ago, although there are some open source implementations, I wonder if Apple is going to release the Swift framework for it?


Thanks again

Did you end up with 2 projects, one being a framework, the other being the app?

No. I had a single project with two targets.

The framework contains the

#if LOG ...
but the app project is where you would set
LOG
in the
SWIFT_ACTIVE_COMPILATION_CONDITIONS
.
After reading your message, I think I've got this around the wrong way.

Indeed. Remember that frameworks are built binaries, so the Active Compilation Conditions has to be set when that binary is built, that is, in the framework target (or project, if you’re using separate projects). Setting it in the app target only affects the built app, and your app contains no code that depends on it.

I think I should expose a global property out of the Framework that enables the logging based on the

SWIFT_ACTIVE_COMPILATION_CONDITIONS
in the app.

That’s one option. The main drawback to this approach is that the logging code all gets compiled into your framework, so if that code is large then your framework is unnecessarily large. As I’ve no idea how much logging you’re doing, you’ll have to make that call yourself.

Is this a better way of address the problem?

If you’re OK with the logging code being compiled into your framework, you have some other options, as discussed below.

Also, do you have any info on the Activity tracing for Unified logging in Swift?

I’m not sure whether you’re talking about

OSLog
here? In which case, yes, that has a Swift wrapper as I’ve discussed below. However, if you’re asking about activity tracing specifically, I’ve not looked at that recently.

You can inject an

OSLog
object into your framework, or into the object’s vended by your framework. Your framework can then log to that object always, relying on the fact that
OSLog
is super efficient if the logging is disabled.

So your framework code might look like this:

import os.log

var log: OSLog = .disabled

and inside your framework you can log like this:

// If you’re deploying to the very latest systems, use the very latest
// `os_log` function.

os_log(.default, log: log, "Hello Cruel World!")

// If not, use the older version.

os_log("Hello Cruel World!", log: log, type: .default)

Your client code can then tell the framework to log by changing the

log
global variable to something more appropriate.
MyFramework.log = OSLog(subsystem: "com.example.MyApp", category: "MyFramework")

This approach has a bunch of advantages:

  • You’re using

    OSLog
    directly, rather than the
    NSLog
    shim.
    NSLog
    is cool for day-to-day debugging but if you’re going to add logging that you rely on in the long term then
    OSLog
    is a much better option.
  • OSLog
    is super efficient when logging is disabled.
  • You don’t have to hard code logging policy into your framework; that policy is controlled by the clients of your framework.

You can do a similar thing on an object-by-object basis. So if you have an object exported from your framework, you can add a

log
property to that and then the client can control its logging directly.

Another option is to have your framework, or the object’s vended by your framework, have a delegate that they call to log. This gives the client complete control.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Active Compilation Condition
 
 
Q