I recently joined a company with a large app. Anything that can be tested, has an associated protocol (with a restriction on using PATs). As a result, initializers for virtually everything define their parameters as existentials rather than concrete types. implementation.
Example:
// ModuleA
public protocol ModelProtocol { }
public final class Model: ModelProtocol { }
// ModuleB
import ModuleA
public protocol ComplexModelProtocol {
init(model: ModelProtocol)
}
public final class ComplexModel: ComplexModelProtocol {
let model: ModelProtocol
public init(model: ModelProtocol) {
self.model = model
}
}
// In test targets
final class MockModel: ModelProtocol { }
final class MockComplexModel: ComplexModelProtocol {
let model: ModelProtocol
public init(model: ModelProtocol) {
self.model = model
}
}
In production every protocol (with few exceptions) has only one concrete protocol. The company has a fair amount of internal documentation stating that using generics drastically bloats binary size compared to using existentials. I believe (from following these forums for some time), that there is definitely a performance hit in using existentials everywhere compared to taking advantage of generics, but I've encountered little discussion around binary size.
It feels "un-swifty" to be doing this, at the very least unfamiliar in my experience in Swift (although I have definitely encountered this pattern in other languages). My question is:
What is the guidance on binary size? It seems wrong to ignore generics altogether.
It seems that where we really only have one concrete implementation in most cases, that the compiler would generate the same code as if we just used the concrete implementations directly (if we used generics). Generics or using the boxing method we are is only necessary for testing, is there another approach altogether to enable testing?