{"id":311840,"date":"2020-10-21T21:00:30","date_gmt":"2020-10-21T21:00:30","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=311840"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=311840","title":{"rendered":"Single source of truth (SSOT) on MVVM with RxSwift &amp; CoreData"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\u0427\u0430\u0441\u0442\u043e \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b:<\/p>\n<ol>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441<\/li>\n<li>\u0417\u0430\u0431\u0438\u043d\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435 \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 view<\/li>\n<li>\u0415\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e, \u0442\u043e \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u044d\u0442\u0438\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439, \u0442\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435<\/li>\n<li>\u0421\u043e\u0431\u043b\u044e\u0441\u0442\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f <b>SSOT<\/b> \u0434\u043b\u044f \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/li>\n<li>\u0412\u0441\u0451 \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c<\/li>\n<\/ol>\n<p>  \u0420\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Model-View-ViewModel\">MVVM<\/a> \u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 <a href=\"https:\/\/github.com\/ReactiveX\/RxSwift\">RxSwift<\/a>, <a href=\"https:\/\/developer.apple.com\/documentation\/coredata\">CoreData<\/a>.<\/p>\n<p>  \u041e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u043d\u0438\u0436\u0435 \u043f\u043e\u0434\u0445\u043e\u0434 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043d\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a <a href=\"https:\/\/github.com\/ReactiveX\/RxSwift\">RxSwift<\/a> \u0438 <a href=\"https:\/\/developer.apple.com\/documentation\/coredata\">CoreData<\/a>. \u0418 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0440\u0443\u0433\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432.<br \/>  <a name=\"habracut\"><\/a><br \/>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u044f \u0432\u043e\u0437\u044c\u043c\u0443 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0434\u0430\u0432\u0446\u0430. \u0412 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0434\u0432\u0430 \u0430\u0443\u0442\u043b\u0435\u0442\u0430 UILabel \u0434\u043b\u044f \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0438 \u0430\u0434\u0440\u0435\u0441\u0430 \u0438 \u043e\u0434\u043d\u0430 UIButton \u0434\u043b\u044f \u0437\u0432\u043e\u043d\u043a\u0430 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443. <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Contacts\/ContactsViewController.swift\">ContactsViewController<\/a>.<\/p>\n<p>  \u041e\u0431\u044a\u044f\u0441\u043d\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043e\u0442 model \u043a view.<\/p>\n<h3>Model<\/h3>\n<p>  \u0424\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u0430\u0432\u0442\u043e\u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 SellerContacts+CoreDataProperties \u0438\u0437 DerivedSources<br \/>  \u0441 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438:<\/p>\n<pre><code class=\"swift\">extension SellerContacts {      @nonobjc public class func fetchRequest() -&gt; NSFetchRequest&lt;SellerContacts&gt; {         return NSFetchRequest&lt;SellerContacts&gt;(entityName: &quot;SellerContacts&quot;)     }      @NSManaged public var address: String?     @NSManaged public var order: Int16     @NSManaged public var phone: String?  } <\/code><\/pre>\n<p>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Data\/Repository.swift\"><b>Repository<\/b><\/a>.<\/p>\n<p>  \u041c\u0435\u0442\u043e\u0434 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0434\u0430\u0432\u0446\u0430:<\/p>\n<pre><code class=\"swift\">func sellerContacts() -&gt; Observable&lt;Event&lt;[SellerContacts]&gt;&gt; {         \/\/ 1         Observable.merge([             \/\/ 2             context.rx.entities(fetchRequest: SellerContacts.fetchRequestWithSort()).materialize(),             \/\/ 3             updater.sync()         ])     } <\/code><\/pre>\n<p>  \u041a\u0430\u043a \u0440\u0430\u0437 \u0432 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f <b>SSOT<\/b>. \u0417\u0430\u043f\u0440\u043e\u0441 \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043a <b>CoreData<\/b>, \u0438 <b>CoreData<\/b> \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e. \u0412\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u0422\u041e\u041b\u042c\u041a\u041e \u0438\u0437 \u0411\u0414, \u0430 <b>updater.sync()<\/b> \u043c\u043e\u0436\u0435\u0442 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e Event \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u043d\u043e \u041d\u0415 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438.<\/p>\n<ol>\n<li>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 merge \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0435\u0451 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.<\/li>\n<li>\u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a \u0411\u0414 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <a href=\"https:\/\/github.com\/RxSwiftCommunity\/RxCoreData\">RxCoreData<\/a><\/li>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0411\u0414<\/li>\n<\/ol>\n<p>  \u0422.\u043a. \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <b>Observable&lt;Event&lt;&#8230;&gt;&gt;<\/b>. \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b subscriber \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b Error, \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f remote data, \u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u043b \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 <b>CoreData<\/b>. \u041e\u0431 \u044d\u0442\u043e\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.<\/p>\n<p>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Data\/DatabaseUpdater.swift\"><b>DatabaseUpdater<\/b><\/a><br \/>  \u0412 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442\u0441\u044f \u0438\u0437 <b>Firebase Remote Config<\/b>. <b>CoreData<\/b> \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 <b>fetchAndActivate()<\/b> \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0441\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c <b>.successFetchedFromRemote<\/b>.<\/p>\n<p>  \u041d\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0434\u0440\u0443\u0433\u0438\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<br \/>  \u041c\u0435\u0442\u043e\u0434 sync() \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0411\u0414:<\/p>\n<pre><code class=\"swift\">func sync&lt;T&gt;() -&gt; Observable&lt;Event&lt;T&gt;&gt; {         \/\/ 1         \/\/ Check can fetch         if fetchLimiter.fetchInProcess {             return Observable.empty()         }         \/\/ 2         \/\/ Block fetch for other requests         fetchLimiter.fetchInProcess = true         \/\/ 3         \/\/ Fetch & activate remote config         return remoteConfig.rx.fetchAndActivate().flatMap { [weak self] status, error -&gt; Observable&lt;Event&lt;T&gt;&gt; in             \/\/ 4             \/\/ Default result             var result = Observable&lt;Event&lt;T&gt;&gt;.empty()             \/\/ Update database only when config wethed from remote             switch status {             \/\/ 5             case .error:                 let error = error ?? AppError.unknown                 print(&quot;Remote config fetch error: \\(error.localizedDescription)&quot;)                 \/\/ Set error to result                 result = Observable.just(Event.error(error))             \/\/ 6             case .successFetchedFromRemote:                 print(&quot;Remote config fetched data from remote&quot;)                 \/\/ Update database from remote config                 try self?.update()             case .successUsingPreFetchedData:                 print(&quot;Remote config using prefetched data&quot;)             @unknown default:                 print(&quot;Remote config unknown status&quot;)             }             \/\/ 7             \/\/ Unblock fetch for other requests             self?.fetchLimiter.fetchInProcess = false             return result         }     } <\/code><\/pre>\n<p>  <\/p>\n<ol>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043f\u0443\u0441\u0442\u0443\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0436\u0435 \u0438\u0434\u0435\u0442. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u0440\u0443\u0433\u043e\u0439 \u043c\u0435\u0442\u043e\u0434 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u0437\u0432\u0430\u043b sync(). <b>fetchLimiter<\/b> \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043f\u043e\u0442\u043e\u043a\u043e\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c. \u0410 \u0438\u043c\u0435\u043d\u043d\u043e, \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432 \u043f\u043e\u043b\u0435 <b>fetchInProcess<\/b> \u043d\u0443\u0436\u043d\u043e \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438. <\/li>\n<li>\u0411\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430<\/li>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445<\/li>\n<li>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0441 \u043f\u0443\u0441\u0442\u043e\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043b\u0441\u044f \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 \u0442\u043e \u043f\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0443 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0441 \u043e\u0434\u043d\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u043c <b>Event<\/b> \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439<\/li>\n<li>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0411\u0414<\/li>\n<li>\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0411\u0414 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/li>\n<\/ol>\n<p>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Contacts\/ContactsViewModel.swift\"><b>ViewModel<\/b><\/a><br \/>  \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u043e <b>ViewModel<\/b> \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <b>sellerContacts()<\/b> \u0438\u0437 <b>Repository<\/b> \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<pre><code class=\"swift\">func contacts() -&gt; Observable&lt;Event&lt;[SellerContacts]&gt;&gt; {         repository.sellerContacts()     } <\/code><\/pre>\n<p>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Contacts\/ContactsViewController.swift\"><b>ViewController<\/b><\/a><br \/>  \u0412 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0431\u0438\u043d\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0432 \u043f\u043e\u043b\u044f. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 <b>viewDidLoad()<\/b> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <b>bindContacts()<\/b>:<\/p>\n<pre><code class=\"swift\">private func bindContacts() {         \/\/ 1         viewModel?.contacts()             .subscribeOn(SerialDispatchQueueScheduler.init(qos: .userInteractive))             .observeOn(MainScheduler.instance)              \/\/ 2             .flatMapError { [weak self] in                 self?.rx.showMessage($0.localizedDescription) ?? Observable.empty()             }              \/\/ 3             .compactMap { $0.first }              \/\/ 4             .subscribe(onNext: { [weak self] in                 self?.phone.text = $0.phone                 self?.address.text = $0.address             }).disposed(by: disposeBag)     } <\/code><\/pre>\n<p>  <\/p>\n<ol>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435, \u0430 \u0441 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c<\/li>\n<li>\u0415\u0441\u043b\u0438 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439 <b>Event<\/b> \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0442\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e\u0431 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0435 <b>flatMapError<\/b> \u0438 <b>showMessage<\/b> \u043d\u0438\u0436\u0435<\/li>\n<li>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 <b>compactMap<\/b> \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u0438\u0437 \u043c\u0430\u0441\u0441\u0438\u0432\u0430<\/li>\n<li>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0430\u0443\u0442\u043b\u0435\u0442\u044b<\/li>\n<\/ol>\n<p>  <b>\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Util\/ObservableType%2BExtensions.swift\">.flatMapError()<\/a><\/b><br \/>  \u0414\u043b\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438\u0437 <b>Event<\/b> \u0432 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0432 \u043d\u0451\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439\u0441\u044f \u0438\u043b\u0438 \u043f\u043e\u043a\u0430\u0437\u0430 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440:<\/p>\n<pre><code class=\"swift\">func flatMapError&lt;T&gt;(_ handler: ((_ error: Error) -&gt; Observable&lt;T&gt;)? = nil) -&gt; Observable&lt;Element.Element&gt; {         \/\/ 1         flatMap { element -&gt; Observable&lt;Element.Element&gt; in             switch element.event {             \/\/ 2             case .error(let error):                 return handler?(error).flatMap { _ in Observable&lt;Element.Element&gt;.empty() } ?? Observable.empty()             \/\/ 3             case .next(let element):                 return Observable.just(element)             \/\/ 4             default:                 return Observable.empty()             }         }     } <\/code><\/pre>\n<p>   <\/p>\n<ol>\n<li>\u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u0437 <b>Event.Element<\/b> \u0432 <b>Element<\/b><\/li>\n<li>\u0415\u0441\u043b\u0438 <b>Event<\/b> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0448\u0438\u0431\u043a\u0443, \u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c handler \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0432 \u043f\u0443\u0441\u0442\u0443\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/li>\n<li>\u0415\u0441\u043b\u0438 <b>Event<\/b> \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0441 \u043e\u0434\u043d\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u043c, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u043c \u044d\u0442\u043e\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/li>\n<li>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/li>\n<\/ol>\n<p>  \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043d\u0435 \u043f\u043e\u0441\u044b\u043b\u0430\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u0443 Error Event. \u0418 \u043d\u0430\u0431\u043b\u044e\u0434\u0435\u043d\u0438\u0435 \u0437\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u0432 \u0411\u0414 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c.<\/p>\n<p>  <b>\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhin\/Util\/UIViewController%2BRx.swift\">.showMessage()<\/a><\/b><br \/>  \u0414\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440:<\/p>\n<pre><code class=\"swift\">public func showMessage(_ text: String, withEvent: Bool = false) -&gt; Observable&lt;Void&gt; {         \/\/ 1         let _alert = alert(title: nil,               message: text,               actions: [AlertAction(title: &quot;OK&quot;, style: .default)]         \/\/ 2         ).map { _ in () }         \/\/ 3         return withEvent ? _alert : _alert.flatMap { Observable.empty() }     } <\/code><\/pre>\n<p>  <\/p>\n<ol>\n<li>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/github.com\/RxSwiftCommunity\/RxAlert\">RxAlert<\/a> \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u043e\u043a\u043d\u043e \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c \u0438 \u043e\u0434\u043d\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u043e\u0439<\/li>\n<li>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442\u0441\u044f \u0432 <b>Void<\/b><\/li>\n<li>\u0415\u0441\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u043a\u0430\u0437\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442. \u0418\u043d\u0430\u0447\u0435 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0432 \u043f\u0443\u0441\u0442\u0443\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0430 \u0437\u0430\u0442\u0435\u043c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c<\/li>\n<\/ol>\n<p>  \u0422.\u043a. <b>.showMessage()<\/b> \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043f\u043e\u043a\u0430\u0437\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445, \u0442\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0438\u0442\u043e\u0433\u0435 \u2014 \u043f\u0443\u0441\u0442\u0430\u044f \u0438\u043b\u0438 \u0441 \u0441\u043e\u0431\u044b\u0442\u0438\u0435\u043c.<\/p>\n<h3>\u0422\u0435\u0441\u0442\u044b<\/h3>\n<p>  \u0412\u0441\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0435 \u0432\u044b\u0448\u0435 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u0440\u0443\u0434\u043d\u043e. \u041d\u0430\u0447\u043d\u0435\u043c \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443 \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <b><a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Data\/RepositoryTests.swift\">RepositoryTests<\/a><\/b><br \/>  \u0414\u043b\u044f \u0442\u0435\u0441\u0442\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Mock\/DatabaseUpdaterMock.swift\">DatabaseUpdaterMock<\/a>. \u0422\u0430\u043c \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u043b\u0441\u044f \u043b\u0438 \u043c\u0435\u0442\u043e\u0434 sync() \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0435\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"swift\">func testSellerContacts() throws {         \/\/ 1         \/\/ Success         \/\/ Check sequence contains only one element         XCTAssertThrowsError(try repository.sellerContacts().take(2).toBlocking(timeout: 1).toArray())         updater.isSync = false         \/\/ Check that element         var result = try repository.sellerContacts().toBlocking().first()?.element         XCTAssertTrue(updater.isSync)         XCTAssertEqual(result?.count, sellerContacts.count)          \/\/ 2         \/\/ Sync error         updater.isSync = false         updater.error = AppError.unknown         let resultArray = try repository.sellerContacts().take(2).toBlocking().toArray()         XCTAssertTrue(resultArray.contains { $0.error?.localizedDescription == AppError.unknown.localizedDescription })         XCTAssertTrue(updater.isSync)         result = resultArray.first { $0.error == nil }?.element         XCTAssertEqual(result?.count, sellerContacts.count)     } <\/code><\/pre>\n<p>  <\/p>\n<ol>\n<li>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <b>sync()<\/b><\/li>\n<li>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0432\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430. \u041e\u0434\u0438\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 <b>Event<\/b> \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439, \u0434\u0440\u0443\u0433\u043e\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438\u0437 \u0411\u0414, \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <b>sync()<\/b><\/li>\n<\/ol>\n<p>  <b><a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Data\/DatabaseUpdaterTests.swift\">DatabaseUpdaterTests<\/a><\/b><\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">testSync()<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"swift\">func testSync() throws {         let remoteConfig = RemoteConfigMock()         let fetchLimiter = FetchLimiter(serialQueue: DispatchQueue(label: &quot;test&quot;))         let databaseUpdater = DatabaseUpdaterImpl(remoteConfig: remoteConfig, decoder: JSONDecoderMock(), context: context, fetchLimiter: fetchLimiter)         \/\/ 1         \/\/ Not update. Fetch in process         fetchLimiter.fetchInProcess = true         XCTAssertFalse(remoteConfig.isFetchAndActivate)         XCTAssertFalse(remoteConfig.isSubscript)                  expectation(forNotification: .NSManagedObjectContextDidSave, object: context)             .isInverted = true              var sync: Observable&lt;Event&lt;Void&gt;&gt; = databaseUpdater.sync()         XCTAssertNil(try sync.toBlocking().first())         XCTAssertFalse(remoteConfig.isFetchAndActivate)         XCTAssertFalse(remoteConfig.isSubscript)         XCTAssertTrue(fetchLimiter.fetchInProcess)                  waitForExpectations(timeout: 1)         \/\/ 2         \/\/ Not update. successUsingPreFetchedData         fetchLimiter.fetchInProcess = false                  expectation(forNotification: .NSManagedObjectContextDidSave, object: context)             .isInverted = true                  sync = databaseUpdater.sync()         var result: Event&lt;Void&gt;?         sync.subscribe(onNext: { result = $0 }).disposed(by: disposeBag)         XCTAssertTrue(fetchLimiter.fetchInProcess)         remoteConfig.completionHandler?(RemoteConfigFetchAndActivateStatus.successUsingPreFetchedData, nil)                  waitForExpectations(timeout: 1)         XCTAssertNil(result)         XCTAssertTrue(remoteConfig.isFetchAndActivate)         XCTAssertFalse(remoteConfig.isSubscript)         XCTAssertFalse(fetchLimiter.fetchInProcess)         \/\/ 3         \/\/ Not update. Error         fetchLimiter.fetchInProcess = false         remoteConfig.isFetchAndActivate = false                  expectation(forNotification: .NSManagedObjectContextDidSave, object: context)             .isInverted = true         sync = databaseUpdater.sync()         sync.subscribe(onNext: { result = $0 }).disposed(by: disposeBag)         XCTAssertTrue(fetchLimiter.fetchInProcess)         remoteConfig.completionHandler?(RemoteConfigFetchAndActivateStatus.error, AppError.unknown)                  waitForExpectations(timeout: 1)                  XCTAssertEqual(result?.error?.localizedDescription, AppError.unknown.localizedDescription)         XCTAssertTrue(remoteConfig.isFetchAndActivate)         XCTAssertFalse(remoteConfig.isSubscript)         XCTAssertFalse(fetchLimiter.fetchInProcess)         \/\/ 4         \/\/ Update         fetchLimiter.fetchInProcess = false         remoteConfig.isFetchAndActivate = false         result = nil                  expectation(forNotification: .NSManagedObjectContextDidSave, object: context)                  sync = databaseUpdater.sync()         sync.subscribe(onNext: { result = $0 }).disposed(by: disposeBag)         XCTAssertTrue(fetchLimiter.fetchInProcess)         remoteConfig.completionHandler?(RemoteConfigFetchAndActivateStatus.successFetchedFromRemote, nil)                  waitForExpectations(timeout: 1)                  XCTAssertNil(result)         XCTAssertTrue(remoteConfig.isFetchAndActivate)         XCTAssertTrue(remoteConfig.isSubscript)         XCTAssertFalse(fetchLimiter.fetchInProcess)     } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<ol>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0435\u0441\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435<\/li>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b<\/li>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f <b>Event<\/b> \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439<\/li>\n<li>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u0430\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0438\u0441\u044c<\/li>\n<\/ol>\n<p>  <b><a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Contacts\/ContactsViewModelTests.swift\">ViewModelTests<\/a><\/b><\/p>\n<p>  <b><a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Contacts\/ContactsViewControllerTests.swift\">ViewControllerTests<\/a><\/b><\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">testBindContacts()<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"swift\">func testBindContacts() {         \/\/ 1         \/\/ Error. Show message         XCTAssertNotEqual(controller.phone.text, contacts.phone)         XCTAssertNotEqual(controller.address.text, contacts.address)         viewModel.contactsResult.accept(Event.error(AppError.unknown))                  expectation(description: &quot;wait 1 second&quot;).isInverted = true         waitForExpectations(timeout: 1)         \/\/ 2         XCTAssertNotNil(controller.presentedViewController)         let alertController = controller.presentedViewController as! UIAlertController         XCTAssertEqual(alertController.actions.count, 1)         XCTAssertEqual(alertController.actions.first?.style, .default)         XCTAssertEqual(alertController.actions.first?.title, &quot;OK&quot;)         XCTAssertNotEqual(controller.phone.text, contacts.phone)         XCTAssertNotEqual(controller.address.text, contacts.address)         \/\/ 3         \/\/ Trigger action OK         let action = alertController.actions.first!         typealias AlertHandler = @convention(block) (UIAlertAction) -&gt; Void         let block = action.value(forKey: &quot;handler&quot;)         let blockPtr = UnsafeRawPointer(Unmanaged&lt;AnyObject&gt;.passUnretained(block as AnyObject).toOpaque())         let handler = unsafeBitCast(blockPtr, to: AlertHandler.self)         handler(action)                  expectation(description: &quot;wait 1 second&quot;).isInverted = true         waitForExpectations(timeout: 1)         \/\/ 4         XCTAssertNil(controller.presentedViewController)         XCTAssertNotEqual(controller.phone.text, contacts.phone)         XCTAssertNotEqual(controller.address.text, contacts.address)         \/\/ 5         \/\/ Empty array of contats         viewModel.contactsResult.accept(Event.next([]))                  expectation(description: &quot;wait 1 second&quot;).isInverted = true         waitForExpectations(timeout: 1)                  XCTAssertNil(controller.presentedViewController)         XCTAssertNotEqual(controller.phone.text, contacts.phone)         XCTAssertNotEqual(controller.address.text, contacts.address)         \/\/ 6         \/\/ Success         viewModel.contactsResult.accept(Event.next([contacts]))                  expectation(description: &quot;wait 1 second&quot;).isInverted = true         waitForExpectations(timeout: 1)                  XCTAssertNil(controller.presentedViewController)         XCTAssertEqual(controller.phone.text, contacts.phone)         XCTAssertEqual(controller.address.text, contacts.address)     } <\/code><\/pre>\n<p>  <\/div>\n<\/p><\/div>\n<p>  <\/p>\n<ol>\n<li>\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435<\/li>\n<li>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432 <b>controller.presentedViewController<\/b> \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435<\/li>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c handler \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u041e\u043a \u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043a\u043d\u043e \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c \u0441\u043a\u0440\u044b\u043b\u043e\u0441\u044c<\/li>\n<li>\u0414\u043b\u044f \u043f\u0443\u0441\u0442\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u0438 \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u043e\u043b\u044f<\/li>\n<li>\u0414\u043b\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u043f\u043e\u043b\u044f<\/li>\n<\/ol>\n<p>  <\/p>\n<h3>\u0422\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432<\/h3>\n<p>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Util\/ObservableType%2BExtensionsTests.swift\">.flatMapError()<\/a><br \/>  <a href=\"https:\/\/github.com\/alesat1215\/ProductsFromErokhin-iOS\/blob\/master\/ProductsFromErokhinTests\/Util\/UIViewController%2BRxTests.swift\">.showMessage()<\/a><\/p>\n<p>  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0431\u0435\u0437 \u043f\u043e\u0442\u0435\u0440\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441\u043b\u0435\u0434\u0443\u044f \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443 <b>SSOT<\/b>.<\/div>\n<p> \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\/post\/524508\/\"> https:\/\/habr.com\/ru\/post\/524508\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\u0427\u0430\u0441\u0442\u043e \u0432 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b:<\/p>\n<ol>\n<li>\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441<\/li>\n<li>\u0417\u0430\u0431\u0438\u043d\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435 \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 view<\/li>\n<li>\u0415\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e, \u0442\u043e \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435<\/li>\n<li>\u0415\u0441\u043b\u0438 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u044d\u0442\u0438\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439, \u0442\u043e \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435<\/li>\n<li>\u0421\u043e\u0431\u043b\u044e\u0441\u0442\u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f <b>SSOT<\/b> \u0434\u043b\u044f \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/li>\n<li>\u0412\u0441\u0451 \u044d\u0442\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c<\/li>\n<\/ol>\n<p>  \u0420\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Model-View-ViewModel\">MVVM<\/a> \u0438 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0438 <a href=\"https:\/\/github.com\/ReactiveX\/RxSwift\">RxSwift<\/a>, <a href=\"https:\/\/developer.apple.com\/documentation\/coredata\">CoreData<\/a>.<\/p>\n<p>  \u041e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u043d\u0438\u0436\u0435 \u043f\u043e\u0434\u0445\u043e\u0434 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u044b \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043d\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a <a href=\"https:\/\/github.com\/ReactiveX\/RxSwift\">RxSwift<\/a> \u0438 <a href=\"https:\/\/developer.apple.com\/documentation\/coredata\">CoreData<\/a>. \u0418 \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0440\u0443\u0433\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432.  <\/p>\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-311840","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/311840","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=311840"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/311840\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=311840"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=311840"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=311840"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}