مرحبا هبر! اسمي فاسيلي كوزلوف ، أنا قائد تقنية iOS في Delivery Club ، ووجدت المشروع في شكله الأحادي. أعترف أن لدي يدًا فيما توشك هذه المقالة على قتاله ، لكنني تبت وغيرت وعيي جنبًا إلى جنب مع المشروع.
أريد أن أخبركم كيف قسمت مشروعًا موجودًا في Objective-C و Swift إلى وحدات منفصلة - أطر عمل. وفقًا لشركة Apple ، فإن إطار العمل هو دليل لهيكل معين.
في البداية ، حددنا هدفًا: عزل الكود الذي ينفذ وظيفة الدردشة لدعم المستخدم وتقليل وقت الإنشاء. أدى هذا إلى عواقب مفيدة يصعب متابعتها بدون العادة والموجودة في العالم الأحادي لمشروع واحد.
فجأة ، بدأت مبادئ SOLID سيئة السمعة في التبلور ، والأهم من ذلك ، أجبرنا بيان المشكلة ذاته على تنظيم الكود وفقًا لها. من خلال نقل كيان إلى وحدة منفصلة ، فإنك تصادف تلقائيًا جميع تبعياته ، والتي لا ينبغي أن تكون في هذه الوحدة ، وأيضًا تتكرر في مشروع التطبيق الرئيسي. لذلك ، فإن مسألة تنظيم وحدة إضافية ذات وظائف مشتركة أصبحت ناضجة. أليس هذا هو مبدأ المسؤولية الفردية عندما يكون لكيان واحد هدف واحد؟
إن تعقيد تقسيم مشروع بلغتين وإرث كبير إلى وحدات يمكن أن يخيف للوهلة الأولى ، وهو ما حدث لي ، لكن الاهتمام بالمهمة الجديدة ساد.
في المقالات التي تم العثور عليها مسبقًا ، وعد المؤلفونمستقبل خالٍ من السحاب بخطوات بسيطة وواضحة نموذجية لمشروع جديد. ولكن عندما قمت بنقل الفئة الأساسية الأولى إلى الوحدة النمطية للرمز العام ، ظهر الكثير من التبعيات غير الواضحة ، حيث تمت تغطية العديد من أسطر التعليمات البرمجية باللون الأحمر في Xcode ولم أرغب في الاستمرار.
احتوى المشروع على الكثير من التعليمات البرمجية القديمة ، والاعتماد المتبادل على الفئات في Objective-C و Swift ، وأهداف مختلفة من حيث تطوير iOS ، وقائمة رائعة من CocoaPods. أدت أي خطوة بعيدًا عن هذا النظام المترابط إلى حقيقة أن المشروع توقف عن البناء في Xcode ، وأحيانًا يجد أخطاء في أكثر الأماكن غير المتوقعة.
لذلك ، قررت أن أكتب سلسلة الإجراءات التي اتخذتها لتسهيل الحياة لأصحاب مثل هذه المشاريع.
الخطوات الأولى
إنها واضحة ، وقد كتبت العديد من المقالات عنها . حاولت Apple جعلها سهلة الاستخدام قدر الإمكان.
1. أنشئ الوحدة الأولى: ملف ← مشروع جديد ← إطار عمل Cocoa Touch
2. أضف الوحدة النمطية إلى مساحة عمل المشروع
3. أنشئ تبعية المشروع الرئيسي في الوحدة النمطية ، مع تحديد الأخير في قسم الثنائيات المضمنة. إذا كان هناك العديد من الأهداف في المشروع ، فستحتاج الوحدة النمطية إلى تضمينها في قسم الثنائيات المضمنة لكل هدف يعتمد عليها.
سأضيف تعليقًا واحدًا فقط من نفسي: لا تتعجل.
هل تعرف ما الذي سيتم وضعه في هذه الوحدة ، وعلى أي أساس سيتم تقسيم الوحدات؟ في روايتي ، كان يجب أن يكون
UIViewControllerللدردشة مع الجدول والخلايا. يجب إرفاق Cocoapods مع الدردشة بالوحدة. لكن اتضح أن الأمر مختلف قليلاً. اضطررت إلى تأجيل تنفيذ الدردشة ، لأن UIViewControllerمقدمها وحتى الخلية كانت تستند إلى فئات أساسية وبروتوكولات لا تعرف الوحدة الجديدة شيئًا عنها.
كيف تسلط الضوء على وحدة؟ النهج الأكثر منطقية - على "ficham" ( الميزات ) ، أي لبعض مهام المستخدم. على سبيل المثال ، قم بالدردشة مع الدعم الفني وشاشات التسجيل / تسجيل الدخول والصحيفة السفلية مع إعدادات الشاشة الرئيسية. بالإضافة إلى ذلك ، على الأرجح ، ستحتاج إلى نوع من الوظائف الأساسية ، وهي ليست ميزة ، ولكنها فقط مجموعة من عناصر واجهة المستخدم ، والفئات الأساسية ، وما إلى ذلك. يجب نقل هذه الوظيفة إلى وحدة نمطية شائعة مماثلة لملف Utils الشهير... لا تخف من تقسيم هذه الوحدة أيضًا. كلما كانت المكعبات أصغر ، كان من الأسهل وضعها في المبنى الرئيسي. يبدو لي أن هذه هي الطريقة التي يمكن بها صياغة واحد من مبادئ SOLID .
هناك نصائح جاهزة للتقسيم إلى وحدات لم أستخدمها ، ولهذا كسرت العديد من النسخ ، وقررت التحدث عن النسخة المؤلمة. ومع ذلك ، فإن هذا النهج - العمل أولاً ، ثم التفكير - فتح عيني للتو على رعب الكود التابع في مشروع متجانسة. عندما تكون في بداية الرحلة ، يصعب عليك استيعاب المقدار الكامل للتغييرات المطلوبة للتخلص من التبعيات.
لذا فقط قم بنقل الفصل من وحدة إلى أخرى ، واطلع على ما هو أحمر الخدود في Xcode ، وحاول معرفة التبعيات. يعد Xcode 10 أمرًا صعبًا: عندما تنقل الروابط إلى الملفات من وحدة إلى أخرى ، فإنها تترك الملفات في نفس المكان. لذلك ستكون الخطوة التالية على هذا النحو ...
4. انقل الملفات في مدير الملفات ، واحذف الروابط القديمة في Xcode وأعد إضافة الملفات إلى الوحدة الجديدة. إذا قمت بفصل واحد في كل مرة ، فسيكون من الأسهل عدم الخلط بين التبعيات.
لإتاحة جميع الكيانات المنفصلة من خارج الوحدة النمطية ، عليك أن تأخذ في الاعتبار خصوصيات Swift و Objective-C.
5. في Swift ، يجب تمييز جميع الفئات والتعدادات والبروتوكولات بمعدِّل وصول
publicثم يمكن الوصول إليها من خارج الوحدة. إذا تم نقل فئة أساسية إلى إطار عمل منفصل ، فيجب تمييزها بمعدِّل open، وإلا فلن تعمل لإنشاء فئة فرعية منها.
يجب أن تتذكر على الفور (أو تعلم لأول مرة) ما هي مستويات الوصول في Swift ، وتحصل على ربح!
عند تغيير مستوى الوصول للفئة المنقولة ، سيطلب منك Xcode تغيير مستوى الوصول لجميع الطرق التي تم تجاوزها إلى نفس المستوى.
ثم تحتاج إلى إضافة استيراد إطار العمل الجديد إلى ملف Swift ، حيث يتم استخدام الوظيفة المحددة ، إلى جانب بعض UIKit. بعد ذلك ، يجب أن يكون هناك عدد أقل من الأخطاء في Xcode.
import UIKit
import FeatureOne
import FeatureTwo
class ViewController: UIViewController {
//..
}
مع Objective-C ، يكون التسلسل أكثر تعقيدًا بعض الشيء. أيضًا ، استخدام رأس تجسير لاستيراد فئات Objective-C إلى Swift غير مدعوم في إطارات العمل.
لذلك ، يجب أن يكون حقل Objective-C Bridging Header فارغًا في إعدادات إطار العمل.
هناك طريقة للخروج من هذا الموقف ، ولماذا هذا موضوع لدراسة منفصلة.
6. يحتوي كل إطار على ملف رئيسي خاص به ، والذي من خلاله ستنظر جميع واجهات Objective-C العامة إلى العالم الخارجي.
إذا حددت استيراد جميع ملفات الرأس الأخرى في رأس المظلة هذا ، فستكون متاحة في 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.
}
في Objective-C ، للوصول إلى فئات خارج الوحدة النمطية ، عليك أن تتلاعب بإعداداتها: اجعل ملفات الرأس عامة.
7. عندما يتم نقل جميع الملفات واحدة تلو الأخرى إلى وحدة منفصلة ، لا تنسى Cocoapods. يحتاج Podfile إلى إعادة التنظيم إذا انتهى الأمر ببعض الوظائف في إطار عمل منفصل. هكذا كان الأمر بالنسبة لي: كان لابد من إدخال الكبسولة ذات المؤشرات الرسومية في الإطار العام ، وتم تضمين الدردشة - الكبسولة الجديدة - في إطارها المنفصل.
من الضروري الإشارة صراحةً إلى أن المشروع ليس الآن مجرد مشروع ، ولكنه مساحة عمل بها مشاريع فرعية:
workspace 'myFrameworkTest'
يجب نقل التبعيات الشائعة للأطر إلى متغيرات منفصلة ، على سبيل المثال ،
networkPodsو uiPods:
def networkPods
pod 'Alamofire'
end
def uiPods
pod 'GoogleMaps'
end
ثم يتم وصف تبعيات المشروع الرئيسي على النحو التالي:
target 'myFrameworkTest' do
project 'myFrameworkTest'
networkPods
uiPods
target 'myFrameworkTestTests' do
end
end
تبعيات إطار العمل مع الدردشة - بهذه الطريقة:
target 'FeatureOne' do
project 'FeatureOne/FeatureOne'
uiPods
pod 'ChatThatMustNotBeNamed'
end
صخور تحت الماء
ربما يمكن الانتهاء من ذلك ، لكنني اكتشفت لاحقًا العديد من المشكلات الضمنية ، والتي أريد أيضًا أن أذكرها.
يتم نقل جميع التبعيات الشائعة إلى إطار عمل منفصل واحد ، والدردشة - إلى آخر ، أصبح الرمز أكثر وضوحًا ، والمشروع مبني ، لكنه يتعطل عندما يبدأ.
كانت المشكلة الأولى في تنفيذ الدردشة. في اتساع الشبكة ، تحدث المشكلة أيضًا في البودات الأخرى ، فقط google " المكتبة غير محملة: السبب: الصورة غير موجودة ". بهذه الرسالة حدث السقوط.
لم أتمكن من العثور على حل أكثر أناقة واضطررت إلى تكرار اتصال pod مع الدردشة في التطبيق الرئيسي:
target 'myFrameworkTest' do
project 'myFrameworkTest'
pod 'ChatThatMustNotBeNamed'
networkPods
uiPods
target 'myFrameworkTestTests' do
end
end
وبالتالي ، يسمح Cocoapods للتطبيق برؤية المكتبة المرتبطة ديناميكيًا عند بدء التشغيل وعند تجميع المشروع.
كانت هناك مشكلة أخرى وهي الموارد التي نسيتها بأمان ولم أر قط أي ذكر لهذا الجانب لأخذها في الاعتبار. تعطل التطبيق عند محاولة تسجيل ملف خلية xib: "تعذر تحميل NIB في الحزمة" . يبحث
مُنشئ
init(nibName:bundle:)الفئة UINibالافتراضي عن مورد في وحدة التطبيق الرئيسية. بطبيعة الحال ، لا تعرف أي شيء عن هذا عندما يتم تنفيذ التطوير في مشروع مترابط.
الحل هو تحديد الحزمة التي يتم فيها تعريف فئة الموارد ، أو السماح للمترجم بعمل ذلك بنفسه باستخدام مُنشئ
init(for:)الفئةBundle... وبالطبع ، لا تنس في المستقبل أن الموارد يمكن أن تكون مشتركة الآن في جميع الوحدات أو خاصة بوحدة واحدة.
إذا كانت الوحدة النمطية تستخدم xibs ، فسيقوم Xcode ، كالمعتاد ، بتقديم الأزرار
UIImageViewوتحديد الموارد الرسومية من المشروع بأكمله ، ولكن في وقت التشغيل لن يتم تحميل جميع الموارد الموجودة في الوحدات النمطية الأخرى. لقد قمت بتحميل الصور في الكود باستخدام مُنشئ init(named:in:compatibleWith:)الفئة UIImage، حيث يكون المعامل الثاني Bundleهو مكان ملف الصورة.
الخلايا في
UITableViewو UICollectionViewيجب الآن أيضا تسجيل بطريقة مماثلة. ويجب أن نتذكر أن فئات Swift في تمثيل السلسلة تتضمن أيضًا اسم الوحدة ، NSClassFromString()وإرجاع طريقة من Objective-Cnil، لذلك أوصي بتسجيل الخلايا من خلال تحديد ليس سلسلة ، ولكن فئة. ل UITableViewتتمكن من استخدام أسلوب مساعد التالية:
@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)
}
}
الاستنتاجات
الآن لا داعي للقلق إذا كان طلب السحب يحتوي على تغييرات في هيكل المشروع تم إجراؤها في وحدات مختلفة ، لأن كل وحدة لها ملف xcodeproj الخاص بها. يمكنك توزيع العمل بحيث لا تضطر إلى قضاء عدة ساعات في تجميع ملف المشروع معًا. من المفيد أن يكون لديك بنية معيارية في فرق كبيرة وموزعة. نتيجة لذلك ، يجب أن تزداد سرعة التطوير ، ولكن العكس هو الصحيح أيضًا. قضيت وقتًا أطول في الوحدة النمطية الأولى الخاصة بي مما لو كنت أقوم بإنشاء محادثة داخل وحدة متراصة.
من المزايا الواضحة التي تشير إليها Apple أيضًا، - القدرة على إعادة استخدام الكود. إذا كان التطبيق يحتوي على أهداف مختلفة (إضافات التطبيقات) ، فهذا هو النهج الأكثر سهولة. ربما لا تكون الدردشة أفضل مثال. كان يجب أن أبدأ بوضع طبقة الشبكة ، لكن لنكن صادقين مع أنفسنا ، فهذا طريق طويل جدًا وخطير ومن الأفضل تقسيمه إلى أقسام صغيرة. وبما أنه على مدار العامين الماضيين كان هذا بمثابة تقديم لخدمة ثانية لتنظيم الدعم الفني ، فقد أردت أن أضعها دون أن أقدمها. وأين الضمانات بأن الثالث لن يظهر قريباً؟
أحد التأثيرات غير الواضحة عند تطوير وحدة نمطية هو واجهات أكثر عمقًا ونظافة. يجب على المطور تصميم الفئات بحيث يمكن الوصول إلى خصائص وطرق معينة من الخارج. حتما ، عليك التفكير في ما تخفيه وكيفية صنع الوحدة بحيث يمكن استخدامها بسهولة مرة أخرى.