To a new Xcode project, I have added a new class with a custom init method in this way:
import Cocoa
class NewClass: NSControl {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However, if I build and archive the project, the absolute path to the source file is revealed in the binary:
/Users/my_username/my_path_to_xcode_projects/new_project/NewClass.swift
I plan to distribute my final project over the internet. How can I prevent Xcode to disclose my directory layout to others? I have learned from answers to similar questions that in the "Build Settings" of Xcode one should set "Deployment Prostprocessing" to Yes in order to strip symbols from the binary but this does not seem to work in my case.
If you look closely at the signature of fatalError
:
@noreturn public func fatalError(
@autoclosure message: () -> String = default,
file: StaticString = #file, line: UInt = #line)
You can see the #file
and #line
default arguments. These are evaluated at compile time and get resolved to the current file and line.
To not use the default arguments, you can provide them yourself:
fatalError("Something went wrong", file: "", line: 0)
When this is compiled on the release configuration, the path isn't included in the binary.
If you search for #file
in the standard library, you can find 4 other instances, two of which are assert
and assertionFailure
which won't be included in a release build. The remaining 2 are precondition
and preconditionFailure
.
If we add the DEBUG
/RELEASE
symbols:
we can have conditional compilation of mocked fatalError
, etc. functions that don't use the #file
preprocessor statements on release builds:
#if RELEASE
@inline(__always) @noreturn func fatalError(@autoclosure message: () -> String = "") {
Swift.fatalError(message, file: "", line: 0)
}
@inline(__always) func preconditionAlt(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "") {
Swift.precondition(condition, message, file: "", line: 0)
}
@inline(__always) @noreturn func preconditionFailureAlt(@autoclosure message: () -> String = "") {
Swift.preconditionFailure(message, file: "", line: 0)
}
#else
@inline(__always) func preconditionAlt(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = "", file: StaticString = #file, line: UInt = #line) {
Swift.precondition(condition, message, file: file, line: line)
}
@inline(__always) @noreturn func preconditionFailureAlt(@autoclosure message: () -> String = "") {
Swift.preconditionFailure(message, file: file, line: line)
}
#endif
All instances where you called any of these functions now uses the custom implementation. This way you can still get file and line information when debugging but prevent it from appearing in the product. You'll have to change every instance of precondition[Failure]
to precondition[Failure]Alt
though, Swift cannot unambiguously infer the function otherwise. You can press Shift-Alt-Cmd-F to search and replace in the whole project
If a third party framework uses these functions and you compile it on your machine, the path will obviously be included in the framework product. To prevent this you either have to use a precompiled version or compile it yourself with the mocking.
If any other self declared or third party function uses #file
, you obviously would have to change that as well, it's not very common though.
There may be other possibilities for the file structure to be leaked of which I don't know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With