Generic (การเขียนโปรแกรมแบบทั่วไป) — เป็นกลไกของ Swift ที่ช่วยให้เขียนชนิดข้อมูลและฟึงก์ชันโดยมีพารามิเตอร์ชนิด
หัวข้อสำคัญ
Generic (ชนิดทั่วไป) — เป็นโครงสร้างทางไวยากรณ์ของ Swift ที่ช่วยให้เขียนโค้ดที่ยืดหยุ่นและสามารถใช้ซ้ำได้โดยมีการกำหนดพารามิเตอร์ชนิด แทนที่จะเป็นชนิดเฉพาะเจาะจง, ตัวเตือนตำแหน่งจะถูกเขียนในเครื่องหมายมุม — โดยทั่วไป
หากไม่มี generics, คุณจะต้องเขียนฟึงก์ชันแยกต่างสำหรับแต่ละชนิด: swapInts, swapStrings, swapDates ฟึงก์ชัน swap แบบ generic หนึ่งตัวแทนที่สามตัวแปรด้วยการประกาศเดียว ซึ่งช่วยลดการทำซ้ำ, ปรับปรุงความสามารถในการอ่านและลดโอกาสเกิดข้อผิดพลาดเมื่อคัดลอกโค้ด
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
ตัวแปลจัดเกิด Swift สร้าง โค้ดเฉพาะเจาะจง สำหรับแต่ละการใช้งานแบบเฉพาะเจาะจงของ generic ซึ่งหมายความว่า ฟึงก์ชัน generic swapValues
ฟึงก์ชัน generic คือฟึงก์ชันที่รับพารามิเตอร์ชนิดหนึ่งหรือมากกว่า พารามิเตอร์ชนิดจะถูกระบุหลังชื่อฟึงก์ชันในเครื่องหมายมุม:
ฟึงก์ชันสามารถมี หลาย พารามิเตอร์ generic ตัวอย่างเช่น, ฟึงก์ชัน findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
ข้อจำกัด K: Hashable และ where V: Equatable รับประกันว่า findKey สามารถเรียกได้เฉพาะกับพจนานุกรมที่มีคีย์ที่สามารถ hash ได้และค่าที่เปรียบเทียบได้ ข้อจำกัดเหล่านี้ไม่ใช่ระบบข้าราชการแต่เป็นความจำเป็น: หากไม่มี Hashable คุณจะไม่สามารถทำดัชนีการค้นหาโดยคีย์ได้, หากไม่มี Equatable คุณจะไม่สามารถเปรียบเทียบ value กับ dictValueได้
where — เป็นคำสำคัญของ Swift ที่ใช้กำหนดข้อจำกัดเพิ่มเติมบนพารามิเตอร์ generic แตกต่างจากการระบุโปรโตคอลในเครื่องหมายมุม (
ประโยก where จะถูกเขียนหลังลายมือฟึงก์ชันหรือชนิด, ก่อนเครื่องหมายปีกษาเปิด ใน where คุณสามารถระบุได้ว่า T: Comparable & Hashable (ปฏิบัติตามโปรโตคอลสองตัวพร้อมกัน) หรือ T.U == Int (การทำให้ associated type กลายเป็นรูปธรรม)
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
ส่วนขยายที่มี where จะเพิ่มเมธอดต่างๆ เฉพาะ สำหรับชนิดที่ตรงตามเงื่อนไข Container
Associated Types (ชนิดสมาชิก) — เป็นวิธีทำให้โปรโตคอลเป็น generic โดยไม่ต้องระบุชนิดเฉพาะเจาะจงในขณะประกาศ แทนที่จะเป็น
ด้วยการใช้ where คุณสามารถสร้าง ความสัมพันธ์ ระหว่าง associated types ของโปรโตคอลต่างๆ ตัวอย่างเช่น, คุณสามารถกำหนดได้ว่า Item ของโปรโตคอลหนึ่งตรงกับ Iterator.Element ของอีกโปรโตคอลหนึ่ง ซึ่งรับประกันความเข้ากันได้ของชนิดในระดับตัวแปลจัดเกิด
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack ทำให้ Element กลายเป็น Int โดยใช้ typealias ตัวแปลจัดเกิดจะตรวจสอบว่าข้อกำหนดทั้งหมดของโปรโตคอลได้รับการตรงตามด้วยชนิดนี้ หากไม่มี associated types, คุณจะต้องเขียน StackProtocol
ส่วนขยาย generic ช่วยให้เพิ่มเมธอดให้กับชนิด generic โดยมีข้อจำกัดเพิ่มเติม นี้คือรูปแบบ “การปฏิบัติตามแบบมีเงื่อนไข”, ซึ่งชนิดจะได้รับฟังก์ชันภายใต้เงื่อนไขบางอย่างเท่านั้น ซับสคริปต์ก็สามารถเป็น generic ได้
ซับสคริปต์สามารถเป็น generic ได้: มันรับพารามิเตอร์ generic ในเครื่องหมายมุม ซึ่งมีประโยชน์สำหรับการเข้าถึงคอลเลกชันอย่างปลอดภัยโดยใช้ดัชนีตามอำเหมาใจ, ในกรณีที่ดัชนีสามารถเป็นได้หลายชนิด
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
โครงสร้างนี้ให้ การรวมกัน ที่ทรงพลัง: เมธอด sum จะปรากฏเฉพาะในอาร์เรย์ตัวเลข, และซับสคริปต์ที่มีดัชนีตามอำเหมาใจจะทำงานกับลำดับของดัชนีเลขจำนวนเท่านั้น ตัวแปลจัดเกิดจะจัดการการมองเห็นของสมาชิกเหล่านี้โดยอัตโนมัติตามเงื่อนไข where
ข้อผิดพลาด เมื่อใช้ generic มักเกี่ยวข้องกับข้อจำกัดที่ไม่ถูกต้องหรือความพยายามใช้ generic ในที่ที่ต้องการชนิดเฉพาะเจาะจง มาดูสามสถานการณ์ทั่วไปที่นักพัณฐ์พบเจอ
การเพิ่มพารามิเตอร์ generic ในที่ชนิดเฉพาะเจาะจงก็เพียงพอเป็นรูปแบบที่ไม่ดี หากฟึงก์ชันทำงานกับ String เสมอ, ไม่จำเป็นต้องทำให้เป็น
การเรียกเมธอดบนพารามิเตอร์ generic โดยไม่ได้ระบุว่าเมธอดนั้นมีอยู่เป็นข้อผิดพลาด
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
ข้อผิดพลาดชัดเจน: generic
คำถามที่พบบ่อย
Generic เป็นวิธีเขียนโค้ดที่ทำงานกับชนิดใดก็ได้, โดยไม่ต้องทำซ้ำตรรกะเดียวกันสำหรับ Int, String และชนิดอื่นๆ แทนที่จะเป็นชนิดเฉพาะเจาะจง, จะใช้ตัวเตือนตำแหน่ง
Generic เก็บข้อมูลเกี่ยวกับชนิดเฉพาะเจาะจงไว้ในขณะเรียบเรียง, ทำให้ตัวแปลจัดเกิดสามารถตรวจสอบชนิดและสร้างโค้ดที่เพิ่มเติมแล้ว Any คือการลบชนิด: ค่าใดก็สามารถส่งไปได้, แต่ชนิดจะหายไปและต้องใช้การแปลงชนิด (as?)
ใช้ไวยากรณ์ T: SomeProtocol ในเครื่องหมายมุมหรือประโยก where แยกต่างแยก where T: SomeProtocol ข้อจำกัดรับประกันว่าพารามิเตอร์ generic รองรับเมธอดและคุณสมบัติเฉพาะเจาะจง
Associated Type (associatedtype) คือตัวเตือนตำแหน่งสำหรับชนิดภายในโปรโตคอล โปรโตคอลจะไม่ระบุชนิดเฉพาะเจาะจงแต่จะประกาศ associatedtype, ซึ่งจะถูกกำหนดเฉพาะเจาะจงในการนำไปใช้: Array มี Element, Dictionary มี Key และ Value
Swift แปลงโค้ด generic เป็น รุ่นเฉพาะเจาะจง สำหรับแต่ละชนิดเฉพาะเจาะจง ในทางปฏิบัติ, ฟึงก์ชัน generic สำหรับ Int ไม่ช้ากว่าฟึงก์ชันที่เขียนด้วยมือสำหรับ Int ไม่มีค่าใช้จ่ายเพิ่มสำหรับการเอนกลางอย่างเป็นนามธรรม
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ