Download the sample project if you want. You don’t have to though. The project doesn’t even need to be ran. It’s just provided for context.
I’m going to discuss two gotchas I hade with Xcode. For quite some time I thought a fix was coming, then I realized this is because I didn’t fully understand the difference between an interface (or protocol) with a concrete type.
Find Call Hierarchy not working:
Have you ever right clicked on a function -> Find -> Find Call Hierarchy, but then wondered why Xcode doesn’t show you where the function is getting called from?
The reason that such a complication exists is Swift Protocols. The compiler is very precise.
- When you try to look up ‘Find Call Hierarchy’ on a concrete type, Xcode tries to look things up by finding a call to that very implementation.
- When you try to look up ‘Find Call Hierarchy’ on a protocol type, Xcode tries to look things up by finding a call to all conforming implementations.
class Logger: LoggerProtocol {
func performLog() {
print("Production Code")
}
}
Trying to find all callers of performLog
from above results in Xcode looking up references for Logger.performLog
. Usually there aren’t much callers that directly hit a concrete implemenation of a function. Xcode found zero callers!
While if you try finding the caller by write clicking the function signature under the protocol then things are different:
protocol LoggerProtocol {
func performLog()
}
It’s because Xcode will look up references for LoggerProtocol.performLog
(vs. Logger.performLog
).
Usually there are a lot of conforming types that implement a protocol function. In this sample project that I wrote, Xcode only found one caller:
Jump to Definition not working:
Similarly, have you ever right clicked on a function call -> Jump to Definition -> Then get prompted with too many definitions?
When it comes to protocols, the compiler just can’t tell which adaptation of a protocol gets used e.g. upon tapping ‘Jump to Definition’ on performLog
in the snippet below, Xcode will show all conformances (production adoption, test-code adoption, another production adoption, etc.) to that protocol requirement.
struct ViewModel {
var logger: LoggerProtocol
func executeLog() {
logger.performLog()
}
}
Xcode leaves it up to you to decide which class definition/implementation is the one you’re looking for.
Usually you don’t want the protocol definition nor want the mock implementation. Rather you just want one of the concrete implementations that is for production code.
Runtime advantages
During runtime Xcode can actually identify the implementation is uses. Obviously if that wasn’t the case then Xcode won’t know which implementation to execute. The advantage of this is that if you use Xcode’s Step Into ( ) on a function then it will take you to the right implementation.
Will take you to:
Conclusion:
Xcode can’t automagically identify which implementation of a protocol is used. Additionally when a concrete type is never made part of the contract/API/signature, then it won’t be used in Xcode’s lookups that happen against the protocol type. Using this insight we can better understand the behavior of Xcode and use its tools in the right way.