التحكم بالموارد. تخصيص SwiftGen

على الأرجح ، في كل مشروع كبير يعمل بنظام iOS ، يمكن أن يتعثر الشخص طويل العمر على الرموز التي لم يتم استخدامها في أي مكان ، أو الوصول إلى مفاتيح التوطين التي اختفت منذ فترة طويلة. في معظم الأحيان ، تنشأ مثل هذه المواقف من عدم الانتباه ، والأتمتة هي أفضل علاج لعدم الانتباه.


في فريق iOS في HeadHunter ، نولي اهتمامًا كبيرًا لأتمتة المهام الروتينية التي قد يواجهها المطور. مع هذا المقال ، نريد أن نبدأ دورة من القصص حول تلك الأدوات والأساليب التي تبسط عملنا اليومي.


منذ بعض الوقت ، تمكنا من السيطرة على موارد التطبيق باستخدام الأداة المساعدة SwiftGen. حول كيفية تكوينه ، وكيفية التعايش معه وكيف تساعد هذه الأداة على تحويل التحقق من صلة الموارد بالمترجم ، وسوف نتحدث عن القط.



SwiftGen هي أداة مساعدة تسمح لك بإنشاء رمز Swift للوصول إلى الموارد المختلفة لمشروع Xcode ، من بينها:


  • الخطوط
  • الألوان
  • القصص المصورة.
  • سلاسل التوطين
  • الأصول.

يمكن لأي شخص كتابة رمز مشابه لتهيئة الصور أو سلاسل التوطين:


logoImageView.image = UIImage(named: "Swift") nameLabel.text = String( format: NSLocalizedString("languages.swift.name", comment: ""), locale: Locale.current ) 

للإشارة إلى اسم الصورة أو مفتاح الترجمة ، نستخدم سلسلة حرفية. ما هو مكتوب بين علامات الاقتباس المزدوجة لا يتم التحقق من صحتها من قبل المترجم أو بيئة التطوير (Xcode). وهنا تكمن مجموعة المشاكل التالية:


  • يمكنك عمل خطأ مطبعي ؛
  • قد تنسى تحديث الاستخدام في الكود بعد تعديل أو حذف المفتاح / الصورة.

دعونا نرى كيف يمكننا تحسين هذا الرمز باستخدام SwiftGen.


بالنسبة إلى فريقنا ، كان الجيل ملائمًا للسلاسل والأصول فقط ، وسيتم مناقشتها في المقالة. يتشابه توليد أنواع أخرى من الموارد ، وإذا رغبت في ذلك ، يمكن إتقانها بسهولة بشكل مستقل.


تنفيذ المشروع


تحتاج أولاً إلى تثبيت SwiftGen. لقد اخترنا تثبيته من خلال CocoaPods كوسيلة ملائمة لتوزيع الأداة بين جميع أعضاء الفريق. ولكن يمكن القيام بذلك بطرق أخرى ، موصوفة بالتفصيل في الوثائق . في حالتنا ، كل ما يجب القيام به هو إضافة pod 'SwiftGen' ، ثم إضافة مرحلة بناء جديدة (مرحلة Build Phase ) ستطلق SwiftGen قبل بناء المشروع.


 "$PODS_ROOT"/SwiftGen/bin/swiftgen 

من المهم تشغيل SwiftGen قبل بدء مرحلة Compile Sources لتجنب الأخطاء عند تجميع المشروع.



الآن نحن على استعداد لتكييف SwiftGen مع مشروعنا.


تكوين SwiftGen


بادئ ذي بدء ، تحتاج إلى تكوين القوالب التي سيتم من خلالها إنشاء رمز الوصول إلى الموارد. تحتوي الأداة المساعدة بالفعل على مجموعة من النماذج لإنشاء التعليمات البرمجية ، ويمكن عرضها جميعًا على github ، ومن حيث المبدأ ، فهي جاهزة للاستخدام. القوالب مكتوبة بلغة الاستنسل ، قد تكون على دراية بها إذا كنت تستخدم Sourcery أو لعبت مع Kitura . إذا رغبت في ذلك ، يمكن تكييف كل من القوالب مع أدلة خاصة بهم.
على سبيل المثال ، خذ النموذج الذي يولد للوصول إلى سلاسل التوطين. بدا لنا أنه في المعيار هناك الكثير من الفائض ويمكن تبسيطه. يوجد مثال مبسط مع التعليقات التوضيحية تحت المفسد.


مثال على القالب
 {#      #} {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} {#    #} {% macro parametersBlock types %}{% filter removeNewlines:"leading" %} {% for type in types %} _ p{{forloop.counter}}: {{type}}{% if not forloop.last %}, {% endif %} {% endfor %} {% endfilter %}{% endmacro %} {% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} {% for type in types %} p{{forloop.counter}}{% if not forloop.last %}, {% endif %} {% endfor %} {% endfilter %}{% endmacro %} {#       enum        #} {% macro recursiveBlock table item sp %} {{sp}}{% for string in item.strings %} {{sp}}{% if not param.noComments %} {{sp}}/// {{string.translation}} {{sp}}{% endif %} {{sp}}{% if string.types %} {{sp}}{{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { {{sp}} return localize("{{string.key}}", {% call argumentsBlock string.types %}) {{sp}}} {{sp}}{% else %} {{sp}}{{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = localize("{{string.key}}") {{sp}}{% endif %} {{sp}}{% endfor %} {{sp}}{% for child in item.children %} {{sp}}{{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { {{sp}}{% set sp2 %}{{sp}} {% endset %} {{sp}}{% call recursiveBlock table child sp2 %} {{sp}}} {{sp}}{% endfor %} {% endmacro %} import Foundation {#   enum #} {% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} {{accessModifier}} enum {{enumName}} { {% if tables.count > 1 %} {% for table in tables %} {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { {% call recursiveBlock table.name table.levels " " %} } {% endfor %} {% else %} {% call recursiveBlock tables.first.name tables.first.levels " " %} {% endif %} } {#  enum Localization         #} extension Localization { fileprivate static func localize(_ key: String, _ args: CVarArg...) -> String { return String( format: NSLocalizedString(key, comment: ""), locale: Locale.current, arguments: args ) } } 

من السهل حفظ ملف القالب نفسه في جذر المشروع ، على سبيل المثال ، في مجلد SwiftGen/Templates بحيث يكون هذا القالب متاحًا لكل من يعمل في المشروع.
تدعم الأداة التكوين من خلال ملف YAML swiftgen.yml ، حيث يمكنك تحديد المسارات إلى ملفات المصدر والقوالب والمعلمات الإضافية. قم Swiftgen في جذر المشروع في مجلد Swiftgen ، ثم قم بتجميع الملفات الأخرى ذات الصلة Swiftgen النصي في نفس المجلد.
بالنسبة لمشروعنا ، قد يبدو هذا الملف كما يلي:


 xcassets: - paths: ../SwiftGenExample/Assets.xcassets templatePath: Templates/ImageAssets.stencil output: ../SwiftGenExample/Image.swift params: enumName: Image publicAccess: 1 noAllValues: 1 strings: - paths: ../SwiftGenExample/en.lproj/Localizable.strings templatePath: Templates/LocalizableStrings.stencil output: ../SwiftGenExample/Localization.swift params: enumName: Localization publicAccess: 1 noComments: 0 

في الواقع ، يشار إلى مسارات الملفات والقوالب هناك ، بالإضافة إلى المعلمات الإضافية التي يتم تمريرها إلى سياق القالب.
نظرًا لأن الملف ليس في جذر المشروع ، نحتاج إلى تحديد المسار إليه عند بدء Swiftgen. تغيير البرنامج النصي لبدء التشغيل:


 "$PODS_ROOT"/SwiftGen/bin/swiftgen config run --config SwiftGen/swiftgen.yml 

الآن يمكن تجميع مشروعنا. بعد التجميع ، يجب أن يظهر Image.swift Localization.swift و Image.swift في مجلد المشروع على طول المسارات المحددة في Image.swift . يجب إضافتها إلى مشروع Xcode. في حالتنا ، تحتوي الملفات التي تم إنشاؤها على ما يلي:


للسلاسل:
 public enum Localization { public enum Languages { public enum ObjectiveC { /// General-purpose, object-oriented programming language that adds Smalltalk-style messaging to the C programming language public static let description = localize("languages.objective-c.description") /// https://en.wikipedia.org/wiki/Objective-C public static let link = localize("languages.objective-c.link") /// Objective-C public static let name = localize("languages.objective-c.name") } public enum Swift { /// General-purpose, multi-paradigm, compiled programming language developed by Apple Inc. for iOS, macOS, watchOS, tvOS, and Linux public static let description = localize("languages.swift.description") /// https://en.wikipedia.org/wiki/Swift_(programming_language) public static let link = localize("languages.swift.link") /// Swift public static let name = localize("languages.swift.name") } } public enum MainScreen { /// Language public static let title = localize("main-screen.title") public enum Button { /// View in Wikipedia public static let title = localize("main-screen.button.title") } } } extension Localization { fileprivate static func localize(_ key: String, _ args: CVarArg...) -> String { return String( format: NSLocalizedString(key, comment: ""), locale: Locale.current, arguments: args ) } } 

للصور:
 public enum Image { public enum Logos { public static var objectiveC: UIImage { return image(named: "ObjectiveC") } public static var swift: UIImage { return image(named: "Swift") } } private static func image(named name: String) -> UIImage { let bundle = Bundle(for: BundleToken.self) guard let image = UIImage(named: name, in: bundle, compatibleWith: nil) else { fatalError("Unable to load image named \(name).") } return image } } private final class BundleToken {} 

يمكنك الآن استبدال جميع استخدامات التوطين وسلاسل التهيئة للصور من النموذج UIImage(named: "") بما أنشأناه. سيؤدي ذلك إلى تسهيل تتبع التغييرات على مفاتيح سلسلة الأقلمة أو إزالتها. في أي من هذه الحالات ، لن يتم تجميع المشروع حتى يتم إصلاح جميع الأخطاء المرتبطة بالتغييرات.
بعد التغييرات ، يبدو رمزنا كما يلي:


  let logos = Image.Logos.self let localization = Localization.self private func setupWithLanguage(_ language: ProgrammingLanguage) { switch language { case .Swift: logoImageView.image = logos.swift nameLabel.text = localization.Languages.Swift.name descriptionLabel.text = localization.Languages.Swift.description wikiUrl = localization.Languages.Swift.link.toURL() case .ObjectiveC: logoImageView.image = logos.objectiveC nameLabel.text = localization.Languages.ObjectiveC.name descriptionLabel.text = localization.Languages.ObjectiveC.description wikiUrl = localization.Languages.ObjectiveC.link.toURL() } } 

إنشاء مشروع في Xcode


هناك مشكلة واحدة في الملفات التي تم إنشاؤها: يمكن تغييرها يدويًا عن طريق الخطأ ، ونظرًا لأنه يتم استبدالها من جديد مع كل مجموعة ، فقد يتم فقد هذه التغييرات. لتجنب ذلك ، يمكنك قفل الملفات للكتابة بعد SwiftGen البرنامج النصي SwiftGen .
يمكن تحقيق ذلك باستخدام chmod . نعيد كتابة Build Phase مع إطلاق SwiftGen على النحو التالي:


 PODSROOT="$1" OUTPUT_FILES=() COUNTER=0 while [ $COUNTER -lt ${SCRIPT_OUTPUT_FILE_COUNT} ]; do tmp="SCRIPT_OUTPUT_FILE_$COUNTER" OUTPUT_FILES+=(${!tmp}) COUNTER=$[$COUNTER+1] done for file in "${OUTPUT_FILES[@]}" do if [ -f $file ] then chmod a=rw "$file" fi done $PODSROOT/SwiftGen/bin/swiftgen config run --config SwiftGen/swiftgen.yml for file in "${OUTPUT_FILES[@]}" do chmod a=r "$file" done 

البرنامج النصي بسيط للغاية. قبل بدء الجيل ، إذا كانت الملفات موجودة ، نعطيهم أذونات الكتابة. بعد تشغيل البرنامج النصي ، نحظر القدرة على تعديل الملفات.
لسهولة التحرير والتحقق من البرنامج النصي للمراجعة ، من الملائم وضعه في ملف runswiftgen.sh منفصل. يمكن العثور على النسخة النهائية من البرنامج النصي مع تعديلات طفيفة هنا . الآن ستبدو Build Phase لدينا على هذا النحو: نمرر المسار إلى مجلد Pods إلى إدخال البرنامج النصي:


 "$SRCROOT"/SwiftGen/runswiftgen.sh "$PODS_ROOT" 

نعيد بناء المشروع ، والآن عند محاولة تعديل الملف الذي تم إنشاؤه يدويًا ، يظهر تحذير:



لذلك ، يحتوي المجلد الذي يحتوي على Swiftgen الآن على ملف تكوين ، وبرنامج نصي لقفل الملفات وتشغيل Swiftgen ومجلد يحتوي على قوالب مخصصة. من المناسب إضافته إلى المشروع لمزيد من التحرير إذا لزم الأمر.



ونظرًا لأن Image.swift Localization.swift و Image.swift إنشاؤها تلقائيًا ، يمكنك إضافتها إلى .gitignore حتى لا تضطر إلى حل التعارضات بعد git merge مرة أخرى.


أود أيضًا أن ألفت الانتباه إلى ضرورة تحديد ملفات الإدخال / الإخراج لبناء مرحلة SwiftGen إذا كان مشروعك يستخدم نظام البناء الجديد Xcode (وهو نظام البناء الافتراضي مع Xcode 10). في قائمة ملفات الإدخال ، يجب عليك تحديد جميع الملفات التي تم إنشاء الرمز على أساسها. في حالتنا ، هذا هو البرنامج النصي نفسه ، والتكوين ، والقوالب ، وملفات سلاسل .xcassets:


 $(SRCROOT)/SwiftGen/runswiftgen.sh $(SRCROOT)/SwiftGen/swiftgen.yml $(SRCROOT)/SwiftGen/Templates/ImageAssets.stencil $(SRCROOT)/SwiftGen/Templates/LocalizableStrings.stencil $(SRCROOT)/SwiftGenExample/en.lproj/Localizable.strings $(SRCROOT)/SwiftGenExample/Assets.xcassets 

في ملفات الإخراج ، نضع الملفات التي ينشئ SwiftGen الرمز فيها:


 $(SRCROOT)/SwiftGenExample/Image.swift $(SRCROOT)/SwiftGenExample/Localization.swift 

من الضروري الإشارة إلى هذه الملفات حتى يتمكن نظام البناء من تحديد ما إذا كان سيتم تشغيل البرنامج النصي ، اعتمادًا على وجود أو عدم وجود تغييرات في الملفات ، وعند أي نقطة في تجميع المشروع يجب تنفيذ هذا البرنامج النصي.


الملخص


يعد SwiftGen أداة جيدة للحماية من الإهمال عند العمل مع موارد المشروع. مع ذلك ، تمكنا من إنشاء رمز تلقائيًا للوصول إلى موارد التطبيق وتحويل جزء من العمل على التحقق من صلة الموارد بالمترجم ، مما يعني تبسيط عملنا قليلاً. بالإضافة إلى ذلك ، قمنا بإعداد مشروع Xcode بحيث يكون المزيد من العمل مع الأداة أكثر ملاءمة.


الإيجابيات:


  1. أسهل للسيطرة على موارد المشروع.
  2. يتم تقليل أخطاء الأخطاء المطبعية ، يصبح من الممكن استخدام الاستبدال التلقائي.
  3. يتم التحقق من الأخطاء في مرحلة التجميع.

السلبيات:


  1. لا يوجد دعم Localizable.stringsdict.
  2. لا يتم أخذ الموارد غير المستخدمة في الاعتبار.

يمكن رؤية المثال الكامل على جيثب

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


All Articles