This is an experimental library for faster casting by caching protocol conformance checks, so that unsuccessful as? casts are faster. The first time a conformance check for a [type, protocol] pair is done, e.g. foo as? BarProtocol, it can be costly if the lookup is not stored in Apple's protocol conformance cache already. If foo does indeed conform to BarProtocol, then the cost is unavoidable. There's no way to not go through Apple's machinery and be able to call BarProtocol functions on foo, at least not without relying on internal ABI details (as far as I know). However, if foo does not conform to BarProtocol, then foo as? BarProtocol just returns nil and we won't be calling any BarProtocol functions on it. In this case, if we know that it doesn't conform via a cache (e.g., from a previous run where this check was done) then we can just return nil.
FastCaster sharedFastCaster = FastCaster(cacheURL: ...)
func foo<T: Hashable>(object: T) {
if let decodable = sharedFastCaster.cast(object, as: Decodable.self) { // Replaces `if let decodable = object as? Decodable`
...
}
}
Determining if the app has changed in a way that may add/remove conformances, thus invalidating the cache
We want a cache that is stable between runs and is invalidated when the OS version or app binaries (the main binary and all dylibs) change at all. Roughly, the cache consists of a mapping of (source type, target type) to bool. To determine if the app binaries have changed, we can look at some hash of the binary. E.g., this can be the LC_UUID for each binary (as long as the developer is using different UUIDs for each build, e.g. with -Wl,-random_uuid). Or, it can be the hashes computed for code signing.
One interesting topic is, can system binaries change even if the OS version doesn't, thus adding/removing conformances? If this is possible, then maybe we can either look at the UUIDs of all loaded Apple binaries, or just version metadata of them. Or, we could restric this cache to only work pairs of types from within the app.
We want a stable key for each type, be it the type we want to cast or the protocol type we want to cast it to, that won't change between runs and can be serialized. There are a number of keys that may possibly work:
String(reflecting: type)(however, this is slow because it does protocol conformance checks internally)_getMangledTypeName(type)which is what it currently uses- The pointer to the type metadata, as long as it's a pointer to an address in the binary, and with the base subtracted to account for ASLR (can type metadata exist outside the binary?)
It could also be desirable to use, say, both the mangled name and the pointer together if we want to be extra cautious.