Swinject คือคอนเทนเนอร์ DI สำหรับ Swift ที่ใช้รูปแบบ Dependency Injection ในแอปพลิเคชัน iOS เฟรมเวิร์กจะทำให้การสร้างและการฉีด dependencies เป็นอัตโนมัติ ช่วยลดการจัดการออบเจ็กต์และ factory ด้วยตนเอง ตามข้อมูลจาก Swinject บน GitHub ไลบรารีรองรับ Constructor Injection, Property Injection และ Method Injection พร้อมระบบ scopes ที่ยืดหยุ่นสำหรับจัดการอายุการใช้งาน
ประเด็นสำคัญ
Swinject คือคอนเทนเนอร์ DI แบบโอเพนซอร์สสำหรับภาษา Swift ที่ออกแบบมาเพื่อทำให้การฉีด dependency ในแอปพลิเคชันสำหรับ iOS, macOS และ watchOS ง่ายขึ้น เฟรมเวิร์กใช้แนวทาง Service Locator: บริการต่างๆ จะถูกลงทะเบียนในคอนเทนเนอร์กลาง และคอนเทนเนอร์จะแก้ไขกราฟ dependency โดยอัตโนมัติเมื่อมีการร้องขออินสแตนซ์
Dependency Injection (DI) คือรูปแบบการออกแบบที่ออบเจ็กต์ได้รับ dependencies จากภายนอกแทนที่จะสร้างขึ้นภายใน ซึ่งช่วยลด การเชื่อมโยง ระหว่างคอมโพเนนต์ ทำให้การทดสอบหน่วยง่ายขึ้น และช่วยให้สามารถเปลี่ยนการใช้งานได้โดยไม่ต้องแก้ไขโค้ดของผู้บริโภค
ตามข้อมูลของ Martin Fowler (2004) DI เป็นกรณีเฉพาะของ Inversion of Control และดำเนินการผ่านการฉีดผ่านคอนสตรัคเตอร์, พรอพเพอร์ตี้ หรือเมธอด Swinject ทำให้กระบวนการนี้เป็นอัตโนมัติ ช่วยลดความจำเป็นในการเขียน factory และ service locator ด้วยตนเอง
ใช้ Swinject ในโปรเจกต์ที่มีบริการตั้งแต่สามรายการขึ้นไปที่มี dependencies ข้ามกัน ซึ่งการสร้างออบเจ็กต์ด้วยตนเองทำให้โค้ดการเริ่มต้นพองตัวและลดความสามารถในการทดสอบ
Swinject ผสานรวมกับระบบนิเวศของ Apple อย่างใกล้ชิดและรองรับ Swift ทุกเวอร์ชันตั้งแต่ 3.0 เป็นต้นไป เฟรมเวิร์กเข้ากันได้กับ Objective-C ผ่านสะพานเชื่อม ทำให้สามารถนำไปใช้ในโปรเจกต์ภาษาแบบผสมที่มีอยู่ได้โดยไม่ต้องย้ายโค้ดทั้งหมด ซึ่งมีความสำคัญอย่างยิ่งสำหรับแอปพลิเคชันขนาดใหญ่ที่มีประวัติการพัฒนามากกว่าห้าปี
คอนเทนเนอร์ Swinject ถูก implement โดยคลาส Container ซึ่งเก็บทะเบียนของบริการที่ลงทะเบียนไว้ เมื่อเรียกเมธอด resolve คอนเทนเนอร์จะสร้างออบเจ็กต์และแก้ไข dependencies ทั้งหมดแบบเรียกซ้ำผ่านกราฟการลงทะเบียน
Container คือออบเจ็กต์ศูนย์กลางที่ลงทะเบียนการจับคู่ระหว่างสิ่งที่เป็นนามธรรมและการใช้งานของมัน Service คือโปรโตคอลที่กำหนดสัญญา ในขณะที่ Component คือคลาสที่ implement โปรโตคอลนี้ การลงทะเบียนทำได้โดยใช้เมธอด register ซึ่งรับชนิดของบริการและ factory
let container = Container()
container.register(Networking.self) { _ in
NetworkService()
}
let service = container.resolve(Networking.self)
เมธอด resolve ส่งคืนอินสแตนซ์ของการใช้งานที่เป็นรูปธรรมที่ลงทะเบียนสำหรับโปรโตคอลที่ระบุ หากไม่ได้ลงทะเบียน dependency ไว้ คอนเทนเนอร์จะโยนข้อผิดพลาดร้ายแรงเพื่อให้ตรวจพบปัญหาได้อย่างรวดเร็วระหว่างการพัฒนา
แต่ละ การลงทะเบียน จะสร้างรายการที่มีฟังก์ชัน factory และขอบเขตที่เลือก บริการหนึ่งสามารถมีการลงทะเบียนหลายรายการในชื่อที่แตกต่างกัน ทำให้สามารถเลือกการใช้งานเฉพาะตามชื่อได้ — มีประโยชน์สำหรับสภาพแวดล้อมที่แตกต่างกัน (การพัฒนา, การทดสอบ, การผลิต)
กระบวนการแก้ไข dependency ทำงานแบบเรียกซ้ำ: เมื่อคอนเทนเนอร์สร้างอินสแตนซ์ Component จะวิเคราะห์ initializer และสำหรับพารามิเตอร์แต่ละตัวจะเรียก resolve ด้วยชนิดที่เกี่ยวข้อง หาก dependency มี dependencies ของตัวเองด้วย กระบวนการจะดำเนินต่อไปจนกว่ากราฟทั้งหมดจะถูกสร้างเสร็จสมบูรณ์ ความลึกของการซ้อนถูกจำกัดด้วยหน่วยความจำที่มีอยู่เท่านั้น แต่ในทางปฏิบัติแทบจะไม่เกินห้าระดับ
Swinject รองรับวิธีการฉีด dependency หลักสามวิธี ซึ่งแต่ละวิธีสามารถใช้ได้ขึ้นอยู่กับบริบททางสถาปัตยกรรม
Constructor Injection ฉีด dependencies ผ่านพารามิเตอร์ initializer นี่คือวิธีที่ต้องการ ซึ่งรับประกันว่าออบเจ็กต์จะอยู่ใน สถานะที่ถูกต้อง เสมอนับตั้งแต่เวลาที่สร้าง Swinject จะแก้ไข dependencies ทั้งหมดที่ส่งไปยังคอนสตรัคเตอร์โดยอัตโนมัติ
class LoginViewModel {
private let authService: AuthProtocol
init(authService: AuthProtocol) {
self.authService = authService
}
}
container.register(AuthProtocol.self) { _ in
AuthService()
}
container.register(LoginViewModel.self) { r in
LoginViewModel(authService: r.resolve(AuthProtocol.self)!)
}
Property Injection ฉีด dependencies โดยการตั้งค่าพรอพเพอร์ตี้ของออบเจ็กต์หลังจากการเริ่มต้น ใช้เมื่อ dependency เป็น ทางเลือก หรือไม่สามารถส่งผ่านคอนสตรัคเตอร์ได้ ตัวอย่างเช่น เมื่อทำงานกับ Storyboard ที่ view controller ถูกสร้างขึ้นโดยอัตโนมัติ Swinject รองรับคำอธิบายประกอบ @Inject สำหรับการฉีดพรอพเพอร์ตี้อัตโนมัติระหว่างรันไทม์โดยไม่ต้องเรียก resolve อย่างชัดเจน
เมื่อใช้ Property Injection สิ่งสำคัญคือต้องแน่ใจว่าได้ตั้งค่า dependency ก่อนการเข้าถึงออบเจ็กต์ครั้งแรก มิฉะนั้น พรอพเพอร์ตี้จะยังคงเป็น nil ซึ่งนำไปสู่การหยุดทำงานที่ไม่คาดคิด Swinject แก้ปัญหานี้ผ่านกลไก Implicitly Unwrapped Optional และการตรวจสอบที่เข้มงวดในขั้นตอนการแก้ไขกราฟ dependency
Method Injection ฉีด dependencies ผ่านพารามิเตอร์เมธอด ใช้สำหรับบริการที่จำเป็นสำหรับการดำเนินการเพียงครั้งเดียวเท่านั้นและไม่ควรจัดเก็บเป็นสถานะถาวรของออบเจ็กต์ นี่เป็นวิธีที่พบได้น้อยที่สุดแต่มีประโยชน์สำหรับ callbacks
ObjectScope คือกลไกที่กำหนดอายุการใช้งานของอินสแตนซ์ที่สร้างขึ้นภายในคอนเทนเนอร์ Swinject เฟรมเวิร์กมีขอบเขตในตัวสามแบบพร้อมความสามารถในการสร้างขอบเขตแบบกำหนดเองผ่าน ObjectScopeProtocol
ขอบเขต graph เป็นค่าเริ่มต้น การเรียก resolve แต่ละครั้งจะสร้างอินสแตนซ์ใหม่ที่คงอยู่เฉพาะในช่วงระยะเวลาของการแก้ไขกราฟ dependency เท่านั้น เป็นตัวเลือกที่ปลอดภัยสำหรับบริการไร้สถานะเนื่องจากช่วยลดการรั่วไหลของหน่วยความจำจากการแคช
ขอบเขต container เป็นซิงเกิลตันภายในคอนเทนเนอร์ อินสแตนซ์จะถูกสร้างขึ้นครั้งเดียวเมื่อมีการ resolve ครั้งแรกและส่งคืนสำหรับคำขอที่ตามมาทั้งหมด เหมาะสำหรับบริการที่มีสถานะร่วมกัน: แคชข้อมูล, ตัวบันทึก, การตั้งค่าแอปพลิเคชัน
ขอบเขต transient สร้างอินสแตนซ์ใหม่ในการเรียก resolve แต่ละครั้งโดยไม่มีการแคช ใช้สำหรับออบเจ็กต์ที่มีน้ำหนักเบาซึ่งไม่จำเป็นต้องนำกลับมาใช้ใหม่ — ตัวอย่างเช่น โมดูลที่จัดการคำขอ HTTP ที่เฉพาะเจาะจง
| ขอบเขต | อายุการใช้งาน | การใช้งานที่แนะนำ |
|---|---|---|
| graph | ตลอดระยะเวลาการแก้ไขกราฟ | บริการไร้สถานะโดยค่าเริ่มต้น |
| container | ตลอดอายุการใช้งานของคอนเทนเนอร์ | ซิงเกิลตัน: แคช, ตัวบันทึก, ไคลเอ็นต์เครือข่าย |
| transient | ไม่มีการแคช | ออบเจ็กต์น้ำหนักเบาสำหรับการใช้ครั้งเดียว |
การรวม Swinject เข้ากับโปรเจกต์ iOS จริงเริ่มต้นด้วยการเริ่มต้นคอนเทนเนอร์เมื่อเริ่มต้นแอปพลิเคชัน — ใน AppDelegate หรือฉาก ขอแนะนำให้จัดโครงสร้างการลงทะเบียนผ่าน Assembly: คลาสหรือโครงสร้างแยกต่างหากที่จัดกลุ่มบริการที่เกี่ยวข้อง
จากการสำรวจของ Swift Developer Community (2025) พบว่า 43% ของนักพัฒนา iOS ใช้คอนเทนเนอร์ DI ในโปรเจกต์เชิงพาณิชย์เพื่อจัดการ dependencies ของเลเยอร์เครือข่าย, พื้นที่เก็บข้อมูล และตัวประสานงานการนำทาง Swinject ยังคงเป็นโซลูชันที่ได้รับความนิยมมากที่สุดเนื่องจาก ไวยากรณ์ ที่น้อยที่สุดและความเข้ากันได้กับ Objective-C
Storyboard Injection เป็นคุณสมบัติเฉพาะของ Swinject: คอนเทนเนอร์จะฉีด dependencies ไปยัง view controller ที่สร้างจาก Storyboard โดยอัตโนมัติโดยไม่ต้องใช้โค้ดเพิ่มเติมใน AppDelegate ซึ่งใช้ตัวแก้ไขพิเศษที่ส่งไปยัง UIStoryboard ผ่านเมธอด init(container:) ที่ขัดขวางการสร้าง view controller และฉีด dependencies ที่ลงทะเบียนไว้
ในโปรเจกต์ขนาดใหญ่ สามารถรวม Swinject เข้ากับตัวประสานงานการนำทาง: ตัวประสานงานรับคอนเทนเนอร์และสร้างหน้าจอโดยการแก้ไข dependencies ผ่าน resolve โดยคงจุดการกำหนดค่าเดียวสำหรับทั้งฉาก
สถาปัตยกรรม Assembly เป็นรูปแบบที่แนะนำสำหรับการจัดระเบียบการลงทะเบียน แต่ละ Assembly จัดกลุ่มบริการที่เกี่ยวข้อง (เช่น NetworkingAssembly, DatabaseAssembly) และสามารถขึ้นอยู่กับ Assembly อื่นๆ เมื่อเริ่มต้นคอนเทนเนอร์ Assembly ทั้งหมดจะถูกโหลดและลงทะเบียนบริการของตน ทำให้เกิดการแยกหน้าที่ชัดเจนและทำให้การนำทางผ่านการกำหนดค่า DI ง่ายขึ้นในโปรเจกต์ขนาดใหญ่ที่มีบริการหลายสิบรายการ
สำหรับการดีบักกราฟ DI Swinject มีส่วนขยาย SwinjectPropertyLoader ซึ่งโหลดการกำหนดค่าจากไฟล์ plist และ SwinjectStoryboard — การรวม storyboard ผ่านเวอร์ชันพิเศษของ UIStoryboard เครื่องมือเหล่านี้มีประโยชน์อย่างยิ่งเมื่อย้ายโปรเจกต์ที่มีอยู่จากการสร้างออบเจ็กต์ด้วยตนเองไปยัง DI: นักพัฒนาสามารถลงทะเบียนบริการได้ทีละน้อย โดยตรวจสอบกราฟ dependency ผ่านการทดสอบและการบันทึกข้อผิดพลาดในการแก้ไขโดยไม่หยุดการพัฒนาฟีเจอร์หลัก
Swinject ยังมีการรวมกับ RxSwift และ Combine ผ่านส่วนขยาย SwinjectAutoregistration สำหรับการแก้ไข dependency อัตโนมัติตามชนิดพารามิเตอร์ initializer โดยไม่ต้องลงทะเบียน factory อย่างชัดเจน ซึ่งช่วยลดปริมาณโค้ดการลงทะเบียนสำหรับบริการที่เรียบง่าย: เพียงเรียก container.register(ServiceProtocol.self) โดยไม่ต้องระบุ factory และ Swinject จะสร้าง factory โดยอัตโนมัติตามการสะท้อน Signal ที่มีให้โดยรันไทม์ Swift วิธีการนี้แนะนำสำหรับบริการที่คอนสตรัคเตอร์ยอมรับเฉพาะชนิดพื้นฐานและไม่ต้องใช้ตรรกะการสร้างที่ซับซ้อน
คำถามที่พบบ่อย
Swinject เขียนด้วย Swift บริสุทธิ์โดยไม่มีการสร้างโค้ดหรือการสะท้อน ต่างจาก Needle ที่ไม่ต้องการการสร้างซอร์ส และเมื่อเทียบกับ Dip แล้ว ให้การสนับสนุน Storyboard Injection ในตัว ซึ่งทำให้การรวมเข้ากับโปรเจกต์ UIKit ที่มีอยู่ทำได้ง่ายขึ้น
เพิ่มแพ็คเกจผ่าน URL github.com/Swinject/Swinject ผ่าน Xcode ในเมนู File — Add Packages นอกจากนี้ยังสามารถติดตั้งผ่าน CocoaPods และ Carthage ได้อีกด้วย หลังจากติดตั้ง ให้นำเข้าโมดูล Swinject และสร้างอินสแตนซ์ Container
ได้ Swinject เข้ากันได้อย่างสมบูรณ์กับ SwiftUI dependencies จะถูกฉีดผ่าน initializer ของ View หรือผ่าน Environment โดยที่คอนเทนเนอร์จะถูกส่งต่อเป็น EnvironmentObject Swinject ไม่ขึ้นอยู่กับ UIKit และทำงานได้ดีเท่าเทียมกันกับทั้งสองเฟรมเวิร์ก
สร้าง คอนเทนเนอร์ แยกต่างหากสำหรับการทดสอบ โดยแทนที่บริการจริงด้วย mock Swinject อนุญาตให้แทนที่การลงทะเบียนโดยไม่ต้องเปลี่ยนโค้ดของผู้บริโภค การทดสอบแต่ละครั้งจะได้รับคอนเทนเนอร์ที่แยกออกมาพร้อมชุด dependencies ที่น้อยที่สุด
สำหรับ การวิเคราะห์ ให้ใช้ขอบเขต container เพื่อให้ทุกหน้าจอส่งเหตุการณ์ผ่านอินสแตนซ์เดียว ซึ่งรับประกันคิวการส่งที่เป็นหนึ่งเดียวและการดำเนินการรวมกลุ่มแบบแบตช์ที่ถูกต้องโดยไม่มีการทำซ้ำข้อมูลระหว่างผู้บริโภคที่แตกต่างกัน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ