Swift PM, Bundles and Resources

I read the linked proposal draft, and not knowing where else to send my feedback, I will do it here.

Historically, macOS and iOS resources have been accessed primarily by name, using untyped Bundle APIs such as path(forResource:ofType:) that have assumed that each resource is stored as a separate file. [...] In the long run, we would like to do better. Because SwiftPM knows the names and types of resource files, it should be able provide type-safe access to those resources by, for example, generating the right declarations that the package code could then reference. [...] In the short term, however, we want to keep things simple and allow existing code to work without major modifications.

I am of the opinion that individual synthesized accessors should not be deferred. The typing isn’t the issue—we can defer that by making everything unspecific Data instances for now. I would rather never provide access to dynamic string identifiers or paths.

If an API like path(forResource: String, ofType: String) is provided, then the package manager and compiler can never have enough information to know which resources are actually being used, and that will prevent future optimization opportunities. The package could do this:

public func cardImage(suit: Suit, rank: Rank) -> Image {
    let name = suit.identifier + rank.identifier
    let file = Bundle.moduleResources.path(forResource: name)
    return Image(from: file)
}

With this ability, the package manager must naively bundle all available resources—not just the 52 card images, but also the cribbage board and peg images.

A client package might be making a solitaire game instead, but want to reuse the 52 card logic, so it links and calls cardImage(suit:rank:). But now they come bundled with board and peg images it does not want and will never use. The dead code contains dead resources that the optimizer has no means to identify.

I think it would be better never to provide dynamic identifiers like that, so that we keep the door open for better automatic optimization in the future.

An API where each resource is a direct property is much more future‐proof:

public func cardImage(suit: Suit, rank: Rank) -> Image {
    let data: Data
    switch suit {
    case .hearts:
        switch rank {
            case .ace:
                  data = Bundle.moduleResources.aceHearts
            case .two:
                  data = Bundle.moduleResources.twoHearts
            // ...
        }
        // ...
    }
    return Image(from: data)
}

Since every access is forced through the same property, when that property is optimized away, the optimizer knows it can remove the resource it represents too. The client only calls cardImage(suit:rank:), and never anything that uses moduleResources.cribbageBoard? Then the client needs AceHearts.png (and co.), but not Cribbage Board.png.

(I believe the vast majority of resource access likely directly loads individual and specific resources, and will not suffer the boilerplate bloat demonstrated by the above function.)

1 Like