مرحبا بالجميع!
اسمي دميتري. حدث ذلك أنني قائد فريق في فريق من 13 مطورًا لنظام iOS خلال العامين الماضيين. ومعاً نعمل على تطبيق Tinkoff Business .
أريد أن أشاركك تجربتنا حول كيفية إصدار التطبيق في لحظة غير متوقعة مع الحد الأقصى من الميزات أو إصلاحات الأخطاء ولا تزال غير رمادية.
سأخبرك عن الممارسات والأساليب التي ساعدت الفريق على تسريع التطوير والاختبار بشكل كبير وتقليل كمية الضغط ، والأخطاء ، والمشكلات المتعلقة بالإصدار غير المجدول أو العاجل. #MakeReleaseWithoutStress .
دعنا نذهب!
وصف المشكلة
تخيل الموقف التالي.
هناك إصدار آخر. سبقه اختبار الانحدار ، وجد المختبرون مرة أخرى مكانًا حيث يتم عرض معرف السطر بدلاً من النص في التطبيق.

كانت هذه واحدة من أكثر المشاكل التي واجهتنا.
قد لا تواجه هذه المشكلة إذا لم يكن التطبيق مترجمًا بلغة أخرى ، أو تمت كتابة كل الترجمة في سطور مباشرة في التعليمات البرمجية دون استخدام ملف Localizable.strings.
ولكن قد تواجه مشاكل أخرى سنساعدك على حلها:
السبب → التأثير
لماذا يحدث كل هذا؟
يوجد كود البرنامج الذي يجمع. إذا كتبت شيئًا خاطئًا (نحويًا أو اسم وظيفة غير صحيح عند الاتصال) ، فلن يتم تجميع مشروعك ببساطة. هذا أمر مفهوم ومفهوم ومنطقي.
ولكن ماذا عن أشياء مثل الموارد؟
لا يتم تجميعها ، يتم إضافتها ببساطة إلى الحزمة بعد تجميع الرمز. في هذا الصدد ، يمكن أن يحدث عدد كبير من المشاكل في وقت التشغيل ، على سبيل المثال ، الحالة الموضحة أعلاه - مع سلاسل في الترجمة.
ابحث عن حل
فكرنا في كيفية حل هذه المشاكل بشكل عام ، وكيف يمكننا إصلاح ذلك. تذكرت أحد مؤتمرات Cocoaheads على mail.ru. كان هناك حديث عن مقارنة أدوات توليد الكود.
بعد الاطلاع مرة أخرى على موضوع هذه الأدوات (المكتبات / الأطر) ، وجدنا أخيرًا ما هو مطلوب.
في الوقت نفسه ، تم استخدام نهج مماثل من قبل المطورين لنظام Android لسنوات. فكرت Google بها وجعلتها مثل هذه الأداة خارج الصندوق. لكن Apple ، حتى Xcode المستقرة ، لا يمكنها أن تفعلنا ...
كل ما تبقى هو معرفة الأداة التي تختارها فقط: Natalie أو SwiftGen أو R.swift ؟
لم يكن لدى ناتالي دعم التوطين ، فقد تقرر التخلي عنه على الفور. كان لدى SwiftGen و R.Swift قدرات متشابهة جدًا. لقد اخترنا R.swift ، بناءً على عدد النجوم ببساطة ، مع العلم أنه في أي وقت يمكننا التغيير إلى SwiftGen.
كيف يعمل R.swift
يتم إطلاق نص برمجي لمرحلة البناء قبل الترجمة ، ويتم تشغيله من خلال بنية المشروع R.generated.swift
ملفًا يسمى R.generated.swift
، والذي سيلزم إضافته إلى المشروع ( R.generated.swift
بالمزيد حول كيفية القيام بذلك في النهاية).
يحتوي الملف على الهيكل التالي:
import Foundation import Rswift import UIKit /// This `R` struct is generated and contains references to static resources. struct R: Rswift.Validatable { fileprivate static let applicationLocale = hostingBundle.preferredLocalizations.first.flatMap(Locale.init) ?? Locale.current fileprivate static let hostingBundle = Bundle(for: R.Class.self) static func validate() throws { try intern.validate() } // ... /// This `R.string` struct is generated, and contains static references to 2 localization tables. struct string { /// This `R.string.localizable` struct is generated, and contains static references to 1196 localization keys. struct localizable { /// en translation: Apple Pay /// /// Locales: en, ru static let card_actions_activate_apple_pay = Rswift.StringResource(key: "card_actions_activate_apple_pay", tableName: "Localizable", bundle: R.hostingBundle, locales: ["en", "ru"], comment: nil) // ... /// en translation: Apple Pay /// /// Locales: en, ru static func card_actions_activate_apple_pay(_: Void = ()) -> String { return NSLocalizedString("card_actions_activate_apple_pay", bundle: R.hostingBundle, comment: "") } } } }
إستعمال:
let str = R.string.localizable.card_actions_activate_apple_pay() print(str) > Apple Pay
"لماذا Rswift.StringResource
إلى Rswift.StringResource
؟" ، تسأل. أنا نفسي لا أفهم سبب إنشائه ، ولكن كما يوضح المؤلفون ، يلزم الأمر لما يلي: الرابط .
تطبيق حقيقي
شرح صغير للمحتوى أدناه:
* لقد كانوا - استخدموا النهج لفترة من الوقت ، في النهاية ، تركوه
* لقد أصبح - النهج الذي نستخدمه عند كتابة رمز جديد
* لم يكن كذلك ، ولكن يمكنك الحصول عليه - وهو نهج لم يكن موجودًا في طلبنا مطلقًا ، لكنني التقيت به في مشاريع مختلفة ، في تلك الأوقات البعيدة ، عندما لم أعمل في Tinkoff.ru حتى الآن.
التعريب
بدأنا في استخدام R.swift
للتوطين ، وقد أنقذنا من المشاكل التي كتبنا عنها في البداية. الآن ، إذا تغير المعرف في الأقلمة ، فلن يتم تجميع المشروع.
* يعمل هذا فقط إذا قمت بتغيير المعرف في جميع الترجمات إلى أخرى. إذا بقيت سلسلة في أي من الترجمات ، فسيظهر تحذير عند التجميع بأن هذا المعرف غير مترجم في جميع اللغات.

ليس هناك ، ولكن قد يكون لديك: final class NewsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() titleLabel.text = NSLocalizedString("news_title", comment: "News title") } }
كان: extension String { public func localized(in bundle: Bundle = .main, value: String = "", comment: String = "") -> String { return NSLocalizedString(self, tableName: nil, bundle: bundle, value: value, comment: comment) } } final class NewsViewController: UIViewController { private enum Localized { static let newsTitle = "news_title".localized() } override func viewDidLoad() { super.viewDidLoad() titleLabel.text = Localized.newsTitle } }
أصبح: titleLabel.text = R.string.localizable.newsTitle()
صور
الآن ، إذا قمنا بإعادة تسمية شيء ما في * .xcassets ، ولم نغير الكود ، فلن يتم تجميع المشروع ببساطة.
كان: imageView.image = UIImage(named: "NotExist") // imageView.image = UIImage(named: "NotExist")! // crash imageView.image = #imageLiteral(resourceName: "NotExist") // crash
أصبح: imageView.image = R.image.tinkoffLogo() //
القصص المصورة
كان: let someStoryboardName = "SomeStoryboard" // Change to something else (eg: "somestoryboard") - get nil or crash in else let someVCIdentifier = "SomeViewController" // Change to something else (eg: "someviewcontroller") - get nil or crash in else let storyboard = UIStoryboard(name: someStoryboardName, bundle: .main) let _vc = storyboard.instantiateViewController(withIdentifier: someVCIdentifier) guard let vc = _vc as? SomeViewController else { // - , Fabric Firebase // fatalError() ¯\_(ツ)_/¯}
أصبح: guard let vc = R.storyboard.someStoryboard.someViewController() else { // - , Fabric Firebase // fatalError() ¯\_(ツ)_/¯ }
و هكذا.
التحقق من القصة المصورة
R.validate () هي أداة رائعة تتغلب على اليدين (أو بالأحرى ، تلقي خطأ في كتلة الصيد) إذا قمت بشيء خاطئ في لوحة القصة أو ملفات xib.
على سبيل المثال:
- أشارت إلى اسم الصورة التي ليست في المشروع
- أشاروا إلى الخط ، ثم توقفوا عن استخدامه وحذفوه من المشروع (من info.plist)
إستعمال:
final class AppDelegate: UIResponder { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { #if DEBUG do { try R.validate() } catch { // fatalError // debug // - , production fatalError(error.localizedDescription) } #endif return true } }
والآن أنت مستعد لشراء اثنين!

كيفية التنفيذ؟
* نظام قائم على المكونات - ويكي ، مفهوم تطوير الكود حيث يتم تطوير المكونات (مجموعة من الشاشات / الوحدات المتصلة ببعضها البعض) في بيئة مغلقة (في حالتنا ، في القرون المحلية) من أجل تقليل تماسك قاعدة الكود. كثير من الناس يعرفون النهج في الواجهة الخلفية ، والذي يقوم على هذا المفهوم - الخدمات المصغرة.
* Monolith - ويكي ، مفهوم تطوير الكود ، حيث تكمن قاعدة الكود بأكملها في مستودع واحد ، ويرتبط الكود ارتباطًا وثيقًا. هذا المفهوم مناسب للمشاريع الصغيرة مع مجموعة محدودة من الوظائف.
إذا كنت تقوم بتطوير تطبيق متآلف أو تستخدم تبعيات تابعة لجهة خارجية فقط ، فأنت محظوظ (لكن هذا ليس دقيقًا). خذ البرنامج التعليمي وافعل كل شيء بدقة.
لم تكن هذه قضيتنا. لقد تدخلنا. نظرًا لأننا نستخدم نظامًا قائمًا على المكونات ،
تضمين R.swift في التطبيق الرئيسي ، فقد قررنا تضمينه في القرون المحلية (وهي مكونات).
نظرًا للتحديث المستمر للترجمات والصور وجميع العناصر التي تؤثر على ملف R.generated.swift ، هناك العديد من التعارضات في الملف الذي تم إنشاؤه عند الدمج في الفرع المشترك. ولتجنب ذلك ، يجب عليك إزالة R.generated.swift من مستودع git. كما يوصي المؤلف بذلك .
أضف الأسطر التالية إلى .gitignore
.
# R.Swift generated files *.generated.swift
أيضًا ، إذا كنت لا تريد إنشاء رمز لبعض الموارد ، فيمكنك دائمًا استخدام تجاهل الملفات الفردية أو المجلدات بأكملها:
"${PODS_ROOT}/R.swift/rswift" generate "${SRCROOT}/Example" "--rswiftignore" "Example/.rswiftignore"
وصف .rswiftignore
كما هو الحال في المشروع الرئيسي ، كان من المهم بالنسبة لنا عدم إضافة ملفات R.generated.swift من القرون المحلية إلى مستودع git. بدأنا النظر في خيارات لكيفية القيام بذلك:
- الاسم المستعار على R.generated.swift بحيث تتم إضافة الملف (الاسم المستعار ، على سبيل المثال: R.swift) إلى المشروع ، وبعد ذلك ، عند الترجمة حسب المرجع ، يتوفر الملف الحقيقي. لكن cocoapods ذكية ، ولا يُسمح لها بذلك
- في podspec في مرحلة ما قبل الترجمة ، أضف ملف R.generated.swift إلى المشروع نفسه باستخدام البرامج النصية ، ولكن بعد ذلك سيتم إضافته ببساطة كملف في نظام الملفات ، ولن يظهر الملف في المشروع
- خيارات أخرى أكثر أو أقل مرتبة
السحر في podfile
سحر
pre_install do |installer| installer.pod_targets.flat_map do |pod_target| if pod_target.pod_target_srcroot.include? 'LocalPods'
- وخيار آخر ... لا يزال يضيف R.generated.swift إلى بوابة
استقرنا مؤقتًا على الخيار: "السحر في Podfile" ، على الرغم من حقيقة أنه كان لديه عدد من أوجه القصور:
- يمكن إطلاقه فقط من جذر المشروع (على الرغم من أنه يمكن تشغيل cocoapods من أي مجلد تقريبًا في المشروع)
- يجب أن تحتوي جميع القرون على مجلد يسمى المصادر (على الرغم من أن هذا ليس حرجًا إذا كانت القرون مرتبة)
- كان غريبًا وغير مفهوم ، ولكن عاجلاً أم آجلاً كان عليه أن يدعم (هذا لا يزال عكازاً)
- إذا كانت هناك مكتبة تابعة لجهة خارجية في مجلد يحتوي على "LocalPods" في مسارها ، فستحاول إضافة ملف R.generated.swift هناك وإلا فسوف تتعطل مع وجود خطأ
تحضير أمر
كنت أعيش لبعض الوقت مع النص والمعاناة ، قررت دراسة هذا الموضوع على نطاق أوسع ووجدت خيارًا آخر.
في Podspec ، هناك أمر تجهيز معد ، وهو مخصص فقط لإنشاء المصادر وتعديلها ، والتي ستتم إضافتها بعد ذلك إلى المشروع.
* الأخبار - اسم الجراب ، الذي يجب استبداله باسم جرابك المحلي
* لمس - الأمر لإنشاء ملف. الوسيطة هي المسار النسبي للملف (بما في ذلك اسم الملف بالملحق)
بعد ذلك سنقوم بعمليات احتيال مع News.podspec
يُسمى هذا البرنامج النصي أول مرة pod install
تشغيل pod install
ويضيف الملف الذي نحتاجه إلى المجلد المصدر في الموقد.
Pod::Spec.new do |s|
التالي هو "خدعة مع آذان" أخرى - نحن بحاجة إلى إجراء مكالمة إلى النص النصي R.swift للمداخن المحلية.
Pod::Spec.new do |s|
صحيح ، هناك واحد "لكن". لا يعمل prepare_command
مع القرون المحلية ، أو بالأحرى يعمل ، ولكن في بعض الحالات الخاصة. هناك مناقشة حول هذا الموضوع على جيثب .
إماتة
* الوفاة - ويكي ، الضربة الأخيرة في مورتال كومبات.
بعد المزيد من البحث ، وجدت حلًا آخر - مزيج من النهج c prepare_command
و pre_install
.
تعديل صغير للسحر من Podfile:
pre_install do |installer|
ونفس البرنامج النصي الذي لم يتم تشغيله في المداخن المحلية
Pod::Spec.new do |s|
في النهاية ، يعمل كما نتوقع.
أخيرًا!
ملاحظة:
حاولت عمل أمر مخصص آخر بدلاً من prepare_command
، لكن pod lib lint
(أمر للتحقق من محتوى podspec والموقد نفسه) يقسم على متغيرات إضافية ولا يمر.
المواقد غير المحلية
في القرون البعيدة (تلك الموجودة في المستودعات الخاصة بها) ، لا تحتاج إلى كل هذه البرمجة النصية ، التي تم وصفها أعلاه ، لأن قاعدة التعليمات البرمجية مرتبطة بشكل صارم بإصدار التبعية.
يكفي ببساطة تضمين المثال (مشروع تم إنشاؤه بعد الأمر pod lib بإنشاء أمر <اسم>) البرنامج النصي R.swift نفسه وإضافة R.generated.swift إلى حزمة المكتبة (أسفل). إذا لم يكن المشروع يحتوي على مثال ، فسيتعين عليك كتابة نصوص مشابهة لتلك التي ذكرتها.
ملاحظة:
هناك توضيح صغير:
R.swift + Xcode 10 + نظام بناء جديد + بناء تزايدي! = <3
مزيد من المعلومات حول المشكلة في الصفحة الرئيسية للمكتبة أو هنا
لا يعمل R.swift v4.0.0 مع cocoapods 1.6.0 :(
أعتقد أنه سيتم قريبا تصحيح جميع المشاكل.
الخلاصة
تحتاج دائمًا إلى الحفاظ على شريط الجودة على أعلى مستوى ممكن. هذا مهم بشكل خاص للتطبيقات التي تعمل مع التمويل.
في هذه الحالة ، لا تحتاج إلى زيادة التحميل على الاختبار والعثور على الأخطاء في أقرب وقت ممكن. في حالتنا ، يكون هذا إما في الوقت الذي قام فيه المطور بتجميع الشفرة ، أو أثناء التشغيل التجريبي لطلبات السحب. وبالتالي ، نجد نقصًا في التوطين ليس من خلال النظرة اليقظة للمختبرين أو من خلال الاختبارات الآلية ، ولكن من خلال العملية المعتادة لبناء التطبيق.
تحتاج أيضًا إلى مراعاة حقيقة أن هذه أداة تابعة لجهة خارجية مرتبطة ببنية المشروع وتوزع محتواها. إذا تغير هيكل ملف المشروع ، فيجب تغيير الأداة.
لقد اتخذنا هذه المخاطرة ، وفي هذه الحالة ، نحن دائمًا على استعداد لتغيير هذه الأداة إلى أي أداة أخرى أو كتابة أداة خاصة بك.
والمكاسب من R.swift هي عدد كبير من ساعات العمل التي يمكن أن يقضيها الفريق في أشياء أكثر أهمية: الميزات الجديدة والحلول التقنية الجديدة وتحسين الجودة وما إلى ذلك. أعادت R.swift تمامًا مقدار الوقت الذي تم قضاؤه في تكامله ، حتى مع الأخذ بعين الاعتبار إمكانية استبداله في المستقبل بحل مماثل آخر.
ر. سويفت
مكافأة
يمكنك اللعب بمثال لرؤية على الفور بأم عينيك الربح من توليد الشفرة للموارد. الكود المصدري للمشروع " للتلاعب ": GitHub .
شكرا جزيلا لقراءة المقال أو لتصفح هذا المكان ، يسعدني على أي حال)
هذا كل شئ.