Halo semuanya! Kami membagikan kepada Anda terjemahan yang disiapkan khusus untuk siswa kursus
“Pengembang iOS. Kursus Lanjutan .
" Selamat membaca.
Fungsi generik, tipe generik, dan batasan tipeApa itu obat generik?
Ketika mereka bekerja, Anda mencintai mereka, dan ketika tidak, Anda membenci mereka!Dalam kehidupan nyata, semua orang tahu kekuatan obat generik: bangun di pagi hari, memutuskan apa yang akan diminum, mengisi cangkir.
️
Swift adalah bahasa yang aman untuk mengetik. Setiap kali kita bekerja dengan tipe, kita perlu menentukannya secara eksplisit. Misalnya, kita memerlukan fungsi yang akan bekerja dengan lebih dari satu jenis. Swift memiliki tipe
Any
dan
AnyObject
, tetapi mereka harus digunakan dengan hati-hati dan tidak selalu. Menggunakan
Any
dan
AnyObject
akan membuat kode Anda tidak dapat diandalkan, karena tidak mungkin untuk melacak ketidakcocokan jenis selama kompilasi. Di sinilah obat generik datang untuk menyelamatkan.
Kode generik memungkinkan Anda untuk membuat fungsi yang dapat digunakan kembali dan tipe data yang dapat bekerja dengan semua jenis yang memenuhi batasan tertentu, sambil memastikan keamanan jenis selama kompilasi. Pendekatan ini memungkinkan Anda untuk menulis kode yang membantu menghindari duplikasi dan mengekspresikan fungsinya secara abstrak jelas. Misalnya, jenis seperti
Array
,
Set
dan
Dictionary
menggunakan generik untuk menyimpan elemen.
Katakanlah kita perlu membuat array yang terdiri dari nilai integer dan string. Untuk mengatasi masalah ini, saya akan membuat dua fungsi.
let intArray = [1, 2, 3, 4] let stringArray = [a, b, c, d] func printInts(array: [Int]) { print(intArray.map { $0 }) } func printStrings(array: [String]) { print(stringArray.map { $0 }) }
Sekarang saya perlu menampilkan array elemen tipe float atau array objek pengguna. Jika kita melihat fungsi-fungsi di atas, kita akan melihat bahwa hanya perbedaan jenis yang digunakan. Oleh karena itu, alih-alih menduplikasi kode, kita dapat menulis fungsi generik untuk digunakan kembali.
Sejarah obat generik di Swift

Fungsi Generik
Fungsi generik dapat bekerja dengan parameter universal apa pun dari tipe
T
Nama tipe tidak mengatakan apa-apa tentang
, tetapi mengatakan bahwa kedua array harus bertipe
, terlepas dari apa
itu. Jenis itu sendiri untuk digunakan sebagai ganti
ditentukan setiap kali fungsi
print(
_:
)
dipanggil.
func print<T>(array: [T]) { print(array.map { $0 }) }
Jenis generik atau polimorfisme parametrik
Tipe T generik dari contoh di atas adalah parameter tipe. Anda dapat menentukan beberapa parameter tipe dengan menulis beberapa nama parameter tipe dalam kurung sudut, dipisahkan dengan koma.
Jika Anda melihat Array dan Kamus <Key, Element>, Anda akan melihat bahwa mereka telah menamai parameter tipe, yaitu, Elemen dan Kunci, Elemen, yang berbicara tentang hubungan antara parameter tipe dan tipe atau fungsi generik yang digunakannya. .
Catatan: Selalu berikan nama untuk mengetik parameter dalam notasi CamelCase (misalnya,
T
dan
TypeParameter
) untuk menunjukkan bahwa mereka adalah nama untuk tipe, bukan nilai.
Jenis Generik
Ini adalah kelas khusus, struktur, dan enumerasi yang dapat bekerja dengan jenis apa pun, mirip dengan array dan kamus.
Mari kita buat tumpukan
import Foundation enum StackError: Error { case Empty(message: String) } public struct Stack { var array: [Int] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: Int) { array.append(element) } public mutating func pop() -> Int? { return array.popLast() } public func peek() throws -> Int { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) stack.pop() stack.pop() stack.push(element: 5) stack.push(element: 3) stack.push(element: 4) print(stack)
Sekarang stack ini hanya dapat menerima elemen integer, dan jika saya perlu menyimpan elemen dari tipe yang berbeda, saya akan perlu membuat stack lain, atau mengonversinya menjadi tampilan umum.
enum StackError: Error { case Empty(message: String) } public struct Stack<T> { var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) var strigStack = Stack<String>(capacity: 10) strigStack.push(element: "aaina") print(strigStack)
Keterbatasan Jenis Generik
Karena generik dapat dari jenis apa pun, Anda tidak dapat berbuat banyak dengannya. Terkadang berguna untuk menerapkan batasan pada tipe yang dapat digunakan dengan fungsi generik atau tipe generik. Pembatasan jenis menunjukkan bahwa parameter jenis harus cocok dengan protokol atau komposisi protokol tertentu.
Misalnya, tipe
Dictionary
Swift memberlakukan batasan pada tipe yang dapat digunakan sebagai kunci untuk kamus. Kamus mengharuskan kunci di-hash agar dapat memeriksa apakah sudah berisi nilai untuk kunci tertentu.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
Pada dasarnya, kami membuat tumpukan tipe T, tetapi kami tidak dapat membandingkan dua tumpukan, karena di sini jenisnya tidak cocok dengan Yang
Equatable
. Kita perlu mengubahnya untuk menggunakan
Stack<
T:
Equatable
>
.
Bagaimana cara kerja obat generik? Mari kita lihat sebuah contoh.
func min<T: Comparable>(_ x: T, _ y: T) -> T { return y < x ? y : x }
Kompiler tidak memiliki dua hal yang diperlukan untuk membuat kode fungsi:
- Ukuran variabel tipe T;
- Alamat-alamat dari kelebihan fungsi <, yang harus dipanggil pada saat run time.
Setiap kali kompiler menemukan nilai yang bertipe generik, ia menempatkan nilai dalam wadah. Wadah ini memiliki ukuran tetap untuk menyimpan nilai. Jika nilainya terlalu besar, Swift mengalokasikannya di heap dan menyimpan tautan ke dalamnya di dalam wadah.
Kompiler juga menyimpan daftar satu atau lebih tabel saksi untuk setiap parameter umum: satu tabel saksi untuk nilai, ditambah satu tabel saksi untuk setiap protokol pembatasan jenis. Tabel saksi digunakan untuk secara dinamis mengirim panggilan fungsi ke implementasi yang diinginkan saat runtime.
Akhir dari bagian pertama. Secara tradisi, kami menunggu komentar Anda, teman-teman.
Bagian kedua