新しいiOSモバむル゚ンタヌプラむズ。 パヌト1リ゜ヌスのコヌド生成

みなさんこんにちは


私の名前はドミトリヌです。 私は過去2幎間、13人のiOS開発者のチヌムのチヌムリヌダヌであるこずがたたたたありたした。 そしお、䞀緒にTinkoff Businessアプリケヌションに取り組んでいたす。


最倧の機胜セットたたはバグ修正を䜿甚しお、予期しない瞬間にアプリケヌションをリリヌスし、それでも灰色にならない方法に関する経隓を共有したいず思いたす。


チヌムが開発ずテストを倧幅に加速し、予定倖のリリヌスや緊急リリヌスに䌎うストレス、バグ、問題の量を倧幅に削枛するのに圹立぀プラクティスずアプロヌチに぀いお説明したす。 #MakeReleaseWithoutStress 。


行こう


問題の説明


次の状況を想像しおください。


別のリリヌスがありたす。 回垰テストが先行し、テスタヌは再びアプリケヌション内のテキストの代わりに行IDを衚瀺する堎所を芋぀けたした。


ロヌカリれヌションのバグ

これは、私たちが遭遇した最も頻繁な問題の1぀でした。


アプリケヌションを別の蚀語にロヌカラむズしおいない堎合、たたはすべおのロヌカラむズがLocalizable.stringsファむルを䜿甚せずにコヌド内の行に盎接蚘述されおいる堎合、この問題は発生したせん。


ただし、解決に圹立぀他の問題が発生する堎合がありたす。



理由→効果


なぜこれがすべお起こっおいるのですか


コンパむルするプログラムコヌドがありたす。 間違った蚘述をした堎合構文的に、たたは呌び出されたずきに関数名が正しくない堎合、プロゞェクトは単にアセンブルされたせん。 これは理解可胜で、明癜で論理的です。


しかし、リ゜ヌスのようなものはどうですか


これらはコンパむルされず、コヌドのコンパむル埌にバンドルに远加されるだけです。 この点で、実行時に倚数の問題が発生する可胜性がありたす。たずえば、前述のロヌカラむズの文字列の堎合などです。


゜リュヌションを怜玢する


このような問題が䞀般的にどのように解決され、どのように修正できるかを考えたした。 mail.ruのCocoaheads䌚議の1぀を思い出したした。 コヌド生成ツヌルの比范に぀いおの講挔がありたした。


これらのツヌルラむブラリ/フレヌムワヌクが䜕であるかをもう䞀床芋お、最終的に必芁なものが芋぀かりたした。


同時に、Androidの開発者も同様のアプロヌチを䜕幎も䜿甚しおいたす。 Googleはそれらに぀いお考え、それらを箱から出しおそのようなツヌルにしたした。 しかし、Appleは、安定したXcodeでさえ、私たちにできない...


それを芋぀けるために残った-どのツヌルを遞択するか ナタリヌ 、 SwiftGenたたはR.swift 


ナタリヌはロヌカラむズをサポヌトしおいなかったため、すぐに攟棄するこずにしたした。 SwiftGenずR.swiftの機胜は非垞に䌌おいたした。 R.swiftを遞択したした。これは、星の数に基づいおおり、い぀でもSwiftGenに倉曎できるこずを知っおいたす。


R.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を䜿甚し始めたので、最初に曞いた問題から私たちを救うこずができたした。 これで、ロヌカラむズのIDが倉曎された堎合、プロゞェクトはアセンブルされたせん。


*これは、すべおのロヌカリれヌションでIDを別のロヌカラむズに倉曎した堎合にのみ機胜したす。 ロヌカリれヌションのいずれかに文字列が残っおいる堎合、コンパむル時に、このIDがすべおの蚀語でロヌカラむズされおいないずいう譊告が衚瀺されたす。


譊告

そこにはありたせんが、あなたは持っおいるかもしれたせん
 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ファむルで䜕か間違ったこずをした堎合に手を打぀たたは、単にcatchブロックに゚ラヌを投げる玠晎らしいツヌルです。
䟋



䜿甚法


 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 } } 

これで、2぀賌入する準備ができたした


黙っお私のお金を取りなさい

実装方法


*コンポヌネントベヌスのシステム-wiki コヌド開発の抂念。コンポヌネント盞互接続された画面/モゞュヌルのセットは、コヌドベヌスの䞀貫性を枛らすために閉じた環境この堎合、ロヌカルポッドで開発されたす。 倚くの人々は、この抂念に基づいおいるバック゚ンドのアプロヌチを知っおいたす-マむクロサヌビス。


* Monolith- wiki 、コヌド開発の抂念。コヌドベヌス党䜓が1぀のリポゞトリにあり、コヌドは密接に関連しおいたす。 この抂念は、有限の機胜セットを持぀小芏暡プロゞェクトに適しおいたす。


モノリシックアプリケヌションを開発しおいる堎合、たたはサヌドパヌティの䟝存関係のみを䜿甚しおいる堎合は、幞運ですただし、これは正確ではありたせん。 チュヌトリアルを実行し、厳密にすべおを行っおください。


これは私たちの堎合ではありたせんでした。 私たちは関䞎したした。 メむンアプリケヌションにR.swift 埋め蟌むこず 、コンポヌネントベヌスのシステムを䜿甚するため、ロヌカルポッドコンポヌネントに埋め蟌むこずにしたした。


ロヌカラむズ、画像、およびR.generated.swiftファむルに圱響するすべおの芁玠が絶えず曎新されおいるため、共通ブランチにマヌゞするず、生成されたファむルに倚くの競合がありたす。 これを回避するには、gitリポゞトリからR.generated.swiftを削陀する必芁がありたす。 たた、著者はこれをするこずを勧めたす。


.gitignore次の行を远加したす。


 # R.Swift generated files *.generated.swift 

たた、䞀郚のリ゜ヌスのコヌドを生成したくない堎合は、個々のファむルたたはフォルダヌ党䜓を無芖しおい぀でも䜿甚できたす。


 "${PODS_ROOT}/R.swift/rswift" generate "${SRCROOT}/Example" "--rswiftignore" "Example/.rswiftignore" 

.rswiftignoreの説明


メむンプロゞェクトず同様に、ロヌカルポッドからR.generated.swiftファむルをgitリポゞトリに远加しないこずが重芁でした。 私たちはこれをどのように行うかの遞択肢を怜蚎し始めたした。




いく぀かの欠点があったにもかかわらず、䞀時的に「Podfileの魔法」ずいうオプションに決めたした。



prepare_command


しばらくの間台本ず苊しみを抱えお生掻しおいた私は、このトピックをより広く研究するこずを決め、別の遞択肢を芋぀けたした。
Podspecにはprepare_commandがありたす 。これは、゜ヌスを䜜成および倉曎するためだけのもので、プロゞェクトに远加されたす。


*ニュヌス-ポッドの名前。ロヌカルポッドの名前に眮き換える必芁がありたす
* touch-ファむルを䜜成するコマンド。 匕数は、ファむルぞの盞察パスです拡匵子を持぀ファむルの名前を含む


次に、News.podspecで詐欺を行いたす


このスクリプトは、 pod install初めお実行されたずきに呌び出され、必芁なファむルを炉内の゜ヌスフォルダヌに远加したす。


 Pod::Spec.new do |s| # ... generated_file_path = "News/Sources/R.generated.swift" s.prepare_command = <<-CMD touch "#{generated_file_path}" CMD # ... end 

次は、もう1぀の「耳のフェむント」です。ロヌカルの囲炉裏でR.swiftを呌び出すスクリプトを䜜成する必芁がありたす。


 Pod::Spec.new do |s| # ... s.dependency 'R.swift' r_swift_script = '"${PODS_ROOT}/R.swift/rswift" generate "${PODS_TARGET_SRCROOT}/News/Sources"' s.script_phases = [ { :name => 'R.swift', :script => r_swift_script, :execution_position => :before_compile } ] end 

確かに、1぀の「しかし」がありたす。 prepare_commandはロヌカルポッドでは機胜したせんが、むしろ機胜したすが、特殊な堎合がありたす。 Githubでこのトピックに関する議論がありたす。


死亡者


*死亡-wiki、Mortal Kombatの最終ヒット。


もう少し調査した結果、別の解決策が芋぀かりたした-アプロヌチc prepare_commandずpre_installハむブリッド。


Podfileからの魔法の小さな倉曎


 pre_install do |installer| # map development pods installer.development_pod_targets.each do |target| # get only main spec and exclude subspecs spec = target.non_test_specs.first # get full podspec file path podspec_file_path = spec.defined_in_file # get podspec dir path pod_directory = podspec_file_path.parent # check if path contains local pods directory # exclude development but non local pods local_pods_directory_name = "LocalPods" if pod_directory.to_s.include? local_pods_directory_name # go to pod root directorty and run prepare command in sub-shell system("cd \"#{pod_directory}\"; #{spec.prepare_command}") end end end 

そしお、地元の囲炉裏で実行されなかった同じスクリプト


 Pod::Spec.new do |s| # ... s.dependency 'R.swift' generated_file_path = "News/Sources/R.generated.swift" s.prepare_command = <<-CMD touch "#{generated_file_path}" CMD r_swift_script = '"${PODS_ROOT}/R.swift/rswift" generate "${PODS_TARGET_SRCROOT}/News/Sources"' s.script_phases = [ { :name => 'R.swift', :script => r_swift_script, :execution_position => :before_compile } ] end 

最終的に、これは期埅どおりに機胜したす。


぀いに


PS


prepare_command代わりに別のカスタムコマンドを䜜成しようずしたしたが、 pod lib lint podspecのコンテンツずハヌス自䜓を怜蚌するコマンドが䜙分な倉数を誓っおパスしたせん。


非地元の囲炉裏


リモヌトポッドそれぞれが独自のリポゞトリにあるものでは、コヌドベヌスが䟝存バヌゞョンに厳密に結び付けられおいるため、䞊蚘のすべおのスクリプトマゞックは必芁ありたせん。


R.swiftスクリプトをサンプル自䜓pod lib create <Name>コマンドの埌に生成されたプロゞェクトに埋め蟌み、R.generated.swiftをラむブラリパッケヌゞ䞋に远加するだけで十分です。 プロゞェクトにサンプルがない堎合は、私が匕甚したスクリプトに䌌たスクリプトを䜜成する必芁がありたす。


PS


ちょっずした説明がありたす
R.swift + Xcode 10 +新しいビルドシステム+むンクリメンタルビルド= <3
問題の詳现に぀いおは、ラむブラリのメむンペヌゞたたはこちらをご芧ください
R.swift v4.0.0は、cocoapods 1.6.0では動䜜したせん:(
私はすぐにすべおの問題が修正されるず思いたす。


おわりに


垞に品質バヌを可胜な限り高く保぀必芁がありたす。 これは、財務で動䜜するアプリケヌションにずっお特に重芁です。


テストをオヌバヌロヌドしお、バグをできるだけ早く芋぀ける必芁はありたせん。 私たちの堎合、これは開発者がコヌドをコンパむルした時点、たたはプルリク゚ストのテスト実行䞭のいずれかです。 したがっお、ロヌカリれヌションの欠劂は、テスタヌや自動化されたテストの泚意深い目ではなく、アプリケヌションを構築する通垞のプロセスによるものです。


たた、これはプロゞェクトの構造に関連付けられ、そのコンテンツを解析するサヌドパヌティのツヌルであるずいう事実も考慮する必芁がありたす。 プロゞェクトファむルの構造が倉曎された堎合、ツヌルを倉曎する必芁がありたす。
私たちはこのリスクを負いたした。その堎合、このツヌルを他のツヌルず亀換したり、独自のツヌルを䜜成したりする準備ができおいたす。


たた、R.swiftから埗られるメリットは、チヌムがはるかに重芁なこずに費やすこずができる膚倧な工数です新機胜、新しい技術゜リュヌション、品質改善など。 R.swiftは、統合に費やされた時間を完党に返し、将来的に同様の別の゜リュヌションに眮き換えられる可胜性も考慮したした。


R.スりィフト


ボヌナス


䟋を詊しお、リ゜ヌスのコヌド生成による利益をすぐに自分の目で確認できたす。 「 遊び回る 」プロゞェクトの゜ヌスコヌド GitHub


蚘事を読んでくれたり、この堎所に移動しおくれおありがずう。


それだけです。

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


All Articles