Halo, Habr! Nama saya Vasily Kozlov, saya pimpinan teknologi iOS di Delivery Club, dan saya menemukan proyek ini dalam bentuk monolitiknya. Saya mengakui bahwa saya memiliki andil dalam apa yang artikel ini akan lawan, tetapi saya bertobat dan mengubah kesadaran saya bersama dengan proyek tersebut.
Saya ingin memberi tahu Anda bagaimana saya membagi proyek yang ada di Objective-C dan Swift menjadi modul terpisah - kerangka kerja. Menurut Apple , kerangka kerja adalah direktori dari struktur tertentu.
Awalnya, kami menetapkan tujuan: untuk mengisolasi kode yang mengimplementasikan fungsi chat untuk dukungan pengguna dan untuk mengurangi waktu build. Hal ini menyebabkan konsekuensi berguna yang sulit diikuti tanpa kebiasaan dan ada di dunia monolitik dari satu proyek.
Tiba-tiba, prinsip SOLID yang terkenal kejam mulai terbentuk, dan yang paling penting, rumusan masalah itu sendiri memaksa kami untuk mengatur kode sesuai dengan mereka. Dengan memindahkan entitas ke modul terpisah, Anda secara otomatis menemukan semua dependensinya, yang seharusnya tidak ada dalam modul ini, dan juga diduplikasi dalam proyek aplikasi utama. Oleh karena itu, pertanyaan tentang mengatur modul tambahan dengan fungsi umum sudah matang. Bukankah ini prinsip tanggung jawab tunggal, ketika satu entitas harus memiliki satu tujuan?
Kompleksitas membagi proyek dengan dua bahasa dan warisan yang besar ke dalam modul dapat membuat saya takut pada pandangan pertama, yang terjadi pada saya, tetapi minat pada tugas baru menang.
Dalam artikel yang ditemukan sebelumnya, penulis berjanjimasa depan tanpa awan dengan langkah-langkah sederhana dan jelas yang khas untuk proyek baru. Tetapi ketika saya memindahkan kelas dasar pertama ke modul untuk kode umum, begitu banyak dependensi yang tidak jelas terungkap, begitu banyak baris kode tercakup dalam warna merah di Xcode sehingga saya tidak ingin melanjutkan.
Proyek ini berisi banyak kode lama, ketergantungan silang pada kelas di Objective-C dan Swift, target berbeda dalam hal pengembangan iOS, daftar CocoaPods yang mengesankan. Setiap langkah menjauh dari monolit ini mengarah pada fakta bahwa proyek berhenti dibangun di Xcode, terkadang menemukan kesalahan di tempat yang paling tidak terduga.
Oleh karena itu, saya memutuskan untuk menuliskan urutan tindakan yang saya ambil untuk membuat hidup lebih mudah bagi pemilik proyek semacam itu.
Langkah pertama
Mereka jelas, banyak artikel telah ditulis tentang mereka . Apple telah mencoba membuatnya senyaman mungkin.
1. Buat modul pertama: File β Proyek Baru β Cocoa Touch Framework
2. Tambahkan modul ke ruang kerja proyek
3. Buat ketergantungan proyek utama pada modul, tentukan yang terakhir di bagian Binari Tertanam. Jika ada beberapa target dalam proyek ini, maka modul perlu dimasukkan ke dalam bagian Binari Tertanam dari setiap target yang bergantung padanya.
Saya hanya akan menambahkan satu komentar dari diri saya sendiri: jangan terburu-buru.
Tahukah Anda apa yang akan ditempatkan di modul ini, atas dasar apa modul tersebut akan dibagi? Dalam versi saya, seharusnya begitu
UIViewControlleruntuk mengobrol dengan tabel dan sel. Cocoapods dengan obrolan harus dilampirkan ke modul. Tapi ternyata sedikit berbeda. Saya harus menunda implementasi obrolan, karena UIViewControllerpenyaji dan bahkan selnya didasarkan pada kelas dasar dan protokol yang tidak diketahui oleh modul baru.
Bagaimana cara menyorot modul? Pendekatan paling logis - pada "fichamΒ» ( fitur ), yaitu untuk beberapa tugas pengguna. Misalnya, mengobrol dengan dukungan teknis, layar pendaftaran / login, lembar bawah dengan pengaturan layar utama. Selain itu, kemungkinan besar, Anda memerlukan beberapa jenis fungsionalitas dasar, yang bukan merupakan fitur, tetapi hanya sekumpulan elemen UI, kelas dasar, dll. Fungsionalitas ini harus dipindahkan ke modul umum yang mirip dengan file Utils yang terkenal... Jangan takut untuk membagi modul ini juga. Semakin kecil kubusnya, semakin mudah untuk memasukkannya ke dalam bangunan utama. Menurut saya, inilah cara satu lagi prinsip SOLID dapat dirumuskan .
Ada tip siap pakai untuk membagi menjadi modul, yang tidak saya gunakan, itulah sebabnya saya memecahkan begitu banyak salinan, dan bahkan memutuskan untuk berbicara tentang yang menyakitkan. Namun, pendekatan ini - pertama bertindak, kemudian berpikir - baru saja membuka mata saya terhadap kengerian kode dependen dalam proyek monolitik. Saat Anda berada di awal perjalanan, Anda merasa sulit untuk memahami jumlah penuh perubahan yang akan diperlukan untuk menghilangkan ketergantungan.
Jadi cukup pindahkan kelas dari satu modul ke modul lainnya, lihat apa yang tersipu di Xcode, dan coba untuk mencari tahu dependensinya. Xcode 10 rumit: ketika Anda memindahkan tautan ke file dari satu modul ke modul lainnya, ia meninggalkan file di tempat yang sama. Oleh karena itu, langkah selanjutnya akan seperti ini ...
4. Pindahkan file di file manager, hapus link lama di Xcode dan tambahkan kembali file ke modul baru. Jika Anda melakukan ini satu kelas dalam satu waktu, akan lebih mudah untuk tidak bingung dalam dependensi.
Untuk membuat semua entitas yang terpisah tersedia dari luar modul, Anda harus mempertimbangkan kekhasan Swift dan Objective-C.
5. Di Swift, semua kelas, enumerasi, dan protokol harus ditandai dengan pengubah akses
publickemudian mereka dapat diakses dari luar modul. Jika kelas dasar dipindahkan ke kerangka kerja terpisah, itu harus ditandai dengan pengubah open, jika tidak maka tidak akan berfungsi untuk membuat kelas turunan darinya.
Anda harus segera mengingat (atau mempelajari untuk pertama kalinya) apa itu level akses di Swift, dan dapatkan untung!
Saat mengubah tingkat akses untuk kelas porting, Xcode akan meminta Anda untuk mengubah tingkat akses untuk semua metode yang diganti menjadi sama.
Kemudian Anda perlu menambahkan impor kerangka kerja baru ke file Swift, tempat fungsionalitas yang dipilih digunakan, bersama dengan beberapa UIKit. Setelah itu, seharusnya ada lebih sedikit kesalahan di Xcode.
import UIKit
import FeatureOne
import FeatureTwo
class ViewController: UIViewController {
//..
}
Dengan Objective-C, urutannya sedikit lebih rumit. Selain itu, menggunakan header penghubung untuk mengimpor kelas Objective-C ke Swift tidak didukung dalam kerangka kerja.
Oleh karena itu, kolom Objective-C Bridging Header harus kosong dalam pengaturan framework.
Ada jalan keluar dari situasi ini, dan mengapa demikian menjadi topik untuk studi terpisah.
6. Setiap kerangka kerja memiliki file tajuk payung sendiri , yang melaluinya semua antarmuka Objective-C publik akan melihat ke dunia luar.
Jika Anda menentukan impor semua file header lainnya di header umbrella ini, maka file tersebut akan tersedia di Swift.
import UIKit
import FeatureOne
import FeatureTwo
class ViewController: UIViewController {
var vc: Obj2ViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
Di Objective-C, untuk mengakses kelas di luar modul, Anda harus bermain-main dengan pengaturannya: buat file header menjadi publik.
7. Ketika semua file telah ditransfer satu per satu ke modul terpisah, jangan lupa tentang Cocoapods. Podfile perlu diatur ulang jika beberapa fungsionalitas berakhir di kerangka kerja terpisah. Begitulah bagi saya: pod dengan indikator grafis harus dimasukkan ke dalam kerangka umum, dan obrolan - pod baru - dimasukkan ke dalam kerangka terpisahnya sendiri.
Perlu untuk secara eksplisit menunjukkan bahwa proyek tersebut sekarang bukan hanya proyek, tetapi ruang kerja dengan subproyek:
workspace 'myFrameworkTest'
Dependensi umum untuk kerangka kerja harus dipindahkan ke variabel terpisah, misalnya,
networkPodsdan uiPods:
def networkPods
pod 'Alamofire'
end
def uiPods
pod 'GoogleMaps'
end
Kemudian dependensi dari proyek utama akan dijelaskan sebagai berikut:
target 'myFrameworkTest' do
project 'myFrameworkTest'
networkPods
uiPods
target 'myFrameworkTestTests' do
end
end
Ketergantungan kerangka kerja dengan obrolan - begini:
target 'FeatureOne' do
project 'FeatureOne/FeatureOne'
uiPods
pod 'ChatThatMustNotBeNamed'
end
Batuan bawah air
Mungkin, ini bisa diselesaikan, tetapi kemudian saya menemukan beberapa masalah implisit, yang juga ingin saya sebutkan.
Semua dependensi umum dipindahkan ke satu kerangka kerja terpisah, mengobrol - ke yang lain, kodenya menjadi sedikit lebih bersih, proyek dibuat, tetapi macet saat dimulai.
Masalah pertama ada pada implementasi chat. Dalam luasnya jaringan, masalah juga terjadi di pod lain, hanya google " Library tidak dimuat: Alasan: gambar tidak ditemukan ". Dengan pesan inilah kejatuhan itu terjadi.
Saya tidak dapat menemukan solusi yang lebih elegan dan terpaksa menduplikasi koneksi pod dengan chat di aplikasi utama:
target 'myFrameworkTest' do
project 'myFrameworkTest'
pod 'ChatThatMustNotBeNamed'
networkPods
uiPods
target 'myFrameworkTestTests' do
end
end
Jadi, Cocoapods memungkinkan aplikasi untuk melihat pustaka yang ditautkan secara dinamis saat memulai dan saat proyek dikompilasi.
Masalah lainnya adalah sumber daya, yang dengan aman saya lupakan dan belum pernah saya lihat menyebutkan aspek ini untuk diingat. Aplikasi mogok saat mencoba mendaftarkan file xib sel: "Tidak dapat memuat NIB dalam paket" .
Konstruktor
init(nibName:bundle:)kelas UINibdefault mencari sumber daya dalam modul aplikasi utama. Secara alami, Anda tidak tahu apa-apa tentang ini ketika pengembangan dilakukan dalam proyek monolitik.
Solusinya adalah dengan menentukan bundel di mana kelas sumber daya didefinisikan, atau biarkan kompilator melakukannya sendiri menggunakan konstruktor
init(for:)kelasBundle... Dan, tentu saja, jangan lupa mulai sekarang bahwa sumber daya sekarang dapat digunakan untuk semua modul atau khusus untuk satu modul.
Jika modul menggunakan xibs, maka Xcode akan, seperti biasa, menawarkan tombol dan
UIImageViewmemilih sumber daya grafis dari keseluruhan proyek, tetapi pada saat berjalan semua sumber daya yang terletak di modul lain tidak akan dimuat. Saya memuat gambar dalam kode menggunakan konstruktor init(named:in:compatibleWith:)kelas UIImage, di mana parameter kedua Bundleadalah tempat file gambar berada.
Sel di dalam
UITableViewdan UICollectionViewsekarang juga harus didaftarkan dengan cara yang sama. Dan kita harus ingat bahwa kelas Swift dalam representasi string juga menyertakan nama modul, dan metode NSClassFromString()dari Objective-C akan kembalinil, jadi saya sarankan untuk mendaftarkan sel dengan menentukan bukan string, tetapi kelas. Untuk UITableViewAnda dapat menggunakan metode helper berikut:
@objc public extension UITableView {
func registerClass(_ classType: AnyClass) {
let bundle = Bundle(for: classType)
let name = String(describing: classType)
register(UINib(nibName: name, bundle: bundle), forCellReuseIdentifier: name)
}
}
kesimpulan
Sekarang Anda tidak perlu khawatir jika satu permintaan tarik berisi perubahan dalam struktur proyek yang dibuat di modul berbeda, karena setiap modul memiliki file xcodeproj sendiri. Anda dapat mendistribusikan pekerjaan sehingga Anda tidak perlu menghabiskan beberapa jam menyusun file proyek. Berguna untuk memiliki arsitektur modular dalam tim yang besar dan terdistribusi. Konsekuensinya, kecepatan pengembangan harus meningkat, tetapi sebaliknya juga terjadi. Saya menghabiskan lebih banyak waktu pada modul pertama saya daripada jika saya membuat obrolan di dalam monolit.
Kelebihan nyata yang juga ditunjukkan Apple, - kemampuan untuk menggunakan kembali kode. Jika aplikasi memiliki target yang berbeda (ekstensi aplikasi), maka ini adalah pendekatan yang paling dapat diakses. Mungkin obrolan bukanlah contoh terbaik. Saya seharusnya mulai dengan meletakkan lapisan jaringan, tetapi jujurlah dengan diri kami sendiri, ini adalah jalan yang sangat panjang dan berbahaya yang paling baik dipecah menjadi beberapa bagian kecil. Dan karena selama beberapa tahun terakhir ini adalah pengenalan layanan kedua untuk mengatur dukungan teknis, saya ingin menerapkannya tanpa memperkenalkannya. Dimana jaminan bahwa yang ketiga tidak akan segera muncul?
Satu efek halus saat mendesain modul adalah antarmuka yang lebih cerdas dan lebih bersih. Pengembang harus mendesain kelas sehingga properti dan metode tertentu dapat diakses dari luar. Mau tidak mau, Anda harus memikirkan apa yang harus disembunyikan dan bagaimana cara membuat modul tersebut agar dapat dengan mudah digunakan kembali.