Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
100: Creating a Multi-Target Swift Package
I've spent a lot of time building libraries that I want to share across multiple projects, and I've noticed a pattern: I usually start with a single library target, but then I realize I need a way to test it or interact with it without booting up a full iOS or macOS app. For this session, let's build a NetworkLogger package. The goal is to have a core logic library that any app can use, but also a standalone Command Line Interface (CLI) tool that can parse those logs from a file.
Starting with a Simple Core
First, I'll just spin up a basic package. I'm going to create a library called NetworkLoggerCore. This is where the heavy lifting happens—formatting the HTTP requests and responses into something readable.
// Package.swift
let package = Package(
name: "NetworkLogger",
products: [
.library(name: "NetworkLoggerCore", targets: ["NetworkLoggerCore"]),
],
targets: [
.target(name: "NetworkLoggerCore"),
]
)
I'll throw a simple LogFormatter class into Sources/NetworkLoggerCore/LogFormatter.swift. At this point, everything is fine. It compiles, it's a standard library. But here is where the limitation hits: if I want to verify how a specific log file looks when parsed, I have to create a whole new Xcode project, import this package, and run it. That's just too much friction.
The "Wait, Where Does the Code Go?" Moment
I want a CLI tool. I'll just add another target to my Package.swift. I'll call it NetworkLoggerCLI and make it an executableTarget.
// Updated Package.swift
targets: [
.target(name: "NetworkLoggerCore"),
.executableTarget(name: "NetworkLoggerCLI"),
]
I hit build, and immediately, SwiftPM screams at me. It says it can't find any source files for NetworkLoggerCLI. Now, if you're new to multi-target packages, you might think you can just put all your files in one folder and let the manifest sort it out. I tried that early in my career, and it's a nightmare of naming collisions. SwiftPM expects a strict directory mapping.
I need to look at my file tree. Right now I have:
Sources/NetworkLoggerCore/LogFormatter.swift
I need to create a sibling directory for the CLI tool. I'll move my main.swift (the entry point for any executable) into its own folder:
Sources/NetworkLoggerCLI/main.swift
Linking the Targets
Now it compiles, but when I try to use LogFormatter inside main.swift, I get a "Use of unresolved identifier" error. I forgot the most important part of a multi-target setup: targets are isolated by default. Just because NetworkLoggerCLI is in the same package as NetworkLoggerCore doesn't mean it can see its code.
I need to explicitly tell the CLI target that it depends on the core library. I'll adjust the targets array again.
targets: [
.target(name: "NetworkLoggerCore"),
.executableTarget(
name: "NetworkLoggerCLI",
dependencies: ["NetworkLoggerCore"] // This is the magic link
),
]
I'll also update the products section. I want the library to be available for app developers, but I also want the CLI tool to be a distributable product.
products: [
.library(name: "NetworkLoggerCore", targets: ["NetworkLoggerCore"]),
.executable(name: "netlog-tool", targets: ["NetworkLoggerCLI"]),
]
Now, when I run swift run netlog-tool, it works. I've effectively split my package into a "Logic" layer and a "Tooling" layer. The logic stays lean and has no dependencies on the CLI's environment, while the CLI can leverage everything in the core library without duplicating a single line of code.
📋 Practical Task
Build a DataValidator Package with a CLI Inspector
Create a Swift Package named DataValidator that consists of two targets:
- DataValidatorCore: A library target containing a struct called
Validatorwith a methodisValid(input: String) -> Boolthat returns true if the input string is not empty and contains at least one digit. - ValidatorInspector: An executable target that depends on
DataValidatorCore. Itsmain.swiftshould take a command-line argument (a string), pass it to theValidator, and print whether the string is valid or invalid.
Ensure your folder structure correctly separates the sources for both targets and that the Package.swift manifest defines both a library and an executable product.
There are no comments for now.