SPM (Swift Package Manager) เป็นตัวจัดการแพ็คเกจในตัวของระบบนิเวศ Swift ที่พัฒนาโดย Apple เพื่อทำให้การเชื่อมต่อ สร้าง และอัปเดตไลบรารีของบุคคลที่สามเป็นอัตโนมัติ SPM เป็นส่วนหนึ่งของคอมไพเลอร์ Swift ตั้งแต่เวอร์ชัน 3.0 (ปี 2016) และไม่ต้องการการติดตั้งแยกต่างหาก แตกต่างจาก CocoaPods และ Carthage ตรงที่ SPM ผสานรวมโดยตรงกับคอมไพเลอร์และ Xcode ทำให้เป็นเครื่องมือมาตรฐานสำหรับการจัดการการพึ่งพาในโปรเจกต์ Swift สมัยใหม่ ในบทความนี้เราจะอธิบายโครงสร้างของ Package.swift คำสั่ง SPM การสร้างแพ็คเกจของตัวเอง และการย้ายจากตัวจัดการอื่น
ประเด็นสำคัญ
SPM (Swift Package Manager) เป็นตัวจัดการแพ็คเกจอย่างเป็นทางการสำหรับภาษา Swift ที่สร้างอยู่ในคอมไพเลอร์ swiftc และสภาพแวดล้อมการพัฒนา Xcode มันช่วยให้นักพัฒนาสามารถเพิ่มไลบรารีของบุคคลที่สาม จัดการเวอร์ชัน และเผยแพร่แพ็คเกจของตนเองได้ SPM ปรากฏครั้งแรกใน Swift 3.0 (กันยายน 2016) ในฐานะเครื่องมือบรรทัดคำสั่ง และตั้งแต่ Xcode 11 (2019) ได้รับการผสานรวมอย่างสมบูรณ์กับอินเทอร์เฟซกราฟิก — ปัจจุบันการพึ่งพาจะถูกเพิ่มผ่านเมนู File → Add Packages
SPM ดาวน์โหลดซอร์สโค้ดของการพึ่งพาจากที่เก็บ Git โดยอัตโนมัติ สร้างแบบขนานกับโปรเจกต์หลัก และแคชผลลัพธ์เพื่อให้การสร้างครั้งต่อไปเร็วขึ้น แตกต่างจาก CocoaPods ตรงที่ SPM ไม่สร้างเวิร์กสเปซแยกต่างหาก (xcworkspace) — การพึ่งพาจะกลายเป็นส่วนหนึ่งของโปรเจกต์ Xcode หลัก ตามแบบสำรวจ Swift.org Developer Survey (2024) นักพัฒนา iOS 67% ใช้ SPM ทำให้เป็นเครื่องมือจัดการการพึ่งพาที่ได้รับความนิยมมากที่สุดในระบบนิเวศ Swift
SPM รองรับสามแพลตฟอร์ม: Apple (iOS, macOS, tvOS, watchOS, visionOS), Linux (Ubuntu, CentOS, Amazon Linux) และ Swift ฝั่งเซิร์ฟเวอร์ (Vapor, Kitura) บน Linux SPM ทำงานผ่านบรรทัดคำสั่งโดยไม่มี Xcode
SPM สร้างขึ้นจากแนวคิดหลักสามประการ: แพ็คเกจ (packages), ผลิตภัณฑ์ (products) และ เป้าหมาย (targets) แพ็คเกจคือที่เก็บ Git ที่มีแมนิเฟสต์ Package.swift ผลิตภัณฑ์คือผลลัพธ์ของการสร้าง (ไลบรารีหรือไฟล์ปฏิบัติการ) เป้าหมายคือโมดูลภายในแพ็คเกจที่ถูกคอมไพล์เป็นหน่วยสร้าง
เมื่อนักพัฒนาเพิ่มการพึ่งพาใน Package.swift SPM จะดำเนินการตามขั้นตอนต่อไปนี้:
~Library/Caches/org.swift.swiftpm/ไฟล์ Package.resolved ล็อกเวอร์ชันที่แน่นอนของการพึ่งพาทั้งหมด เพื่อให้ทีมพัฒนาทำงานกับชุดไลบรารีที่เหมือนกัน ไฟล์นี้ควรถูกเพิ่มในระบบควบคุมเวอร์ชัน (git)
ข้อได้เปรียบหลักของ SPM เหนือทางเลือกอื่นคือ การไม่มีรีจิสทรีแบบรวมศูนย์ แพ็คเกจสามารถอยู่ในที่เก็บ Git สาธารณะใดก็ได้: GitHub, GitLab, Bitbucket รวมถึงบนเซิร์ฟเวอร์ Git ส่วนตัวของบริษัท ตั้งแต่ Swift 5.2 SPM รองรับ การพึ่งพาแบบไบนารี (binary targets) — ไลบรารีแบบปิดที่แจกจ่ายเป็น XCFramework โดยไม่ให้ซอร์สโค้ด
Package.swift เป็นไฟล์ Swift ที่อธิบายโครงสร้างของแพ็คเกจและการพึ่งพาของมัน ไฟล์เขียนด้วย Swift เอง (ไม่ใช่ JSON หรือ YAML) ซึ่งช่วยให้可以使用ตรรกะแบบมีเงื่อนไข ค่าคงที่ที่คำนวณได้ และฟังก์ชันภายในแมนิเฟสต์
โครงสร้างพื้นฐานของ Package.swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyLibrary",
platforms: [
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]
),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git",
from: "5.9.0"),
.package(url: "https://github.com/onevcat/Kingfisher.git",
from: "7.12.0"),
],
targets: [
.target(
name: "MyLibrary",
dependencies: [
"Alamofire",
"Kingfisher"
]
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]
),
]
)
มาวิเคราะห์องค์ประกอบสำคัญ:
// swift-tools-version: 5.9 — คำสั่งที่ระบุเวอร์ชัน SPM; ไวยากรณ์แมนิเฟสต์ที่ใช้ได้ขึ้นอยู่กับสิ่งนี้name — ชื่อแพ็คเกจที่แสดงใน Xcode และใช้ในลิงก์การพึ่งพาplatforms — เวอร์ชันแพลตฟอร์มขั้นต่ำ; SPM จะไม่อนุญาตให้สร้างแพ็คเกจบนเวอร์ชัน OS ที่เก่ากว่าproducts — สิ่งที่แพ็คเกจ "ส่งออก": ไลบรารี (.library) หรือไฟล์ปฏิบัติการ (.executable)dependencies — รายการแพ็คเกจภายนอกพร้อม URL และเวอร์ชัน; รองรับ from:, exact:, branch:, revision:targets — เป้าหมายการสร้าง; แต่ละเป้าหมายประกอบด้วยรายการการพึ่งพา ทรัพยากร และไฟล์ swift จากไดเรกทอรีที่เกี่ยวข้อง (Sources/TargetName/)ตัวอย่างการระบุเวอร์ชันที่แน่นอน สาขา และ commit:
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
exact: "1.17.3"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture.git",
branch: "main"),
.package(url: "https://github.com/apple/swift-log.git",
revision: "e5c6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"),
]
ตั้งแต่ Swift 5.9 Package.swift เพิ่มการรองรับ static framework และ linkerSettings ซึ่งช่วยให้กำหนดค่าลิงเกอร์ได้แม่นยำยิ่งขึ้นสำหรับไลบรารีแบบสแตติกและไดนามิก
Swift Package Manager มีชุดคำสั่งสำหรับทำงานผ่านเทอร์มินัล คำสั่งจะรันจากไดเรกทอรีรากของแพ็คเกจ (ที่ซึ่ง Package.swift อยู่)
# สร้างแพ็คเกจใหม่พร้อมไลบรารี
swift package init --type library
# สร้างแพ็คเกจที่ปฏิบัติการได้ (แอปพลิเคชันคอนโซล)
swift package init --type executable
# สร้างโปรเจกต์
swift build
# สร้างในคอนฟิกูเรชัน release
swift build -c release
# รันทดสอบ
swift test
# รันทดสอบเฉพาะ
swift test --filter "MyLibraryTests/testExample"
# ดาวน์โหลดและแก้ไขการพึ่งพา
swift package resolve
# อัปเดตการพึ่งพาเป็นเวอร์ชันล่าสุดที่มี
swift package update
# แสดงกราฟการพึ่งพา
swift package show-dependencies
# ล้างแคชการสร้าง
swift package clean
# สร้างโปรเจกต์ Xcode (ก่อน Xcode 11)
swift package generate-xcodeproj
เมื่อทำงานภายใน Xcode คำสั่งเหล่านี้ส่วนใหญ่ทำงานโดยอัตโนมัติ: การพึ่งพาจะถูกแก้ไขเมื่อเปิดโปรเจกต์ การสร้างเริ่มด้วย ⌘B การทดสอบด้วย ⌘U อย่างไรก็ตาม การรู้คำสั่งเทอร์มินัลจำเป็นสำหรับไปป์ไลน์ CI/CD (GitHub Actions, GitLab CI, Jenkins) ที่ไม่มี Xcode
คำสั่ง swift package resolve สร้างหรืออัปเดตไฟล์ Package.resolved ไฟล์นี้ล็อกเวอร์ชันที่แน่นอนของการพึ่งพาทั้งหมด รวมถึงแบบทรานซิทีฟ และควรถูกเพิ่มใน git แนะนำให้รัน swift package update ก่อนสาขาคุณลักษณะใหม่แต่ละครั้งเพื่อทำงานกับเวอร์ชันไลบรารีที่เป็นปัจจุบัน
การสร้างแพ็คเกจ SPM ของคุณเองมีประโยชน์สำหรับการห่อหุ้มตรรกะทางธุรกิจในโปรเจกต์แบบหลายโมดูลและการเผยแพร่ไลบรารีโอเพนซอร์ส มาดูกระบวนการทีละขั้นตอน
mkdir MyNetworkKit
cd MyNetworkKit
swift package init --type library
คำสั่ง swift package init สร้างโครงสร้างดังนี้:
MyNetworkKit/
├── Package.swift
├── README.md
├── Sources/
│ └── MyNetworkKit/
│ └── MyNetworkKit.swift
└── Tests/
└── MyNetworkKitTests/
└── MyNetworkKitTests.swift
SPM จะสแกนไดเรกทอรี Sources/ และ Tests/ โดยอัตโนมัติ: แต่ละไดเรกทอรีย่อยภายใน Sources สอดคล้องกับเป้าหมาย (target) หนึ่งเป้าหมาย
มาเพิ่มการพึ่งพาและกำหนดค่าแพลตฟอร์มเป้าหมาย:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyNetworkKit",
platforms: [
.iOS(.v15),
.macOS(.v12)
],
products: [
.library(
name: "MyNetworkKit",
targets: ["MyNetworkKit"]
),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git",
from: "5.9.0"),
],
targets: [
.target(
name: "MyNetworkKit",
dependencies: ["Alamofire"]
),
.testTarget(
name: "MyNetworkKitTests",
dependencies: ["MyNetworkKit"]
),
]
)
// Sources/MyNetworkKit/MyNetworkKit.swift
import Foundation
import Alamofire
public struct NetworkClient {
private let session: Session
public init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
self.session = Session(configuration: configuration)
}
public func fetchData(from url: String) async throws -> Data {
let response = try await session.request(url).serializingData().value
return response
}
}
พุชแพ็คเกจไปยังที่เก็บ Git และสร้างแท็ก SemVer:
git init
git add .
git commit -m "Initial commit: MyNetworkKit"
git remote add origin https://github.com/username/MyNetworkKit.git
git push -u origin main
git tag 1.0.0
git push --tags
หลังจากนั้น นักพัฒนาคนใดก็สามารถเพิ่มแพ็คเกจของคุณโดยใช้ .package(url: "https://github.com/username/MyNetworkKit.git", from: "1.0.0")
Alamofire เป็น HTTP client ยอดนิยมที่สุดสำหรับ Swift มาเพิ่มผ่าน SPM และทำคำขอ GET กัน
import Alamofire
func fetchUsers() {
AF.request("https://jsonplaceholder.typicode.com/users")
.validate()
.responseDecodable(of: [User].self) { response in
switch response.result {
case .success(let users):
print("ได้รับ (users.count) ผู้ใช้")
case .failure(let error):
print("ข้อผิดพลาด: (error.localizedDescription)")
}
}
}
ไลบรารี Swinject ให้คอนเทนเนอร์ DI สำหรับ Swift เพิ่มผ่าน .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")
import Swinject
let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
container.register(DataRepositoryProtocol.self) { r in
DataRepository(networkService: r.resolve(NetworkServiceProtocol.self)!)
}
let repository = container.resolve(DataRepositoryProtocol.self)
repository?.loadData()
แพ็คเกจ swift-log จาก Apple ให้ API การบันทึกแบบรวมที่รองรับแบ็กเอนด์หลายตัว (OSLog, คอนโซล, ไฟล์)
import Logging
var logger = Logger(label: "com.myapp.network")
logger.logLevel = .debug
logger.info("เริ่มคำขอเครือข่าย", metadata: [
"url": "(requestURL)",
"method": "GET"
])
logger.warning("เวลาในการตอบสนองเกิน 2 วินาที")
logger.error("ข้อผิดพลาดการเชื่อมต่อ: ไม่มีอินเทอร์เน็ต")
สามตัวอย่างนี้ครอบคลุมสถานการณ์การใช้งาน SPM ทั่วไป: HTTP clients, DI containers และโครงสร้างพื้นฐานระบบ การเลือกไลบรารีไม่ใช่เรื่องบังเอิญ — Alamofire, Swinject และ swift-log อยู่ใน 20 อันดับแรกของแพ็คเกจ Swift ที่มีดาวมากที่สุดบน GitHub
หากโปรเจกต์ของคุณใช้ CocoaPods หรือ Carthage การย้ายไป SPM ทำได้ไม่กี่ขั้นตอน กระบวนการนี้ปลอดภัย: การพึ่งพาของ SPM สามารถอยู่ร่วมกับ CocoaPods และ Carthage ในโปรเจกต์เดียวกันได้ ทำให้ย้ายได้ทีละน้อย
.xcworkspace เปิด .xcodeproj และดำเนินการ Clean Build Folderrm -rf Carthage/ ในเทอร์มินัลณ ปี 2025 SPM รองรับไลบรารี Swift ยอดนิยมส่วนใหญ่ ข้อยกเว้นคือบางเฟรมเวิร์ก ObjC ที่ไม่มีแผนที่โมดูล หากไลบรารียังไม่รองรับ SPM — ตรวจสอบส่วน Installation ใน README ของมัน; ผู้เขียนส่วนใหญ่ได้เพิ่มการรองรับ SPM ในเวอร์ชันล่าสุดแล้ว
คำถามที่พบบ่อย
SPM สร้างอยู่ในคอมไพเลอร์ Swift และ Xcode ไม่ต้องติดตั้งผ่าน gem หรือ Homebrew CocoaPods ใช้รีจิสทรี Specs แบบรวมศูนย์และสร้างเวิร์กสเปซแยกต่างหาก Carthage ทำงานผ่านเฟรมเวิร์กโดยไม่ผสานรวมกับโปรเจกต์ SPM เป็นตัวจัดการเดียวที่ผสานรวมในระดับคอมไพเลอร์: การพึ่งพาจะถูกแก้ไข แคช และสร้างแบบขนานกับโค้ดหลัก
ได้ SPM รองรับโปรเจกต์แบบผสม Swift + Objective-C ไฟล์ ObjC ภายในแพ็คเกจ SPM จะถูกรวมใน Umbrella Header โดยอัตโนมัติหากมี modulemap ที่ถูกต้อง อย่างไรก็ตาม SPM ไม่รองรับไลบรารี ObjC แบบสแตติกที่ไม่มีแผนที่โมดูล แนะนำให้เชื่อมต่อไลบรารี ObjC ผ่าน SPM เฉพาะเมื่อมี modulemap หรือเขียนด้วย C บริสุทธิ์
SPM ใช้กำหนดเวอร์ชันเชิงความหมาย (SemVer) หากแพ็คเกจ A ต้องการ Alamofire 5.8+ และแพ็คเกจ B ต้องการ Alamofire 5.9+ SPM จะเลือกเวอร์ชัน 5.9.x ที่ตอบสนองทั้งคู่ หากข้อขัดแย้งไม่สามารถแก้ไขได้ (แพ็คเกจหนึ่งต้องการ 5.x อีกแพ็คเกจต้องการ 6.x) SPM จะรายงานข้อผิดพลาด ในกรณีนี้คุณต้องอัปเดตแพ็คเกจใดแพ็คเกจหนึ่งหรือเปลี่ยนการพึ่งพาเป็นเวอร์ชันที่เข้ากันได้กับทั้งสองข้อกำหนด
บน macOS: ~Library/Caches/org.swift.swiftpm/ และ ~/Library/Developer/Xcode/DerivedData/ บน Linux: ~cache/swiftpm/ ระหว่างการสร้าง SPM จะแคชซอร์สโค้ดและไฟล์ออบเจ็กต์ที่คอมไพล์แล้ว หากต้องการล้างแคชทั้งหมด ให้รัน swift package reset — คำสั่งนี้จะลบแคชการพึ่งพาและ DerivedData สำหรับโปรเจกต์ปัจจุบัน
ได้ ตั้งแต่ Swift 5.2 SPM รองรับเป้าหมายแบบไบนารี (binary targets) ไลบรารีแบบปิดถูกแจกจ่ายเป็น XCFramework และระบุพาธไปยัง .xcframework ใน Package.swift ซอร์สโค้ดจะไม่ถูกเปิดเผย เป้าหมายแบบไบนารีระบุผ่าน .binaryTarget(name: "PrivateSDK", path: "Sources/PrivateSDK.xcframework") ซึ่งช่วยให้เชื่อมต่อ SDK เชิงพาณิชย์โดยไม่ละเมิดข้อตกลงใบอนุญาต
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ