suppose i have an application with a dependency graph that looks like:
.target("FrameworkCore"),
.target("Framework",
dependencies:
[
"FrameworkCore",
.product(name: "NIOCore", package: "swift-nio")
]),
.target("ApplicationCore", dependencies: ["FrameworkCore"]),
.target("Application", dependencies: ["ApplicationCore", "Framework"]),
FrameworkCore
the FrameworkCore target defines a MyDecodingContainer<Storage> type, which is generic over some storage type, whose subscript(_:) will be called often.
this type is @frozen, and all of its API is @inlinable.
it also has a public MyDecodable protocol, which has a generic init(from:) requirement taking an instance of MyDecodingContainer.
FrameworkCore/MyDecodable.swift
@frozen public
struct MyDecodingContainer<Storage>
where Storage:RandomAccessCollection<UInt8>
{
...
}
public
protocol MyDecodable
{
init(from:MyDecodingContainer<some RandomAccessCollection<UInt8>>)
throws
}
Framework
the Framework target contains a type MyGenericScheme, which is generic over some MyDecodable type, and uses a specialization of MyDecodable and MyDecodingContainers’ API where Storage == NIOCore.ByteBufferView.
MyGenericScheme is not @frozen, and its API is not @inlinable, at least not yet.
Framework depends on NIOCore, and NIOCore takes a long time to clone and build. and it’s also annoying to repeatedly import NIOCore everywhere, so targets that can depend on FrameworkCore only should depend on FrameworkCore and not Framework.
Framework/MyGenericScheme.swift
import FrameworkCore
import NIOCore
public
struct MyGenericScheme<Element> where Element:MyDecodable
{
public
let element:Element
}
extension MyGenericScheme
{
public
init(bytes:ByteBufferView) throws
{
let decoder:MyDecodingContainer<ByteBufferView> = .init(bytes)
self.init(element: try .init(from: decoder))
}
}
ApplicationCore
ApplicationCore is a application-specific target that defines a type Foo that conforms to FrameworkCore.MyDecodable.
ApplicationCore does not depend on NIOCore, and knows nothing about ByteBuffer or ByteBufferView. so clearly, Foo’s MyDecodable conformance must be @inlinable in order to get anything resembling decent performance.
ApplicationCore/Foo.swift
import FrameworkCore
@frozen public
struct Foo
{
}
extension Foo:MyDecodable
{
@inlinable public
init(from:MyDecodingContainer<some RandomAccessCollection<UInt8>>)
throws
{
...
}
}
Application
finally, Application brings the four components (ApplicationCore, FrameworkCore, Framework, and NIOCore) together, with a function bar(bytes:) like the one below:
Application/Bar.swift
import ApplicationCore
import FrameworkCore
import Framework
import NIOCore
func bar(bytes:ByteBufferView) throws -> Foo
{
let scheme:MyGenericScheme<Foo> = try .init(bytes: bytes)
return scheme.element
}
my question is about how resilient MyGenericScheme can be, while still maintaining acceptable performance.
- does
MyGenericScheme.init(from:) need to become @inlinable?
- does
MyGenericScheme itself need to become @frozen?