Kekuatan generik di Swift. Bagian 1

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 tipe

Apa 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) { // function body goes here } 

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

Source: https://habr.com/ru/post/id462753/


All Articles