What happens to Console.WriteLine calls in Xamarin.iOS release builds? - ios

When developing iOS apps using XCode, you can use NSLog calls to write information to the console for debugging. In Xamarin.iOS, the same can be done with Console.WriteLine.
When running a release build of an app, without the debugger attached, these calls serve no purpose. So I've thought of excluding them with pre-processor directives:
#if DEBUG
Console.WriteLine("foo");
#endif
So that the calls don't even exist in the release build. I appreciate this may be micro-optimisation - but I'm curious if Xamairn already does this, making this unnecessary?

Console output is not removed in iOS release builds.
FYI: Xcode does not "automatically" remove NSLog or print from Obj-C/Swift "release" code either. (You typically define a single global preprocessor macro to remove them all, if desired...)
Your options are many... including:
DEBUG preprocessor as you show in your question.
Use System.Diagnostic.Debug.WriteLine:
Any calls to Debug.* will be removed by the compiler due to the [Conditional("DEBUG")] attribute being applied.
Create your own "Logger" class as a wrapper to the stdout writers and [Conditional("DEBUG")] it
Use Fody and re-weave the assemblies to remove/NOP/redirect the WriteLine
I do this to redirect the calls to in internal log and upon crash or user stat requests, forward this log to our crash reporting servers.
etc, .....
Ref: Xamarin's NSLogWriter Helper Class

Related

Remove logs completely from iOS release build (also from compiled binary file)

Is there any way to completely remove logs from a final release build of an application written in Swift? By completely I mean that if I open the compiled binary file in a text/hex editor there should be no logged strings in it and if someone disassembles the binary file also he should not see any logged strings in a disassembler.
I know that I can disable writing the logs to the console in release build with #if DEBUG for example with something like this:
func log(_ message: String) {
#if DEBUG
print(message)
#endif
}
But when using this approach logged strings are still visible in compiled binary (and could for example help someone in analysing disassembled code of the application). They are just not printed on the console and that is not acceptable for me. I want to remove them completely from compiled binary. In Objective-C we could use preprocesor macros to achieve that but they are not available in Swift. I could put #if DEBUG before each occurrence of log/print function in the code and that would work but obviously this is not a great solution as it would require to add this #if DEBUG in hundreds of places. Alternatively I could just do find and replace to comment all print/log calls before building a release build but this is also not a great solution as I would need to comment and uncomment those logs each time I build the release version. Is there any better way to achieve that?
You could try using the the Logger framework provided by Apple
It is more performant that print statements, and you have more control over what is logged - for developing and debugging you can use the debug log level. There are other logging levels that generate messages during run time with privacy options.
There is a WWDC Video which you may find useful to introduce you to this.

Why would I ever NOT want to Enable Testability

In ios apps the default behaviour appears to be to fail for Test Compilation.
Why would I want that to be default? Surely, at worst, I would want Debug to have it enabled? What changes does Enabling Testability actually make?
I happened upon this while tracking down another issue. But perhaps I can give provide a scenario. Why would you ever not want to enable testability?
-fvisibility=hidden.
If you want to use the GCC_SYMBOLS_PRIVATE_EXTERN (aka Symbols Hidden By Default), enable testability has higher precedence and will override this.
In my case, I have a configuration which is copied from Debug and hence has Enable Testability == YES. I have an external static library which was built with -fvisibility=hidden which is used to build one of my own static libs (built with Xcode). However when building my debug builds, I get errors such as (I redacted the function names and paths)
Showing All Messages : Direct access in function ... means the weak
symbol cannot be overridden at runtime. This was likely caused by
different translation units being compiled with different visibility
settings.
From the Apple doc here:
https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/04-writing_tests.html
When you set the Enable Testability build setting to Yes, which is
true by default for test builds in new projects, Xcode includes the
-enable-testing flag during compilation. This makes the Swift entities declared in the compiled module eligible for a higher level of access.
It would seem that it is also there for Swift accessibility. So if you are not using Xcode's testing and Swift, it would also seem like you could do without this.

Asserts are hit in production build causing crashes

I have several assert(condition, "message") statements in my project.
They are used to check invariant conditions during development. I thought they would be ignored in production/release build (as stated in this answer). They are not. Instead they cause crashes during TestFlight testing. When I comment asserts the app does not crash. Something usually gets wrong a bit but it does not crash.
Can it be something with my build settings?
All my archive schemes use release configuration:
The asserts are in Cocoa Touch Framework project, that is used from custom keyboard extension.
All the targets in all projects (Cocoa Touch Framework, and the main project with keyboard extension target) have these Build Settings:
Enable Foundation Assertions
Debug YES
Release NO
Disable Safety Checks NO
What's wrong?
EDIT:
Sulthan's answer shows how to disable asserts globally for both debug and relase builds. That is not what I need. I want it to work as expected - asserts should be enabled in debug but disabled in release builds.
By default it works that way - and it also works that way in my main project. But it does not work for asserts located in Framework project that is linked from that main project (details in this question). Why? How to fix it?
The options you have tried:
Enable Foundation Assertions is in the preprocessing section (Macros). Swift is not preprocessed and does not use macros. This option disables NSAssert, NSParameterAssert and similar macros commonly used in Objective-C.
Disable Safety Checks is a performance option:
By default, the standard library guarantees memory safety. Many functions and methods document the requirements that must be satisfied by the caller, such as an array index being valid; memory safety is guaranteed even if a requirement is violated. However, violating a requirement can trigger a runtime error. APIs that include the word “unsafe” in their name let you explicitly disable safety checks in places where you need the additional performance. It’s your responsibility to verify the memory safety of code that uses unsafe APIs. Memory safety is also not guaranteed if there is a race condition in multithreaded code.
(Swift Library Reference)
You should probably try my answer here
(use -assert-config Release in Other Swift Flags).
Or just keep the asserts in production builds. Every failing assert is a bug and in general it's better to know about a bug as soon as possible.

Should debug code be removed when released (for iOS apps)

I am currently making an application using swift where I am using some debug code.
Here is my logic:
//Do some code that always gets executed
if self.debug
{
//Do some debugging code
}
//Do more code that always gets executed
When I put this for release, should I remove the debug code or could I just set self.debug to false?
I have looked at this question: Should debugging code be left in place?, however that sounds like it's to give the user choice if they want to debug the application or not, with iOS applications users cannot debug it unless they have the Xcode project.
Preprocessor directives are a more preferable way of marking code that should only be compiled for debug builds.
#if DEBUG
// Do debug things
#endif
By using preprocessor directives, the code that you put in that block doesn't get compiled into release builds.

Disable ASL/NSLog at runtime

It's easy to "disable" NSLog at compile time by replacing it with a macro, etc.
Can NSLog (or ASL in general) be disabled at runtime?
My goal is to silence some logs that I do not have control over.
Look at the Lumberjack Framework. There you can set the logging level as required. To hide certain log statements when you release the app, just change the logging level from say DEBUG to INFO using #ifdef RELEASE and #ifdef DEBUG macros.
Update:
In case the log statements are coming from a different framework that you are linking, you can use something like Method Swizzling to swap the implementation of NSlog with your custom method.

Resources