---
title: 'Frameworks and Libraries'
source: 'https://academia.sh/en/courses/ios-platform-and-tools/frameworks-and-libraries'
course: 'iOS Platform and Tools'
language: en
updated: '2026-10-21T05:03:10+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Separates a framework from a library by who calls the code'
    - 'Measures that the visible names of two modules are collected as a union'
    - 'Measures that using the same name from two modules without qualification is rejected'
    - 'Shows that a name qualified with its module removes the meeting'
---

# Frameworks and Libraries

Measuring that the visible names of two imported modules merge, that the same name is rejected, and that a local definition shadows an imported one.

The previous lesson sorted four of the eight meetings into four resolutions; one name lookup meeting gave precedence and one gave rejection. This lesson reads those two meetings closely. When an app imports several modules, the names each module opens meet in the same scope. The question does not change: **how are these two definitions resolved when they meet?** The resolution examined in this lesson is **union**.

First, two concepts need to be separated. A library is ready-made code that the app calls: the app chooses when to call it. A framework builds its own skeleton and calls the app's code inside that skeleton; the app enters the flow of the framework as a piece that handles an event. On the platform, frameworks that come with the system are called system frameworks, while code that an app adds as a dependency is mostly called a package or a library. Both are imported as a **module**, and the names both open stand side by side in the same source file. What is measured is this side-by-side arrival.

```swift
// Meeting.swift — classifies how two definitions are resolved when they meet
struct Meeting {
    let label: String
    let first: Set<String>
    let second: Set<String>
    let result: Set<String>?

    init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
        self.label = label
        self.first = first
        self.second = second
        self.result = result
    }

    var resolution: String {
        if first.isEmpty || second.isEmpty
            || first.isSubset(of: second) || second.isSubset(of: first) {
            return "out-of-scope"
        }
        guard let result, !result.isEmpty else { return "rejection" }
        if result == first.union(second) { return "union" }
        if result == first.intersection(second) { return "intersection" }
        if result == first || result == second { return "precedence" }
        return "off-rule"
    }

    static func list(_ values: Set<String>) -> String {
        values.isEmpty ? "empty" : values.sorted().joined(separator: ",")
    }

    func report() {
        let outcome = result.map(Meeting.list) ?? "none"
        print("\(label): first \(Meeting.list(first)) | second \(Meeting.list(second)) | result \(outcome) -> \(resolution)")
    }
}

func summarize(_ meetings: [Meeting]) {
    for resolution in ["precedence", "intersection", "union", "rejection"] {
        let count = meetings.filter { $0.resolution == resolution }.count
        print("\(resolution): \(count)")
    }
}
```

A module is a surface made of the names it opens to the outside. The helper below models three things: the name of a module and the names it opens, the total of the names that are visible when two or more modules are imported, and a lookup that finds which definition a name comes from. The lookup first checks the local definition; if there is no local definition, it searches among the imported modules; if the definition found is not unique, there is no result. There is also a use qualified with the module name: when `Graphics.Canvas` is written, only the name in the `Graphics` module is a candidate. This file has no output; it stands as a helper and is compiled together with the measured blocks that follow.

```swift
// Modules.swift — module surface, visible names and name lookup
struct Module {
    let name: String
    let exports: [String]
}

func visibleNames(of modules: [Module]) -> Set<String> {
    Set(modules.flatMap { $0.exports })
}

func qualified(_ name: String, from module: Module) -> Set<String>? {
    module.exports.contains(name) ? [module.name + "." + name] : nil
}

func owners(of name: String, in modules: [Module], local: Set<String>) -> Set<String>? {
    if local.contains(name) { return ["local." + name] }
    let found = modules.filter { $0.exports.contains(name) }.map { $0.name + "." + name }
    return found.count == 1 ? Set(found) : nil
}
```

## Importing Two Modules

When two modules are imported, the names both open are visible in the same scope. The measurement below builds a graphics module and a storage module and looks at the names they open with `visibleNames`. There is no shared name; for this reason no name replaces another, and no definition overrides another.

```swift
// main.swift — importing two modules collects the visible names
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let storage = Module(name: "Storage", exports: ["load", "save"])

let imports = Meeting("visible-names",
                      first: Set(graphics.exports),
                      second: Set(storage.exports),
                      result: visibleNames(of: [graphics, storage]))
imports.report()
```

```
visible-names: first Canvas,draw,render | second load,save | result Canvas,draw,load,render,save -> union
```

The result is the whole of both surfaces: all five names are visible, and none dropped. This is a union, and it is the ordinary result of adding a framework; when a new module is imported, the number of names the app can see does not fall, it grows. This is also the source of the danger: as the number of visible names grows, so does the chance that two modules open the same name.

**PS4.** This measurement is for a pair of two modules with no shared name. The case where the names of one module are a subset of the other's was not measured; in that case the meeting becomes `out-of-scope`.

## System Framework and Package

Where an imported module comes from does not change how names meet, but it changes who controls it. System frameworks are present on the device together with the operating system; the app links them at build time and uses the system's own copy while running. For this reason their version depends not on the app but on the version of the operating system it runs on, and whether a name exists can change with that version; we will measure this in the version lesson. A library added as a package is requested with a dependency declaration, resolved at build time and shipped with the app; the app chooses its version.

Each `import` line is valid for a single file. The names visible in a file are the union of the surfaces of the modules that file imports, plus the app's own definitions. Another file cannot see the top-level names of a module unless it imports the same module. So the meeting is not a one-time event for an app; it is a meeting set up separately for each file: a name on which two modules collide is experienced only by the files that import both.

## The Same Name and a Local Definition

When two modules open the same name and the use is not qualified, the lookup finds two candidates. There is no information about which one is meant, and the compiler does not pick one: there is no result, and the resolution is rejection. If the same name is also defined in the app's own source, the picture changes. The local definition comes before the imported one; that is, the imported name still exists but does not appear in the lookup, which is called shadowing, and the resolution is precedence. The measurement below builds both cases: the graphics and charts modules both open the name `Canvas`, and the app defines its own `render`.

```swift
// main.swift — the same name stays ambiguous, a local definition shadows it
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])
let local: Set = ["render"]

let cases = [
    Meeting("same-name",
            first: qualified("Canvas", from: graphics)!,
            second: qualified("Canvas", from: charts)!,
            result: owners(of: "Canvas", in: [graphics, charts], local: [])),
    Meeting("local-definition",
            first: qualified("render", from: graphics)!,
            second: owners(of: "render", in: [], local: local)!,
            result: owners(of: "render", in: [graphics, charts], local: local)),
]

for item in cases {
    item.report()
}
summarize(cases)
```

```
same-name: first Graphics.Canvas | second Charts.Canvas | result none -> rejection
local-definition: first Graphics.render | second local.render | result local.render -> precedence
precedence: 1
intersection: 0
union: 0
rejection: 1
```

In the `same-name` line the result is `none`: the lookup saw two candidates and chose neither. In the `local-definition` line the result is the second side, that is, the local definition; the imported `Graphics.render` dropped out. The surfaces of the same two modules had given a union in the previous measurement; the resolution of the meeting came not from the surfaces themselves but from who holds the name being looked up. The total of the surfaces causes no trouble as long as no name is looked up; once the lookup starts, the name being looked up has to resolve to a single definition.

An example outside the lesson that builds the same shadowing with a real system module follows. Because the piece depends on a platform module, it stands as source and no output is claimed for it.

```swift
// Source snippet: a local Data definition shadows the name Foundation.Data
import Foundation

struct Data {
    let bytes: [UInt8]
}

let own = Data(bytes: [1, 2])
let system: Foundation.Data = Foundation.Data()
```

The local `Data` takes the bare name; the type in the system framework stays reachable only by qualifying it with the module name. The model builds this rule of the platform: the lookup in the measured block reproduces the same precedence in the `local-definition` line.

**PS5.** This measurement is only for the precedence of name lookup and for the pair in which two imported modules open the same name. The case where a name is defined in both a type and a function, names resolved by overloading, and the case where three modules open the same name were not measured.

## The Surface a Module Opens

The surface a module opens to the outside (its public surface) is all the declarations that the code importing it can see: types, functions, constants, protocols. Declarations that stay inside the module do not enter the surface, and the importing code cannot reach them. This distinction is the practical face of the difference between a library and a framework. A library generally opens a small surface, and the calling code chooses which function to call and when. A framework opens a wider surface; a significant part of this surface consists of places the app is expected to fill: conforming to a protocol, subclassing a class, writing an event handler. The framework manages the flow, and the app supplies its own piece.

The width of the surface also determines the chance of name collisions. When a source file imports several modules, the names each module opens become visible in that file. As the number of modules grows and the surfaces widen, so does the chance that two modules open the same name. The compiler does not report this collision at import time; it reports it if a name cannot be resolved at the place where it is used. For this reason, in the `same-name` measurement of the previous sections, the resolution does not appear until the line that uses the name: the two definitions stand side by side, and the rejection is experienced only at the moment of use.

Another consequence is that a surface is a contract. The surface of a system framework comes with the system version, and the app cannot change it. The surface of a package is fixed by the version the app chose as a dependency, and when the party that writes the package changes it, the app is not affected until it selects the new version. Which party can change its surface at what speed determines how each dependency is to be handled; this is not the subject of the measurement, only the context of this section.

## When a Qualified Name Is Needed

A qualified name puts the module name in front of a name to say explicitly which module that name comes from. As long as there is no collision, qualification is unnecessary and makes reading harder; if a name exists in only one module, it already has a single meaning. When there is a collision, qualification is the way in the language to choose one of the two candidates.

Qualification works differently from a local definition. A local definition is a declaration written in the app's own module, and it comes before declarations of the same name opened by imported modules; the resolution is precedence, and the reader can reach this result even without seeing it. Qualification, on the other hand, makes it visible to the reader: the name itself says which module it comes from, and the resolution no longer depends on a meeting, because a single definition remains. This is also why, in the limiting measurement at the end of the lesson, the `qualified-name` meeting comes out out of scope: qualification does not make two definitions meet, it removes the meeting.

A practical rule is this: if there is no collision, do not qualify; if there is a collision and the local definition is not the wanted result, consider renaming instead of qualifying. Changing the name of an imported module may not be in your hands, but changing the name of a local definition is. Qualification is the tool for cases in which renaming is not possible.

## The Bounding Measurement

Rejection is not always the final state. When a use is qualified with a module name, the candidate set drops to one, because only the name in the written module is searched. This removes the meeting: the candidates before qualification are two, the candidate after is one, and the second is a subset of the first. The measurement below builds this.

```swift
// main.swift — a name qualified with its module narrows the candidates to one
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])

let all = qualified("Canvas", from: graphics)!.union(qualified("Canvas", from: charts)!)
let written = all.filter { $0.hasPrefix("Graphics.") }

let narrowed = Meeting("qualified-name", first: all, second: written, result: written)
narrowed.report()
```

```
qualified-name: first Charts.Canvas,Graphics.Canvas | second Graphics.Canvas | result Graphics.Canvas -> out-of-scope
```

The output is `out-of-scope`: the second candidate set is a subset of the first, so the two definitions no longer meet. Rejection is a situation that can be resolved. Here both colliding names belong to imported modules, and renaming either of them is not in your hands; this is exactly the case the rule above reserves for qualification, and the resolution is to qualify the name. A union causes no trouble as long as the name is not used.

**PS6.** This measurement is only for filtering by the `Module.name` prefix; the case in which the name of a module itself collides with the name of another module was not measured.

## Summary

- A framework calls the app's code in its own flow, while the app calls a library; both are imported as modules, and the names they open meet in the same scope.
- The surfaces of two modules give a union when there is no shared name: the number of visible names only grows.
- If the same name is used from two modules without qualification, the lookup gives no result and the resolution is rejection.
- A local definition shadows an imported name; the resolution of this meeting is precedence.
- A name qualified with its module narrows the candidate set to one and the meeting disappears: the output is `out-of-scope`.
- The resolution of this lesson: **union**.

## Next Step

In this lesson, two definitions met as names. In the next lesson what meets is a permission: the permissions the app asks for and what the user grants. The domain frameworks that touch the user's data or a capability of the device (location, health, media, augmented reality) ask for these permissions; the resolution is intersection.
