{"id":374106,"date":"2024-05-21T05:52:12","date_gmt":"2024-05-21T05:52:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=374106"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=374106","title":{"rendered":"<span>Clean architecture \u0438 MVVM \u0432 iOS \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434<\/a><\/p>\n<p>\u041f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u041f\u041e \u0432\u0430\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0437\u0430\u0439\u043d-, \u043d\u043e \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b. \u0418\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e. \u0412 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u0430\u043c\u044b\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 &#8212; MVVM, Clean Architecture \u0438 Redux.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043f\u043e\u043a\u0430\u0436\u0435\u043c \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043a\u0430\u043a \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b MVVM \u0438 Clean Architecture \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b \u0432 iOS \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442\u0435\u0441\u044c Redux, <a href=\"https:\/\/www.kodeco.com\/books\/advanced-ios-app-architecture\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u0446\u0435\u043d\u0438\u0442\u0435 \u044d\u0442\u0443 \u043a\u043d\u0438\u0433\u0443<\/a>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/2ce\/550\/197\/2ce550197ac5bd1caeab94dcef84321d.png\" width=\"1400\" height=\"741\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/2ce\/550\/197\/2ce550197ac5bd1caeab94dcef84321d.png\"\/><\/figure>\n<p>\u041a\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c \u043d\u0430 \u0441\u0445\u0435\u043c\u0435\u00a0<strong>Clean Architecture,<\/strong>\u00a0\u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e &#8212;\u00a0<em>\u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0441\u043b\u043e\u0435\u0432 \u043e\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0445.<\/em>\u00a0\u0421\u0442\u0440\u0435\u043b\u043a\u0438, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u0441\u043d\u0430\u0440\u0443\u0436\u0438 \u0432\u043d\u0442\u0443\u0440\u044c \u044d\u0442\u043e Dependency Rule. \u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0434\u0442\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u043b\u043e\u0435\u0432 \u0432\u043d\u0443\u0442\u0440\u044c \u043a \u0446\u0435\u043d\u0442\u0440\u0443.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438 \u0443 \u043d\u0430\u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438\u0441\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043b\u043e\u0438:<\/p>\n<p><strong>Presentation, Domain \u0438 Data<\/strong><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/049\/f12\/8be\/049f128bef473aa0169ebe235e01cdbf.png\" width=\"1400\" height=\"335\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/049\/f12\/8be\/049f128bef473aa0169ebe235e01cdbf.png\"\/><\/p>\n<div><figcaption><strong>Domain<\/strong>\u00a0\u0441\u043b\u043e\u0439 (\u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430) &#8212; \u0441\u0430\u043c\u044b\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0441\u043b\u043e\u0439 \u043d\u0430\u0448\u0435\u0439 \u201c\u043b\u0443\u043a\u043e\u0432\u0438\u0446\u044b\u201d (\u0431\u0435\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043e\u043d \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d). \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 Entities (\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\/\u0431\u0438\u0437\u043d\u0435\u0441 \u043c\u043e\u0434\u0435\u043b\u0438), Use Cases \u0438 Repository Interfaces. \u042d\u0442\u043e\u0442 \u0441\u043b\u043e\u0439 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445. \u0422\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c host-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0446\u0435\u043b\u044f\u0445, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435). \u041f\u043e\u044d\u0442\u043e\u043c\u0443 Domain Use Cases \u0442\u0435\u0441\u0442\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u0437\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434.\u00a0<strong>\u0412\u0430\u0436\u043d\u043e: Domain \u0441\u043b\u043e\u0439 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f \u0447\u0442\u043e-\u043b\u0438\u0431\u043e \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043b\u043e\u0435\u0432.<\/strong><\/figcaption><\/div>\n<\/figure>\n<ul>\n<li>\n<p>\u0425\u043e\u0440\u043e\u0448\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0432\u044b\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0432\u043e\u043a\u0440\u0443\u0433 Use Cases \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043c\u043e\u0433\u043b\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 Use Cases, \u043d\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0443\u043b\u0437\u044b. \u042d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f\u00a0<a href=\"https:\/\/blog.cleancoder.com\/uncle-bob\/2011\/09\/30\/Screaming-Architecture.html\" rel=\"noopener noreferrer nofollow\"><strong>Screaming Architecture<\/strong><\/a><strong>.<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Presentation \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 UI (UIViewController, SwiftUI View).<\/strong>\u00a0\u0412\u044c\u044e\u0445\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0438\u0440\u044e\u0442\u0441\u044f \u0432\u044c\u044e-\u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 (\u0438\u043b\u0438 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0435\u0440\u0430\u043c\u0438), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e Use Cases.\u00a0<strong>Presentation \u0441\u043b\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 Domain \u0441\u043b\u043e\u044f.<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Data<\/strong>\u00a0\u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438 \u043e\u0434\u0438\u043d\/\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e Data Source. \u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b \u0437\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u044e \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u0430\u0442\u0430-\u0441\u043e\u0440\u0441\u043e\u0432. \u0414\u0430\u0442\u0430-\u0441\u043e\u0440\u0441\u044b \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0435 \u0438\u043b\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, persistent database).\u00a0<strong>Data \u0441\u043b\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 Domain \u0441\u043b\u043e\u044f.<\/strong>\u00a0\u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u043e\u0435 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 Network JSON Data \u0432 \u043c\u043e\u0434\u0435\u043b\u0438 Domain.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430 \u0441\u0445\u0435\u043c\u0435 \u043d\u0438\u0436\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0441 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 Data Flow (Request\/Response). \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c \u0438\u043d\u0432\u0435\u0440\u0441\u0438\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (<strong>Dependency Inversion)<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0433\u0434\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f(\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b). \u041e\u0431\u044a\u044f\u0441\u043d\u0438\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u0438 \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f62\/d7e\/868\/f62d7e86834f81c6391aa8ca818ee0bc.png\" width=\"1400\" height=\"348\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f62\/d7e\/868\/f62d7e86834f81c6391aa8ca818ee0bc.png\"\/><\/figure>\n<h3>Data Flow<\/h3>\n<p>1.\u00a0<strong>View<\/strong>(UI)\u00a0\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u0438\u0437\u00a0<strong><em>ViewModel<\/em><\/strong><em>\u00a0(Presenter).<\/em><\/p>\n<p><em>2.\u00a0<\/em><strong><em>ViewModel<\/em><\/strong><em>\u00a0\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u00a0<\/em><strong><em>Use Case<\/em><\/strong><em>.<\/em><\/p>\n<p><em>3.\u00a0<\/em><strong><em>Use Case<\/em><\/strong><em>\u00a0\u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437\u00a0<\/em><strong><em>User\u00a0\u0438\u00a0Repositorie<\/em><\/strong><em>s.<\/em><\/p>\n<p><em>4. \u041a\u0430\u0436\u0434\u044b\u0439\u00a0<\/em><strong><em>Repository<\/em><\/strong><em>\u00a0\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435\u00a0<\/em><strong><em>Remote Data<\/em><\/strong><em>\u00a0(Network),\u00a0<\/em><strong><em>Persistent DB<\/em><\/strong><em>Storage Source \u0438\u043b\u0438 In-memory Data (\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u0443\u044e \u0438\u043b\u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e).<\/em><\/p>\n<p><em>5. \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u0437\u0430\u0434 \u0432\u00a0<\/em><strong><em>View<\/em><\/strong><em>(UI), \u0433\u0434\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432.<\/em><\/p>\n<h3>\u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/h3>\n<p><strong>Presentation Layer -> Domain Layer &lt;- Data Repositories Layer<\/strong><\/p>\n<p><strong><em>Presentation Layer (MVVM)<\/em><\/strong><em>\u00a0= ViewModels(Presenters) + Views(UI)<\/em><\/p>\n<p><strong><em>Domain Layer<\/em><\/strong><em>\u00a0= Entities + Use Cases + Repositories Interfaces<\/em><\/p>\n<p><strong><em>Data Repositories Layer<\/em><\/strong><em>\u00a0= Repositories Implementations + API(Network) + Persistence DB<\/em><\/p>\n<p><strong>\u041f\u0440\u0438\u043c\u0435\u0440 &#8212; \u043f\u0440\u043e\u0435\u043a\u0442 \u201cMovies App\u201d<\/strong><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/3f9\/3d5\/e97\/3f93d5e978b186073e10eea1ee2616f4.png\" width=\"1400\" height=\"488\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3f9\/3d5\/e97\/3f93d5e978b186073e10eea1ee2616f4.png\"\/><\/figure>\n<p><strong>Domain \u0441\u043b\u043e\u0439<\/strong><\/p>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u043c Domain Layer. \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 Entities, SearchMovies<strong>UseCase, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0449\u0443\u0442 \u0444\u0438\u043b\u044c\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u0422\u0430\u043a\u0436\u0435 \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442<\/strong>\u00a0<em>Data Repositories Interfaces, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u0434\u043b\u044f \u0438\u043d\u0432\u0435\u0440\u0441\u0438\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439.<\/em><\/p>\n<pre><code class=\"swift\">protocol SearchMoviesUseCase { func execute(requestValue: SearchMoviesUseCaseRequestValue, completion: @escaping (Result&lt;MoviesPage, Error>) -> Void) -> Cancellable? } final class DefaultSearchMoviesUseCase: SearchMoviesUseCase { private let moviesRepository: MoviesRepository private let moviesQueriesRepository: MoviesQueriesRepository  init(moviesRepository: MoviesRepository, moviesQueriesRepository: MoviesQueriesRepository) {     self.moviesRepository = moviesRepository     self.moviesQueriesRepository = moviesQueriesRepository }  func execute(requestValue: SearchMoviesUseCaseRequestValue,              completion: @escaping (Result&amp;lt;MoviesPage, Error&amp;gt;) -&amp;gt; Void) -&amp;gt; Cancellable? {     return moviesRepository.fetchMoviesList(query: requestValue.query, page: requestValue.page) { result in          if case .success = result {             self.moviesQueriesRepository.saveRecentQuery(query: requestValue.query) { _ in }         }          completion(result)     } }  } \/\/ Repository Interfaces protocol MoviesRepository { func fetchMoviesList(query: MovieQuery, page: Int, completion: @escaping (Result&lt;MoviesPage, Error>) -> Void) -> Cancellable? } protocol MoviesQueriesRepository { func fetchRecentsQueries(maxCount: Int, completion: @escaping (Result&lt;[MovieQuery], Error>) -> Void) func saveRecentQuery(query: MovieQuery, completion: @escaping (Result&lt;MovieQuery, Error>) -> Void) } <\/code><\/pre>\n<p>\u0412\u0430\u0436\u043d\u043e: \u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0437\u0434\u0430\u0442\u044c Use Cases \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Domain\/UseCases\/Protocol\/UseCase.swift\" rel=\"noopener noreferrer nofollow\"><em>UseCase<\/em><\/a>\u00a0\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439\u00a0<em>start()<\/em>\u00a0\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0432\u0441\u0435 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 Use Cases. \u041e\u0434\u0438\u043d \u0438\u0437 \u043a\u0435\u0439\u0441\u043e\u0432 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0442\u0430\u043a \u0438 \u0434\u0435\u043b\u0430\u0435\u0442:\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Domain\/UseCases\/FetchRecentMovieQueriesUseCase.swift\" rel=\"noopener noreferrer nofollow\">FetchRecentMovieQueriesUseCase<\/a>. Use Cases \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u00a0<strong>Interactors<\/strong><\/p>\n<p>\u0412\u0430\u0436\u043d\u043e: \u00a0<em>UseCase<\/em>\u00a0\u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445\u00a0<em>UseCases<\/em><\/p>\n<p><strong>Presentation<\/strong>\u00a0\u0441\u043b\u043e\u0439<\/p>\n<p>\u042d\u0442\u043e\u0442 \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 MoviesList<strong>ViewModel<\/strong>\u00a0\u0441 \u0430\u0439\u0442\u0435\u043c\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0434\u0431\u044e\u0434\u0430\u044e\u0442\u0441\u044f \u0438\u0437 MoviesList<strong>View<\/strong>. MoviesList<strong>ViewModel<\/strong>\u00a0\u043d\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 UIKit. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u0432\u043e ViewModel \u0442\u0430\u043a\u0438\u0435 \u0444\u0440\u0435\u0439\u0432\u043e\u0440\u043a\u0438 \u043a\u0430\u043a UIKit, SwiftUI \u0438\u043b\u0438 WatchKit, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u0435\u0435 \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u0442\u044c\u00a0<strong>Views<\/strong>\u00a0\u0431\u0435\u0437 UIKit \u0438\u043b\u0438 SwiftUI \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043c\u0435\u043d\u044f\u0442\u044c\u00a0<strong>ViewModel<\/strong>.<\/p>\n<pre><code class=\"swift\">\/\/ \u0412\u0430\u0436\u043d\u043e: \u041d\u0435 \u0438\u043c\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c UIKit \u0438\u043b\u0438 SwiftUI protocol MoviesListViewModelInput { func didSearch(query: String) func didSelect(at indexPath: IndexPath) } protocol MoviesListViewModelOutput { var items: Observable&lt;[MoviesListItemViewModel]> { get } var error: Observable&lt;String> { get } } protocol MoviesListViewModel: MoviesListViewModelInput, MoviesListViewModelOutput { } struct MoviesListViewModelActions { \/\/ \u0412\u0430\u0436\u043d\u043e: \u0435\u0441\u043b\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u043c \u0432\u043d\u0443\u0442\u0440\u0438 Details \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c   \/\/ MoviesList \u044d\u043a\u0440\u0430\u043d \u043d\u043e\u0432\u044b\u043c \u0444\u0438\u043b\u044c\u043c\u043e\u043c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043b\u043e\u0443\u0436\u0435\u0440: \/\/  showMovieDetails: (Movie, @escaping (_ updated: Movie) -> Void) -> Void let showMovieDetails: (Movie) -> Void } final class DefaultMoviesListViewModel: MoviesListViewModel { private let searchMoviesUseCase: SearchMoviesUseCase private let actions: MoviesListViewModelActions?  private var movies: [Movie] = []  \/\/ MARK: - OUTPUT let items: Observable&amp;lt;[MoviesListItemViewModel]&amp;gt; = Observable([]) let error: Observable&amp;lt;String&amp;gt; = Observable(\"\")  init(searchMoviesUseCase: SearchMoviesUseCase,      actions: MoviesListViewModelActions) {     self.searchMoviesUseCase = searchMoviesUseCase     self.actions = actions }  private func load(movieQuery: MovieQuery) {      searchMoviesUseCase.execute(movieQuery: movieQuery) { result in         switch result {         case .success(let moviesPage):             \/\/ \u0412\u0430\u0436\u043d\u043e: \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u0431\u044f\u0437\u0430\u043d\u044b \u0437\u0430\u043c\u0430\u043f\u0438\u0442\u044c \u0438\u0437  Domain Entities \u0432 Item View Models. \u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 Domain \u0438 View             self.items.value += moviesPage.movies.map(MoviesListItemViewModel.init)             self.movies += moviesPage.movies         case .failure:             self.error.value = NSLocalizedString(\"Failed loading movies\", comment: \"\")         }     } }  } \/\/ MARK: - INPUT. View event-\u043c\u0435\u0442\u043e\u0434\u044b extension MoviesListViewModel { func didSearch(query: String) {     load(movieQuery: MovieQuery(query: query)) }  func didSelect(at indexPath: IndexPath) {     actions?.showMovieDetails(movies[indexPath.row]) }  } \/\/ \u0412\u0430\u0436\u043d\u043e: \u042d\u0442\u0430 \u0432\u044c\u044e\u043c\u043e\u0434\u0435\u043b\u044c - \u0434\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u0430\u043a\u0443\u044e-\u043b\u0438\u0431\u043e domain \u043c\u043e\u0434\u0435\u043b\u044c, \u0447\u0442\u043e\u0431\u044b \u043a \u043d\u0435\u0439 \u043d\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u043b\u0438\u0441\u044c view struct MoviesListItemViewModel: Equatable { let title: String } extension MoviesListItemViewModel { init(movie: Movie) { self.title = movie.title ?? \"\" } } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b MoviesList<strong>ViewModelInput<\/strong>\u00a0\u0438 MoviesList<strong>ViewModelOutput, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c<\/strong>MoviesList<strong>View<\/strong>Controller \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c ( \u0441\u0434\u0435\u043b\u0430\u0432 \u043c\u043e\u043a\u00a0<strong>ViewModel)<\/strong>\u00a0. \u0422\u0430\u043a\u0436\u0435 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u044b MoviesListViewModel<strong>Actions<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0430\u044e\u0442\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/Flows\/MoviesSearchFlowCoordinator.swift\" rel=\"noopener noreferrer nofollow\">MoviesSearch<strong>FlowCoordinator<\/strong><\/a><strong>\u00a0\u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u0435 View<\/strong>. \u041a\u043e\u0433\u0434\u0430 \u0432\u044b\u0437\u043e\u0432\u0443\u0442\u0441\u044f \u044d\u0442\u0438 \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u044b, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u043f\u043e\u043a\u0430\u0436\u0435\u0442 \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u043e \u0444\u0438\u043b\u044c\u043c\u0435. \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0437\u0434\u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435.<\/p>\n<p><a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/tree\/master\/ExampleMVVM\/Presentation\" rel=\"noopener noreferrer nofollow\">Presentation<\/a>\u00a0\u0441\u043b\u043e\u0439\u00a0\u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 MoviesList<strong>View<\/strong>Controller \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u0434\u0430\u0442\u043e\u0439(items) \u0438\u0437 MoviesList<strong>ViewModel<\/strong>.<\/p>\n<p>\u0423 UI \u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435 \u0438\u043b\u0438 \u043b\u043e\u0433\u0438\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (Business Models \u0438 UseCases), \u043e\u043d \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0443 ViewModel. \u042d\u0442\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441 \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432\u043e View (UI). \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043c\u0430\u043f\u043f\u0438\u043c Business Models \u043a ViewModel \u0432\u043d\u0443\u0442\u0440\u0438 ViewModel \u0438 \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u043c \u0438\u0445 \u0432 View.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438\u0437 View \u0432\u043e ViewModel, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u0438\u0441\u043a\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u043c\u044b:<\/p>\n<pre><code class=\"swift\">import UIKit final class MoviesListViewController: UIViewController, StoryboardInstantiable, UISearchBarDelegate { private var viewModel: MoviesListViewModel!  final class func create(with viewModel: MoviesListViewModel) -&amp;gt; MoviesListViewController {     let vc = MoviesListViewController.instantiateViewController()     vc.viewModel = viewModel     return vc }  override func viewDidLoad() {     super.viewDidLoad()      bind(to: viewModel) }  private func bind(to viewModel: MoviesListViewModel) {     viewModel.items.observe(on: self) { [weak self] items in         self?.moviesTableViewController?.items = items     }     viewModel.error.observe(on: self) { [weak self] error in         self?.showError(error)     } }  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {     guard let searchText = searchBar.text, !searchText.isEmpty else { return }     viewModel.didSearch(query: searchText) }  } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u041c\u044b \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c \u0437\u0430 \u0430\u0439\u0442\u0435\u043c\u0430\u043c\u0438 \u0438\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c View, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f. \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0437\u0434\u0435\u0441\u044c\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/Utils\/Observable.swift\" rel=\"noopener noreferrer nofollow\"><em>Observable<\/em><\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u0431\u0437\u043e\u0440\u0435 MVVM \u043d\u0438\u0436\u0435.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e\u00a0<em>showMovieDetails<\/em>(movie:)\u00a0\u0432\u00a0<strong>Actions \u043d\u0430\u0448\u0435\u0439<\/strong>MoviesList<strong>ViewModel\u00a0\u0432\u043d\u0443\u0442\u0440\u0438<\/strong>\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/Flows\/MoviesSearchFlowCoordinator.swift\" rel=\"noopener noreferrer nofollow\">MoviesSearch<strong>FlowCoordinator<\/strong><\/a>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u043e\u0432\u0430\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u0438\u0437 flow coordinator:<\/p>\n<pre><code class=\"swift\">protocol MoviesSearchFlowCoordinatorDependencies  { func makeMoviesListViewController() -> UIViewController func makeMoviesDetailsViewController(movie: Movie) -> UIViewController } final class MoviesSearchFlowCoordinator { private weak var navigationController: UINavigationController? private let dependencies: MoviesSearchFlowCoordinatorDependencies  init(navigationController: UINavigationController,      dependencies: MoviesSearchFlowCoordinatorDependencies) {     self.navigationController = navigationController     self.dependencies = dependencies }  func start() {     \/\/ \u0412\u0430\u0436\u043d\u043e: \u0422\u0443\u0442 \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0441\u0438\u043b\u044c\u043d\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u0447\u0435\u0440\u0435\u0437 \u043a\u043b\u043e\u0443\u0436\u0435\u0440, \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u0432 \u043a \u0434\u0430\u043d\u043d\u043e\u043c\u0443 \u0444\u043b\u043e\u0443 \u043d\u0435 \u043d\u0430\u0434\u043e \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0441\u0441\u044b\u043b\u043a\u0435     let actions = MoviesListViewModelActions(showMovieDetails: showMovieDetails)     let vc = dependencies.makeMoviesListViewController(actions: actions)      navigationController?.pushViewController(vc, animated: false) }  private func showMovieDetails(movie: Movie) {     let vc = dependencies.makeMoviesDetailsViewController(movie: movie)     navigationController?.pushViewController(vc, animated: true) }  } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u00a0<strong>Flow Coordinator<\/strong>\u00a0\u0434\u043b\u044f \u043b\u043e\u0433\u0438\u043a\u0438 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u044f \u043e\u0431\u044a\u0435\u043c View Controllers \u0438\u00a0\u0441\u043d\u0438\u0436\u0430\u044f \u0438\u0445 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c. \u0423 \u043d\u0430\u0441\u00a0<strong>strong<\/strong>\u00a0\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430\u00a0<strong>Flow<\/strong>\u00a0(\u0441 \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u0430\u043c\u0438, self \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438), \u0447\u0442\u043e\u0431\u044b\u00a0<strong>Flow<\/strong>\u00a0\u043d\u0435 \u0434\u0435\u0430\u043b\u043b\u043e\u0446\u0438\u0440\u043e\u0432\u0430\u043b\u0441\u044f, \u043f\u043e\u043a\u0430 \u043e\u043d \u043d\u0443\u0436\u0435\u043d.<\/p>\n<p>\u0421 \u044d\u0442\u0438\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u043c\u044b \u043b\u0435\u0433\u043a\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0440\u0430\u0437\u043d\u044b\u0435 View \u0441 \u043e\u0434\u043d\u043e\u0439 ViewModel, \u043d\u0435 \u043c\u0435\u043d\u044f\u044f \u0435\u0435. \u041f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c \u0441 iOS 13.0 \u0438 \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c SwiftUI View \u0432\u043c\u0435\u0441\u0442\u043e UIKit \u0438 \u0431\u0438\u043d\u0434\u0438\u043c \u0435\u0435 \u043a \u0442\u043e\u0439 \u0436\u0435 ViewModel (\u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c UIKit View). \u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435\u00a0\u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 SwiftUI \u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f MoviesQueriesSuggestionsList*.\u00a0<strong>\u041d\u0443\u0436\u0435\u043d \u0445\u043e\u0442\u044f \u0431\u044b Xcode 11 Beta<\/strong>*.<\/p>\n<pre><code class=\"swift\">\/\/ MARK: - Movies Queries Suggestions List func makeMoviesQueriesSuggestionsListViewController(didSelect: @escaping MoviesQueryListViewModelDidSelectAction) -> UIViewController { if #available(iOS 13.0, *) { \/\/ SwiftUI let view = MoviesQueryListView(viewModelWrapper: makeMoviesQueryListViewModelWrapper(didSelect: didSelect)) return UIHostingController(rootView: view) } else { \/\/ UIKit return MoviesQueriesTableViewController.create(with: makeMoviesQueryListViewModel(didSelect: didSelect)) } } <\/code><\/pre>\n<h3>Data \u0441\u043b\u043e\u0439<\/h3>\n<p>\u042d\u0442\u043e\u0442 \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u00a0DefaultMovies<strong>Repository.<\/strong>\u00a0\u041e\u043d \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d \u043d\u0430 interfaces, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 Domain Layer (<strong>Dependency Inversion<\/strong>). \u041c\u044b \u0442\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 JSON data(<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Data\/Network\/DataMapping\/MoviesResponseDTO%2BMapping.swift\" rel=\"noopener noreferrer nofollow\">Decodable conformance<\/a>) \u0438\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Data\/PersistentStorages\/MoviesResponseStorage\/EntityMapping\/MoviesResponseEntity%2BMapping.swift\" rel=\"noopener noreferrer nofollow\">CoreData Entities<\/a>\u00a0\u0432 Domain Models.<\/p>\n<pre><code class=\"swift\">final class DefaultMoviesRepository { private let dataTransferService: DataTransfer  init(dataTransferService: DataTransfer) {     self.dataTransferService = dataTransferService }  } extension DefaultMoviesRepository: MoviesRepository { public func fetchMoviesList(query: MovieQuery, page: Int, completion: @escaping (Result&amp;lt;MoviesPage, Error&amp;gt;) -&amp;gt; Void) -&amp;gt; Cancellable? {      let endpoint = APIEndpoints.getMovies(with: MoviesRequestDTO(query: query.query,                                                                  page: page))     return dataTransferService.request(with: endpoint) { (response: Result&amp;lt;MoviesResponseDTO, Error&amp;gt;) in         switch response {         case .success(let moviesResponseDTO):             completion(.success(moviesResponseDTO.toDomain()))         case .failure(let error):             completion(.failure(error))         }     } }  } \/\/ MARK: - Data Transfer Object (DTO) \/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0434\u043b\u044f encode\/decode JSON response \u0432 \u0434\u043e\u043c\u0435\u043d, \u0432\u043d\u0443\u0442\u0440\u0438 DataTransferService struct MoviesRequestDTO: Encodable { let query: String let page: Int } struct MoviesResponseDTO: Decodable { private enum CodingKeys: String, CodingKey { case page case totalPages = \"total_pages\" case movies = \"results\" } let page: Int let totalPages: Int let movies: [MovieDTO] } ... \/\/ MARK: - \u041c\u0430\u043f\u0438\u043d\u0433 \u0432 Domain extension MoviesResponseDTO { func toDomain() -> MoviesPage { return .init(page: page, totalPages: totalPages, movies: movies.map { $0.toDomain() }) } } ... <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0Data Transfer Objects DTO \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u043e\u0441\u0440\u0435\u0434\u043d\u0438\u043a \u0434\u043b\u044f \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430 \u0438\u0437 JSON response \u0432 Domain. \u0422\u0430\u043a\u0436\u0435, \u0435\u0441\u043b\u0438 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c endpoint response, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c Data Transfer Objects \u0432 persistent storage, \u0437\u0430\u043c\u0430\u043f\u0438\u0432 \u0438\u0445 \u0432 Persistent objects(DTO -> NSManagedObject).<\/p>\n<p>\u0412 \u0446\u0435\u043b\u043e\u043c Data Repositories \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e API Data Service \u0438 Persistent Data Storage. Data Repository \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u044d\u0442\u0438\u043c\u0438 \u0434\u0432\u0443\u043c\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435. \u041d\u0430\u0434\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0443 persistent storage \u0434\u043b\u044f \u0430\u0443\u0442\u043f\u0443\u0442\u0430 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 (NSManagedObject \u0437\u0430\u043c\u0430\u043f\u043b\u0435\u043d\u044b \u0432 Domain \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e DTO object, \u0438 \u0434\u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0432 \u00a0<em>cached data closure<\/em>). \u041f\u043e\u0442\u043e\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c API Data Service, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445. \u0417\u0430\u0442\u0435\u043c Persistent Storage \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u0442\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 (DTO \u0437\u0430\u043c\u0430\u043f\u043b\u0435\u043d\u044b \u0432 Persistent Objects \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b). \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e DTO \u043c\u0430\u043f\u044f\u0442\u0441\u044f \u0432 Domain \u0438 \u0434\u043e\u0441\u0442\u0430\u044e\u0442\u0441\u044f \u0432\u00a0<em>updated data\/completion closure<\/em>. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0440\u0430\u0437\u0443 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0414\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0441 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u043e\u043c, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u0443\u0432\u0438\u0434\u044f\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Persistent Storage.\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Data\/Repositories\/DefaultMoviesRepository.swift\" rel=\"noopener noreferrer nofollow\"><em>example<\/em><\/a><\/p>\n<p>\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0438 API \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u043c\u0435\u043d\u0435\u043d\u044b \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u043c\u0438 (\u043e\u0442 CoreData \u0434\u043e Realm, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440). \u0412\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b \u044d\u0442\u0438\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e Storage \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u0435\u0442\u0430\u043b\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430.<\/p>\n<h3>Infrastructure\u00a0\u0441\u043b\u043e\u0439 (Network)<\/h3>\n<p>\u042d\u0442\u043e \u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 \u0441\u0435\u0442\u0435\u0432\u044b\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c, \u043e\u043d\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c\u00a0<a href=\"https:\/\/github.com\/Alamofire\/Alamofire\" rel=\"noopener noreferrer nofollow\">Alamofire<\/a>\u00a0(\u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a). \u0415\u0435 \u043c\u043e\u0436\u043d\u043e \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u0430\u0437\u043e\u0432\u044b\u043c URL). \u041e\u043d\u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 endpoints \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0435\u0442\u043e\u0434\u044b \u043c\u0430\u043f\u0438\u043d\u0433\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Decodable).<\/p>\n<pre><code class=\"swift\">struct APIEndpoints { static func getMovies(with moviesRequestDTO: MoviesRequestDTO) -&amp;gt; Endpoint&amp;lt;MoviesResponseDTO&amp;gt; {      return Endpoint(path: \"search\/movie\/\",                     method: .get,                     queryParametersEncodable: moviesRequestDTO) }  } let config = ApiDataNetworkConfig(baseURL: URL(string: appConfigurations.apiBaseURL)!, queryParameters: [\"api_key\": appConfigurations.apiKey]) let apiDataNetwork = DefaultNetworkService(session: URLSession.shared, config: config) let endpoint = APIEndpoints.getMovies(with: MoviesRequestDTO(query: query.query, page: page)) dataTransferService.request(with: endpoint) { (response: Result&lt;MoviesResponseDTO, Error>) in let moviesPage = try? response.get() } <\/code><\/pre>\n<p>\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435:\u00a0<a href=\"https:\/\/github.com\/kudoleh\/SENetworking\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/kudoleh\/SENetworking<\/a><\/p>\n<h2>MVVM<\/h2>\n<p><strong>Model-View-ViewModel<\/strong>\u00a0\u043f\u0430\u0442\u0442\u0435\u0440\u043d (MVVM) \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 UI \u0438 Domain.<\/p>\n<p>\u0412\u043c\u0435\u0441\u0442\u0435 \u0441 Clean Architecture \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 Presentation \u0438 UI \u0441\u043b\u043e\u044f\u043c\u0438.<\/p>\n<p>\u0420\u0430\u0437\u043d\u044b\u0435 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 view \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u044b \u0441 \u043e\u0434\u043d\u043e\u0439 ViewModel. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c CarsAroundList<strong>View<\/strong>\u00a0\u0438 CarsAroundMap<strong>View<\/strong>\u00a0\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c CarsAround<strong>ViewModel<\/strong>\u00a0\u0434\u043b\u044f \u043e\u0431\u043e\u0438\u0445. \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u043d\u043e View \u0438\u0437 UIKit, \u0430 \u0434\u0440\u0443\u0433\u043e\u0435 View \u0438\u0437 SwiftUI. \u0412\u0430\u0436\u043d\u043e \u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u0435 \u043d\u0430\u0434\u043e \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c UIKit, WatchKit \u0438\u043b\u0438 SwiftUI \u0432\u043d\u0443\u0442\u0440\u0438 \u0432\u0430\u0448\u0435\u0439 ViewModel. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0435\u0435 \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8f5\/9da\/c90\/8f59dac90a0ea31618abd71b4f494344.png\" width=\"1400\" height=\"372\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8f5\/9da\/c90\/8f59dac90a0ea31618abd71b4f494344.png\"\/><\/figure>\n<p><strong>Data Binding<\/strong>\u00a0\u043c\u0435\u0436\u0434\u0443\u00a0<strong>View<\/strong>\u00a0\u0438\u00a0<strong>ViewModel<\/strong>\u00a0\u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e closures, delegates \u0438\u043b\u0438 observables (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, RxSwift). Combine \u0438 SwiftUI \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c, \u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u044d\u0442\u043e iOS 13. \u0423\u00a0<strong>View \u0435\u0441\u0442\u044c \u043f\u0440\u044f\u043c\u043e\u0435 \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043a<\/strong>\u00a0<strong>ViewModel, \u043e\u043d\u043e \u0435\u0439 \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 \u043e\u0442 \u043a\u0430\u0436\u0434\u043e\u043c \u0441\u043e\u0431\u044b\u0442\u0438\u0438, \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u0435\u0434\u0448\u0435\u043c \u0432\u043e<\/strong>\u00a0View. \u0423 ViewModel \u043d\u0435\u0442 \u043f\u0440\u044f\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 View (\u0442\u043e\u043b\u044c\u043a\u043e Data Binding).<\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044e Closure \u0438 didSet, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439:<\/p>\n<pre><code class=\"swift\">public final class Observable&lt;Value> { private var closure: ((Value) -&amp;gt; ())?  public var value: Value {     didSet { closure?(value) } }  public init(_ value: Value) {     self.value = value }  public func observe(_ closure: @escaping (Value) -&amp;gt; Void) {     self.closure = closure     closure(value) }  } <\/code><\/pre>\n<p>\u0412\u0430\u0436\u043d\u043e: \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f Observable, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u043d\u0443\u044e \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0438 observer removal:\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/Utils\/Observable.swift\" rel=\"noopener noreferrer nofollow\"><strong>Observable<\/strong><\/a><strong>.<\/strong><\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 data binding \u0438\u0437 ViewController:<\/p>\n<pre><code class=\"swift\">final class ExampleViewController: UIViewController { private var viewModel: MoviesListViewModel!  private func bind(to viewModel: ViewModel) {     self.viewModel = viewModel     viewModel.items.observe(on: self) { [weak self] items in         self?.tableViewController?.items = items         \/\/ \u0412\u0430\u0436\u043d\u043e: \u041d\u0435\u043b\u044c\u0437\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c viewModel \u0432\u043d\u0443\u0442\u0440\u0438 \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u0430, \u044d\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0441\u0442 retain cycle memory leak (viewModel.items.value - \u043d\u0435\u043b\u044c\u0437\u044f)         \/\/ self?.tableViewController?.items = viewModel.items.value \/\/ \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 retain cycle. \u0414\u043e\u0441\u0442\u0443\u043f \u043a viewModel \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 self?.viewModel     }     \/\/ \u0438\u043b\u0438 \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443     viewModel.items.observe(on: self) { [weak self] in self?.tableViewController?.items = $0 } }  override func viewDidLoad() {     super.viewDidLoad()     bind(to: viewModel)     viewModel.viewDidLoad() }  } protocol ViewModelInput { func viewDidLoad() } protocol ViewModelOutput { var items: Observable&lt;[ItemViewModel]> { get } } protocol ViewModel: ViewModelInput, ViewModelOutput {} <\/code><\/pre>\n<p>\u0412\u0430\u0436\u043d\u043e: \u0414\u043e\u0441\u0442\u0443\u043f \u043a viewModel \u0438\u0437 observing closure \u0437\u0430\u043a\u0440\u044b\u0442, \u043e\u043d \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 retain cycle(\u0443\u0442\u0435\u0447\u043a\u0430 \u043f\u0430\u043c\u044f\u0442\u0438). \u0414\u043e\u0441\u0442\u0443\u043f \u043a viewModel \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437: self?.viewModel.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 data binding \u0432 TableViewCell (Reusable Cell):<\/p>\n<pre><code class=\"swift\">final class MoviesListItemCell: UITableViewCell { private var viewModel: MoviesListItemViewModel! { didSet { unbind(from: oldValue) } }  func fill(with viewModel: MoviesListItemViewModel) {     self.viewModel = viewModel     bind(to: viewModel) }  private func bind(to viewModel: MoviesListItemViewModel) {     viewModel.posterImage.observe(on: self) { [weak self] in self?.imageView.image = $0.flatMap(UIImage.init) } }  private func unbind(from item: MoviesListItemViewModel?) {     item?.posterImage.remove(observer: self) }  } <\/code><\/pre>\n<p>\u0412\u0430\u0436\u043d\u043e: \u041d\u0430\u0434\u043e \u0440\u0430\u0437\u0431\u0438\u043d\u0434\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 view \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u0437\u0443\u0435\u0442\u0441\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, UITableViewCell)<\/p>\n<p><strong>MVVM \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0437\u0434\u0435\u0441\u044c:<\/strong>\u00a0\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/tree\/master\/MVVM%20Templates\" rel=\"noopener noreferrer nofollow\">here<\/a><\/p>\n<p><strong>MVVMs Communication<\/strong><\/p>\n<h3>\u0414\u0435\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h3>\n<p>ViewModel \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 MVVM \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u0446\u0438\u0440\u0443\u0435\u0442 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 ViewModel \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 MVVM \u0447\u0435\u0440\u0435\u0437 \u043f\u0430\u0442\u0442\u0435\u0440\u043d \u0434\u0435\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/8c2\/a56\/aff\/8c2a56aff726e5647412f657fafeb463.png\" width=\"1400\" height=\"241\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8c2\/a56\/aff\/8c2a56aff726e5647412f657fafeb463.png\"\/><\/figure>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c ItemsList<strong>ViewModel<\/strong>\u00a0\u0438 ItemEdit<strong>ViewModel<\/strong>. \u041f\u043e\u0442\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b ItemEdit<strong>ViewModelDelegate \u0441 \u043c\u0435\u0442\u043e\u0434\u043e\u043c<\/strong>\u00a0ItemEditViewModelDidEditItem(item). \u0418 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043d\u0430 \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b:<\/p>\n<p>extension ListItemsViewModel: ItemEditViewModel<strong>Delegate<\/strong><\/p>\n<pre><code class=\"swift\">\/\/ Step 1: \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 \u0434\u0435\u043b\u0435\u0433\u0430\u0442 \u0438 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043f\u0435\u0440\u0432\u0443\u044e ViewModel \u043a\u0430\u043a weak property protocol MoviesQueryListViewModelDelegate: class { func moviesQueriesListDidSelect(movieQuery: MovieQuery) } ... final class DefaultMoviesQueryListViewModel: MoviesListViewModel { private weak var delegate: MoviesQueryListViewModelDelegate? func didSelect(item: MoviesQueryListViewItemModel) {     \/\/ Note: \u0417\u0430\u043c\u0430\u043f\u0438\u043c View Item Model \u043a Domain Enity     delegate?.moviesQueriesListDidSelect(movieQuery: MovieQuery(query: item.query)) }  } \/\/ Step 2:  \u041f\u043e\u0434\u043f\u0438\u0448\u0435\u043c \u0432\u0442\u043e\u0440\u0443\u044e ViewModel \u043d\u0430 \u044d\u0442\u043e\u0442 \u0434\u0435\u043b\u0435\u0433\u0430\u0442 extension MoviesListViewModel: MoviesQueryListViewModelDelegate { func moviesQueriesListDidSelect(movieQuery: MovieQuery) { update(movieQuery: movieQuery) } } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 Delegates \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u0432\u0430\u0442\u044c Responders: ItemEditViewModel<strong>Responder<\/strong><\/p>\n<h3>Closures<\/h3>\n<p>\u0414\u0440\u0443\u0433\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 closures, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u043d\u044a\u0435\u0446\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e FlowCoordinator. \u0412 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u043a\u0430\u043a\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/MoviesList\/ViewModel\/MoviesListViewModel.swift\" rel=\"noopener noreferrer nofollow\">MoviesListViewModel<\/a>\u00a0\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u00a0<strong>closure<\/strong>\u00a0<em>showMovieQueriesSuggestions<\/em>\u00a0\u0434\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430 MoviesQueriesSuggestions<strong>View<\/strong>.\u0422\u0430\u043a\u0436\u0435 \u043e\u043d \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u00a0<em>(**_didSelect: MovieQuery) -> Void<\/em>\u00a0\u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0432\u043e\u00a0<strong>View<\/strong>. \u041a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 \u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/Flows\/MoviesSearchFlowCoordinator.swift\" rel=\"noopener noreferrer nofollow\">MoviesSearch<strong>FlowCoordinator<\/strong><\/a>:<\/p>\n<pre><code class=\"swift\">\/\/ MoviesQueryList.swift \/\/ Step 1: \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 \u043a\u043b\u043e\u0436\u0443\u0435\u0440 \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 ViewModel typealias MoviesQueryListViewModelDidSelectAction = (MovieQuery) -> Void \/\/ Step 2: \u0412\u044b\u0437\u043e\u0432\u0438\u0442\u0435 \u043a\u043b\u043e\u0443\u0436\u0435\u0440 \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f class MoviesQueryListViewModel { init(didSelect: MoviesQueryListViewModelDidSelectAction? = nil) { self.didSelect = didSelect } func didSelect(item: MoviesQueryListItemViewModel) { didSelect?(MovieQuery(query: item.query)) } } \/\/ MoviesQueryList.swift \/\/ Step 3: \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438 MoviesQueryListView \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043a\u043b\u043e\u0443\u0436\u0435\u0440 \u043a\u0430\u043a \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 (_ didSelect: MovieQuery) -> Void struct MoviesListViewModelActions { let showMovieQueriesSuggestions: @escapingg (_ didSelect: MovieQuery) -> Void) -> Void } class MoviesListViewModel { var actions: MoviesListViewModelActions? func showQueriesSuggestions() {     actions?.showMovieQueriesSuggestions { self.update(movieQuery: $0) }     \/\/or simpler actions?.showMovieQueriesSuggestions(update) }  } \/\/ FlowCoordinator.swift \/\/ Step 4: \u0412\u043d\u0443\u0442\u0440\u0438 FlowCoordinator \u043c\u044b \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u043c \u0434\u0432\u0435 viewModels, \u0438\u043d\u044a\u0435\u0446\u0438\u0440\u0443\u044f \u043a\u043b\u043e\u0443\u0436\u0435\u0440 \u043a\u0430\u043a self \u0444\u0443\u043d\u043a\u0446\u0438\u044e class MoviesSearchFlowCoordinator { func start() { let actions = MoviesListViewModelActions(showMovieQueriesSuggestions: self.showMovieQueriesSuggestions) let vc = dependencies.makeMoviesListViewController(actions: actions) present(vc) } private func showMovieQueriesSuggestions(didSelect: @escaping (MovieQuery) -&amp;gt; Void) {     let vc = dependencies.makeMoviesQueriesSuggestionsListViewController(didSelect: didSelect)     present(vc) }  } <\/code><\/pre>\n<h2>\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0441\u043b\u043e\u0435\u0432 \u043d\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 (\u043c\u043e\u0434\u0443\u043b\u0438)<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 (Domain, Presentation, UI, Data, Infrastructure Network) \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438.<\/p>\n<p>New Project -> Create Project\u2026 -> Cocoa Touch Framework<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u044d\u0442\u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 \u0432 \u043d\u0430\u0448\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f CocoaPods. \u0412\u043e\u0442 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u0438\u043c\u0435\u0440:\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/MVVM%20Modular%20Layers%20Pods.zip\" rel=\"noopener noreferrer nofollow\">working example here<\/a>.<\/p>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u0412\u0430\u043c \u043d\u0430\u0434\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c\u00a0<strong>ExampleMVVM.xcworkspace<\/strong>\u00a0 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u00a0<strong>pod install, \u0447\u0442\u043e\u0431\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0438\u0437-\u0437\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c.<\/strong><\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a19\/8f9\/2cb\/a198f92cb447ff63e39d7491256d4ece.png\" width=\"1100\" height=\"456\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a19\/8f9\/2cb\/a198f92cb447ff63e39d7491256d4ece.png\"\/><\/figure>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ab8\/7dd\/be3\/ab87ddbe3e765b4e0cfaa1d9f9bd6627.png\" width=\"484\" height=\"328\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ab8\/7dd\/be3\/ab87ddbe3e765b4e0cfaa1d9f9bd6627.png\"\/><\/figure>\n<h2>Dependency Injection Container<\/h2>\n<p><strong>Dependency injection<\/strong>\u00a0\u044d\u0442\u043e \u0442\u0435\u0445\u043d\u0438\u043a\u0430, \u043f\u0440\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u0434\u0438\u043d \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430. \u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Application\/DIContainer\/AppDIContainer.swift\" rel=\"noopener noreferrer nofollow\">DIContainer<\/a>\u00a0\u0432 \u0432\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044d\u0442\u043e \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u044e\u043d\u0438\u0442 \u0432\u0441\u0435\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439.<\/p>\n<p><strong>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c dependencies factory protocols<\/strong><\/p>\n<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c dependencies protocol, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043b\u0435\u0433\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Application\/DIContainer\/MoviesSceneDIContainer.swift\" rel=\"noopener noreferrer nofollow\">DIContainer<\/a>. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0430\u0434\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b MoviesSearchFlowCoordinator<strong>Dependencies<\/strong>\u00a0 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c MoviesScene<strong>DIContainer<\/strong>\u00a0\u043f\u043e\u0434 \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b, \u0437\u0430\u0442\u0435\u043c \u0438\u043d\u044a\u0435\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432 MoviesSearch<strong>FlowCoordinator, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043d\u0443\u0436\u043d\u0430 \u044d\u0442\u043e \u0438\u043d\u044a\u0435\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u043f\u043e\u043a\u0430\u0437\u0430<\/strong>\u00a0MoviesList<strong>View<\/strong>Controller. \u0412\u043e\u0442 \u043d\u0443\u0436\u043d\u044b\u0435 \u0448\u0430\u0433\u0438:<\/p>\n<pre><code class=\"swift\">\/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 Dependencies protocol \u0434\u043b\u044f \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0438\u043b\u0438 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b protocol MoviesSearchFlowCoordinatorDependencies  { func makeMoviesListViewController() -> MoviesListViewController } class MoviesSearchFlowCoordinator { private let dependencies: MoviesSearchFlowCoordinatorDependencies  init(dependencies: MoviesSearchFlowCoordinatorDependencies) {     self.dependencies = dependencies }  ... } \/\/ \u041f\u043e\u0434\u043f\u0438\u0448\u0438\u0442\u0435 DIContainer \u043d\u0430 \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b extension MoviesSceneDIContainer: MoviesSearchFlowCoordinatorDependencies {} \/\/ \u0418\u043d\u044a\u0435\u0446\u0438\u0440\u0443\u0439\u0442\u0435 MoviesSceneDIContainer self \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 final class MoviesSceneDIContainer { ... \/\/ MARK: - Flow Coordinators func makeMoviesSearchFlowCoordinator(navigationController: UINavigationController) -> MoviesSearchFlowCoordinator { return MoviesSearchFlowCoordinator(navigationController: navigationController, dependencies: self) } } <\/code><\/pre>\n<p><strong>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f closures<\/strong><\/p>\n<p>\u0414\u0440\u0443\u0433\u0430\u044f \u043e\u043f\u0446\u0438\u044f \u044d\u0442\u043e \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u044b. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 \u043a\u043b\u043e\u0443\u0436\u0435\u0440 \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0438 \u043f\u043e\u0442\u043e\u043c \u0432 \u043d\u0435\u0433\u043e \u044d\u0442\u043e\u0442 \u043a\u043b\u043e\u0443\u0436\u0435\u0440 \u0438\u043d\u044a\u0435\u0446\u0438\u0440\u0443\u0439\u0442\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"swift\">\/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u0435 makeMoviesListViewController \u043a\u043b\u043e\u0443\u0436\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 MoviesListViewController class MoviesSearchFlowCoordinator { private var makeMoviesListViewController: () -&amp;gt; MoviesListViewController  init(navigationController: UINavigationController,      makeMoviesListViewController: @escaping () -&amp;gt; MoviesListViewController) {     ...     self.makeMoviesListViewController = makeMoviesListViewController } ...  } \/\/ \u0418\u043d\u044a\u0435\u0446\u0438\u0440\u0443\u0439\u0442\u0435 \u0443 MoviesSceneDIContainer self.makeMoviesListViewController \u0444\u0443\u043d\u0446\u043a\u0438\u044e \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 final class MoviesSceneDIContainer { ... \/\/ MARK: - Flow Coordinators func makeMoviesSearchFlowCoordinator(navigationController: UINavigationController) -> MoviesSearchFlowCoordinator { return MoviesSearchFlowCoordinator(navigationController: navigationController, makeMoviesListViewController: self.makeMoviesListViewController) } \/\/ MARK: - Movies List func makeMoviesListViewController() -&amp;gt; MoviesListViewController {     ... }  } <\/code><\/pre>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p><strong>\u0421\u0430\u043c\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 &#8212; Clean Architecture(\u0441\u043b\u043e\u044f\u043c\u0438), MVVM, \u0438 Redux.<\/strong><\/p>\n<p>MVVM \u0438 Clean Architecture \u043c\u043e\u0436\u043d\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u0434\u0435\u043b\u044c\u043d\u043e, \u043d\u043e MVVM \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u043d\u0443\u0442\u0440\u0438 Presentation \u0441\u043b\u043e\u044f, \u0442\u043e\u0433\u0434\u0430 \u043a\u0430\u043a Clean Architecture \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u0442 \u043a\u043e\u0434 \u043d\u0430 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u044b\u0435 \u0441\u043b\u043e\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c.<\/p>\n<p>\u0412\u0430\u0436\u043d\u043e \u043d\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 Use Case, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 Use Case \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u0442, \u043a\u0440\u043e\u043c\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 Repository. \u0422\u0430\u043a \u0432\u0430\u0448\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u0430 \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0432\u0438\u0434\u0438\u0442 Use cases.<\/p>\n<p>\u0425\u043e\u0442\u044c \u044d\u0442\u043e \u0438 \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430,\u00a0<strong>\u044d\u0442\u043e \u043d\u0435 \u043f\u0430\u043d\u0430\u0446\u0435\u044f<\/strong>. \u0412\u044b \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0435 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u0434\u044b.<\/p>\n<p>Clean Architecture \u0445\u043e\u0440\u043e\u0448\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 TDD (Test Driven Development). \u041e\u043d\u0430 \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u043d\u044b\u043c \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0437\u0430\u043c\u0435\u043d\u044b \u0441\u043b\u043e\u0435\u0432 (UI and Data).<\/p>\n<p><a href=\"https:\/\/www.amazon.com\/Domain-Driven-Design-Tackling-Complexity-Software\/dp\/0321125215\" rel=\"noopener noreferrer nofollow\">Domain-Driven Design<\/a>\u00a0(DDD) \u0442\u043e\u0436\u0435 \u0445\u043e\u0440\u043e\u0448\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u00a0<a href=\"https:\/\/8thlight.com\/blog\/uncle-bob\/2012\/08\/13\/the-clean-architecture.html\" rel=\"noopener noreferrer nofollow\">Clean Architecture<\/a>(CA).<\/p>\n<p>\u0415\u0449\u0435 \u0438\u0437 software engineering best practices:<\/p>\n<ul>\n<li>\n<p><em>\u041d\u0435 \u043f\u0438\u0448\u0438\u0442\u0435 \u043a\u043e\u0434 \u0431\u0435\u0437 \u0442\u0435\u0441\u0442\u043e\u0432 (\u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 TDD)<\/em><\/p>\n<\/li>\n<li>\n<p>\u0414\u0435\u043b\u0430\u0439\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433<\/p>\n<\/li>\n<li>\n<p>\u0411\u0443\u0434\u044c\u0442\u0435 \u043f\u0440\u0430\u0433\u043c\u0430\u0442\u0438\u0447\u043d\u044b\u043c\u0438 \u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u0443\u0441\u0435\u0440\u0434\u0441\u0442\u0432\u0443\u0439\u0442\u0435<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0430\u0440\u0430\u0439\u0442\u0435\u0441\u044c \u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u043f\u0440\u0438 \u043b\u044e\u0431\u043e\u0439 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043e\u0442 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/808923\/\"> https:\/\/habr.com\/ru\/articles\/808923\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\" rel=\"noopener noreferrer nofollow\">\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434<\/a><\/p>\n<p>\u041f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u041f\u041e \u0432\u0430\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u0438\u0437\u0430\u0439\u043d-, \u043d\u043e \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b. \u0418\u0445 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e. \u0412 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u0430\u043c\u044b\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 &#8212; MVVM, Clean Architecture \u0438 Redux.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043f\u043e\u043a\u0430\u0436\u0435\u043c \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043a\u0430\u043a \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b MVVM \u0438 Clean Architecture \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b \u0432 iOS \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442\u0435\u0441\u044c Redux, <a href=\"https:\/\/www.kodeco.com\/books\/advanced-ios-app-architecture\" rel=\"noopener noreferrer nofollow\">\u0437\u0430\u0446\u0435\u043d\u0438\u0442\u0435 \u044d\u0442\u0443 \u043a\u043d\u0438\u0433\u0443<\/a>.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041a\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c \u043d\u0430 \u0441\u0445\u0435\u043c\u0435\u00a0<strong>Clean Architecture,<\/strong>\u00a0\u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u043b\u043e\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e &#8212;\u00a0<em>\u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0441\u043b\u043e\u0435\u0432 \u043e\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0445.<\/em>\u00a0\u0421\u0442\u0440\u0435\u043b\u043a\u0438, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u0441\u043d\u0430\u0440\u0443\u0436\u0438 \u0432\u043d\u0442\u0443\u0440\u044c \u044d\u0442\u043e Dependency Rule. \u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0434\u0442\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0441\u043b\u043e\u0435\u0432 \u0432\u043d\u0443\u0442\u0440\u044c \u043a \u0446\u0435\u043d\u0442\u0440\u0443.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438 \u0443 \u043d\u0430\u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438\u0441\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043b\u043e\u0438:<\/p>\n<p><strong>Presentation, Domain \u0438 Data<\/strong><\/p>\n<figure class=\"full-width\">\n<div><figcaption><strong>Domain<\/strong>\u00a0\u0441\u043b\u043e\u0439 (\u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0430) &#8212; \u0441\u0430\u043c\u044b\u0439 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0441\u043b\u043e\u0439 \u043d\u0430\u0448\u0435\u0439 \u201c\u043b\u0443\u043a\u043e\u0432\u0438\u0446\u044b\u201d (\u0431\u0435\u0437 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043e\u043d \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d). \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 Entities (\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\/\u0431\u0438\u0437\u043d\u0435\u0441 \u043c\u043e\u0434\u0435\u043b\u0438), Use Cases \u0438 Repository Interfaces. \u042d\u0442\u043e\u0442 \u0441\u043b\u043e\u0439 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445. \u0422\u0430\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c host-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0446\u0435\u043b\u044f\u0445, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435). \u041f\u043e\u044d\u0442\u043e\u043c\u0443 Domain Use Cases \u0442\u0435\u0441\u0442\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u0437\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434.\u00a0<strong>\u0412\u0430\u0436\u043d\u043e: Domain \u0441\u043b\u043e\u0439 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f \u0447\u0442\u043e-\u043b\u0438\u0431\u043e \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043b\u043e\u0435\u0432.<\/strong><\/figcaption><\/div>\n<\/figure>\n<ul>\n<li>\n<p>\u0425\u043e\u0440\u043e\u0448\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0432\u044b\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0432\u043e\u043a\u0440\u0443\u0433 Use Cases \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u043c\u043e\u0433\u043b\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 Use Cases, \u043d\u0435 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0443\u043b\u0437\u044b. \u042d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f\u00a0<a href=\"https:\/\/blog.cleancoder.com\/uncle-bob\/2011\/09\/30\/Screaming-Architecture.html\" rel=\"noopener noreferrer nofollow\"><strong>Screaming Architecture<\/strong><\/a><strong>.<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Presentation \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 UI (UIViewController, SwiftUI View).<\/strong>\u00a0\u0412\u044c\u044e\u0445\u0438 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0438\u0440\u044e\u0442\u0441\u044f \u0432\u044c\u044e-\u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 (\u0438\u043b\u0438 \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0435\u0440\u0430\u043c\u0438), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e Use Cases.\u00a0<strong>Presentation \u0441\u043b\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 Domain \u0441\u043b\u043e\u044f.<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Data<\/strong>\u00a0\u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438 \u043e\u0434\u0438\u043d\/\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e Data Source. \u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b \u0437\u0430 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0446\u0438\u044e \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u0430\u0442\u0430-\u0441\u043e\u0440\u0441\u043e\u0432. \u0414\u0430\u0442\u0430-\u0441\u043e\u0440\u0441\u044b \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0435 \u0438\u043b\u0438 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, persistent database).\u00a0<strong>Data \u0441\u043b\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 Domain \u0441\u043b\u043e\u044f.<\/strong>\u00a0\u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u043e\u0435 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u0430\u043f\u043f\u0438\u043d\u0433 Network JSON Data \u0432 \u043c\u043e\u0434\u0435\u043b\u0438 Domain.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430 \u0441\u0445\u0435\u043c\u0435 \u043d\u0438\u0436\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0441 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 Data Flow (Request\/Response). \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c \u0438\u043d\u0432\u0435\u0440\u0441\u0438\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (<strong>Dependency Inversion)<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0433\u0434\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f(\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b). \u041e\u0431\u044a\u044f\u0441\u043d\u0438\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u043b\u043e\u0439 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u0438 \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<figure class=\"full-width\"><\/figure>\n<h3>Data Flow<\/h3>\n<p>1.\u00a0<strong>View<\/strong>(UI)\u00a0\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 \u0438\u0437\u00a0<strong><em>ViewModel<\/em><\/strong><em>\u00a0(Presenter).<\/em><\/p>\n<p><em>2.\u00a0<\/em><strong><em>ViewModel<\/em><\/strong><em>\u00a0\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u00a0<\/em><strong><em>Use Case<\/em><\/strong><em>.<\/em><\/p>\n<p><em>3.\u00a0<\/em><strong><em>Use Case<\/em><\/strong><em>\u00a0\u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437\u00a0<\/em><strong><em>User\u00a0\u0438\u00a0Repositorie<\/em><\/strong><em>s.<\/em><\/p>\n<p><em>4. \u041a\u0430\u0436\u0434\u044b\u0439\u00a0<\/em><strong><em>Repository<\/em><\/strong><em>\u00a0\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435\u00a0<\/em><strong><em>Remote Data<\/em><\/strong><em>\u00a0(Network),\u00a0<\/em><strong><em>Persistent DB<\/em><\/strong><em>Storage Source \u0438\u043b\u0438 In-memory Data (\u0443\u0434\u0430\u043b\u0435\u043d\u043d\u0443\u044e \u0438\u043b\u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e).<\/em><\/p>\n<p><em>5. \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043d\u0430\u0437\u0430\u0434 \u0432\u00a0<\/em><strong><em>View<\/em><\/strong><em>(UI), \u0433\u0434\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432.<\/em><\/p>\n<h3>\u041d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/h3>\n<p><strong>Presentation Layer -> Domain Layer &lt;- Data Repositories Layer<\/strong><\/p>\n<p><strong><em>Presentation Layer (MVVM)<\/em><\/strong><em>\u00a0= ViewModels(Presenters) + Views(UI)<\/em><\/p>\n<p><strong><em>Domain Layer<\/em><\/strong><em>\u00a0= Entities + Use Cases + Repositories Interfaces<\/em><\/p>\n<p><strong><em>Data Repositories Layer<\/em><\/strong><em>\u00a0= Repositories Implementations + API(Network) + Persistence DB<\/em><\/p>\n<p><strong>\u041f\u0440\u0438\u043c\u0435\u0440 &#8212; \u043f\u0440\u043e\u0435\u043a\u0442 \u201cMovies App\u201d<\/strong><\/p>\n<figure class=\"full-width\"><\/figure>\n<p><strong>Domain \u0441\u043b\u043e\u0439<\/strong><\/p>\n<p>\u0412\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u043c Domain Layer. \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 Entities, SearchMovies<strong>UseCase, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0449\u0443\u0442 \u0444\u0438\u043b\u044c\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b. \u0422\u0430\u043a\u0436\u0435 \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442<\/strong>\u00a0<em>Data Repositories Interfaces, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u044b \u0434\u043b\u044f \u0438\u043d\u0432\u0435\u0440\u0441\u0438\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439.<\/em><\/p>\n<pre><code class=\"swift\">protocol SearchMoviesUseCase { func execute(requestValue: SearchMoviesUseCaseRequestValue, completion: @escaping (Result&lt;MoviesPage, Error>) -> Void) -> Cancellable? } final class DefaultSearchMoviesUseCase: SearchMoviesUseCase { private let moviesRepository: MoviesRepository private let moviesQueriesRepository: MoviesQueriesRepository  init(moviesRepository: MoviesRepository, moviesQueriesRepository: MoviesQueriesRepository) {     self.moviesRepository = moviesRepository     self.moviesQueriesRepository = moviesQueriesRepository }  func execute(requestValue: SearchMoviesUseCaseRequestValue,              completion: @escaping (Result&amp;lt;MoviesPage, Error&amp;gt;) -&amp;gt; Void) -&amp;gt; Cancellable? {     return moviesRepository.fetchMoviesList(query: requestValue.query, page: requestValue.page) { result in          if case .success = result {             self.moviesQueriesRepository.saveRecentQuery(query: requestValue.query) { _ in }         }          completion(result)     } }  } \/\/ Repository Interfaces protocol MoviesRepository { func fetchMoviesList(query: MovieQuery, page: Int, completion: @escaping (Result&lt;MoviesPage, Error>) -> Void) -> Cancellable? } protocol MoviesQueriesRepository { func fetchRecentsQueries(maxCount: Int, completion: @escaping (Result&lt;[MovieQuery], Error>) -> Void) func saveRecentQuery(query: MovieQuery, completion: @escaping (Result&lt;MovieQuery, Error>) -> Void) } <\/code><\/pre>\n<p>\u0412\u0430\u0436\u043d\u043e: \u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0437\u0434\u0430\u0442\u044c Use Cases \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Domain\/UseCases\/Protocol\/UseCase.swift\" rel=\"noopener noreferrer nofollow\"><em>UseCase<\/em><\/a>\u00a0\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439\u00a0<em>start()<\/em>\u00a0\u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0435\u0433\u043e \u0432\u0441\u0435 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 Use Cases. \u041e\u0434\u0438\u043d \u0438\u0437 \u043a\u0435\u0439\u0441\u043e\u0432 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0442\u0430\u043a \u0438 \u0434\u0435\u043b\u0430\u0435\u0442:\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Domain\/UseCases\/FetchRecentMovieQueriesUseCase.swift\" rel=\"noopener noreferrer nofollow\">FetchRecentMovieQueriesUseCase<\/a>. Use Cases \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u00a0<strong>Interactors<\/strong><\/p>\n<p>\u0412\u0430\u0436\u043d\u043e: \u00a0<em>UseCase<\/em>\u00a0\u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445\u00a0<em>UseCases<\/em><\/p>\n<p><strong>Presentation<\/strong>\u00a0\u0441\u043b\u043e\u0439<\/p>\n<p>\u042d\u0442\u043e\u0442 \u0441\u043b\u043e\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 MoviesList<strong>ViewModel<\/strong>\u00a0\u0441 \u0430\u0439\u0442\u0435\u043c\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0434\u0431\u044e\u0434\u0430\u044e\u0442\u0441\u044f \u0438\u0437 MoviesList<strong>View<\/strong>. MoviesList<strong>ViewModel<\/strong>\u00a0\u043d\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442 UIKit. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f \u0432\u043e ViewModel \u0442\u0430\u043a\u0438\u0435 \u0444\u0440\u0435\u0439\u0432\u043e\u0440\u043a\u0438 \u043a\u0430\u043a UIKit, SwiftUI \u0438\u043b\u0438 WatchKit, \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u0435\u0435 \u043b\u0443\u0447\u0448\u0435 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u0442\u044c\u00a0<strong>Views<\/strong>\u00a0\u0431\u0435\u0437 UIKit \u0438\u043b\u0438 SwiftUI \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043c\u0435\u043d\u044f\u0442\u044c\u00a0<strong>ViewModel<\/strong>.<\/p>\n<pre><code class=\"swift\">\/\/ \u0412\u0430\u0436\u043d\u043e: \u041d\u0435 \u0438\u043c\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c UIKit \u0438\u043b\u0438 SwiftUI protocol MoviesListViewModelInput { func didSearch(query: String) func didSelect(at indexPath: IndexPath) } protocol MoviesListViewModelOutput { var items: Observable&lt;[MoviesListItemViewModel]> { get } var error: Observable&lt;String> { get } } protocol MoviesListViewModel: MoviesListViewModelInput, MoviesListViewModelOutput { } struct MoviesListViewModelActions { \/\/ \u0412\u0430\u0436\u043d\u043e: \u0435\u0441\u043b\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u043c \u0432\u043d\u0443\u0442\u0440\u0438 Details \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c   \/\/ MoviesList \u044d\u043a\u0440\u0430\u043d \u043d\u043e\u0432\u044b\u043c \u0444\u0438\u043b\u044c\u043c\u043e\u043c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043b\u043e\u0443\u0436\u0435\u0440: \/\/  showMovieDetails: (Movie, @escaping (_ updated: Movie) -> Void) -> Void let showMovieDetails: (Movie) -> Void } final class DefaultMoviesListViewModel: MoviesListViewModel { private let searchMoviesUseCase: SearchMoviesUseCase private let actions: MoviesListViewModelActions?  private var movies: [Movie] = []  \/\/ MARK: - OUTPUT let items: Observable&amp;lt;[MoviesListItemViewModel]&amp;gt; = Observable([]) let error: Observable&amp;lt;String&amp;gt; = Observable(\"\")  init(searchMoviesUseCase: SearchMoviesUseCase,      actions: MoviesListViewModelActions) {     self.searchMoviesUseCase = searchMoviesUseCase     self.actions = actions }  private func load(movieQuery: MovieQuery) {      searchMoviesUseCase.execute(movieQuery: movieQuery) { result in         switch result {         case .success(let moviesPage):             \/\/ \u0412\u0430\u0436\u043d\u043e: \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u0431\u044f\u0437\u0430\u043d\u044b \u0437\u0430\u043c\u0430\u043f\u0438\u0442\u044c \u0438\u0437  Domain Entities \u0432 Item View Models. \u0420\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 Domain \u0438 View             self.items.value += moviesPage.movies.map(MoviesListItemViewModel.init)             self.movies += moviesPage.movies         case .failure:             self.error.value = NSLocalizedString(\"Failed loading movies\", comment: \"\")         }     } }  } \/\/ MARK: - INPUT. View event-\u043c\u0435\u0442\u043e\u0434\u044b extension MoviesListViewModel { func didSearch(query: String) {     load(movieQuery: MovieQuery(query: query)) }  func didSelect(at indexPath: IndexPath) {     actions?.showMovieDetails(movies[indexPath.row]) }  } \/\/ \u0412\u0430\u0436\u043d\u043e: \u042d\u0442\u0430 \u0432\u044c\u044e\u043c\u043e\u0434\u0435\u043b\u044c - \u0434\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u0430\u043a\u0443\u044e-\u043b\u0438\u0431\u043e domain \u043c\u043e\u0434\u0435\u043b\u044c, \u0447\u0442\u043e\u0431\u044b \u043a \u043d\u0435\u0439 \u043d\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u043b\u0438\u0441\u044c view struct MoviesListItemViewModel: Equatable { let title: String } extension MoviesListItemViewModel { init(movie: Movie) { self.title = movie.title ?? \"\" } } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b MoviesList<strong>ViewModelInput<\/strong>\u00a0\u0438 MoviesList<strong>ViewModelOutput, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c<\/strong>MoviesList<strong>View<\/strong>Controller \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c ( \u0441\u0434\u0435\u043b\u0430\u0432 \u043c\u043e\u043a\u00a0<strong>ViewModel)<\/strong>\u00a0. \u0422\u0430\u043a\u0436\u0435 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u044b MoviesListViewModel<strong>Actions<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0430\u044e\u0442\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/Flows\/MoviesSearchFlowCoordinator.swift\" rel=\"noopener noreferrer nofollow\">MoviesSearch<strong>FlowCoordinator<\/strong><\/a><strong>\u00a0\u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u0435 View<\/strong>. \u041a\u043e\u0433\u0434\u0430 \u0432\u044b\u0437\u043e\u0432\u0443\u0442\u0441\u044f \u044d\u0442\u0438 \u043a\u043b\u043e\u0443\u0436\u0435\u0440\u044b, \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u043f\u043e\u043a\u0430\u0436\u0435\u0442 \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u043e \u0444\u0438\u043b\u044c\u043c\u0435. \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0437\u0434\u043d\u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435.<\/p>\n<p><a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/tree\/master\/ExampleMVVM\/Presentation\" rel=\"noopener noreferrer nofollow\">Presentation<\/a>\u00a0\u0441\u043b\u043e\u0439\u00a0\u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 MoviesList<strong>View<\/strong>Controller \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u0434\u0430\u0442\u043e\u0439(items) \u0438\u0437 MoviesList<strong>ViewModel<\/strong>.<\/p>\n<p>\u0423 UI \u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0435 \u0438\u043b\u0438 \u043b\u043e\u0433\u0438\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (Business Models \u0438 UseCases), \u043e\u043d \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0443 ViewModel. \u042d\u0442\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c \u0431\u0438\u0437\u043d\u0435\u0441 \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432\u043e View (UI). \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043c\u0430\u043f\u043f\u0438\u043c Business Models \u043a ViewModel \u0432\u043d\u0443\u0442\u0440\u0438 ViewModel \u0438 \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u043c \u0438\u0445 \u0432 View.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438\u0437 View \u0432\u043e ViewModel, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c \u0438\u0441\u043a\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u043c\u044b:<\/p>\n<pre><code class=\"swift\">import UIKit final class MoviesListViewController: UIViewController, StoryboardInstantiable, UISearchBarDelegate { private var viewModel: MoviesListViewModel!  final class func create(with viewModel: MoviesListViewModel) -&amp;gt; MoviesListViewController {     let vc = MoviesListViewController.instantiateViewController()     vc.viewModel = viewModel     return vc }  override func viewDidLoad() {     super.viewDidLoad()      bind(to: viewModel) }  private func bind(to viewModel: MoviesListViewModel) {     viewModel.items.observe(on: self) { [weak self] items in         self?.moviesTableViewController?.items = items     }     viewModel.error.observe(on: self) { [weak self] error in         self?.showError(error)     } }  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {     guard let searchText = searchBar.text, !searchText.isEmpty else { return }     viewModel.didSearch(query: searchText) }  } <\/code><\/pre>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u041c\u044b \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c \u0437\u0430 \u0430\u0439\u0442\u0435\u043c\u0430\u043c\u0438 \u0438\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c View, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f. \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0437\u0434\u0435\u0441\u044c\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/Utils\/Observable.swift\" rel=\"noopener noreferrer nofollow\"><em>Observable<\/em><\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u0431\u0437\u043e\u0440\u0435 MVVM \u043d\u0438\u0436\u0435.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e\u00a0<em>showMovieDetails<\/em>(movie:)\u00a0\u0432\u00a0<strong>Actions \u043d\u0430\u0448\u0435\u0439<\/strong>MoviesList<strong>ViewModel\u00a0\u0432\u043d\u0443\u0442\u0440\u0438<\/strong>\u00a0<a href=\"https:\/\/github.com\/kudoleh\/iOS-Clean-Architecture-MVVM\/blob\/master\/ExampleMVVM\/Presentation\/MoviesScene\/Flows\/MoviesSearchFlowCoordinator.swift\" rel=\"noopener noreferrer nofollow\">MoviesSearch<strong>FlowCoordinator<\/strong><\/a>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u043e\u0432\u0430\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439 \u0438\u0437 flow coordinator:<\/p>\n<pre><code class=\"swift\">protocol MoviesSearchFlowCoordinatorDependencies  { func makeMoviesListViewController() -> UIViewController func<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-374106","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/374106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=374106"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/374106\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=374106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=374106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=374106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}