{"id":271312,"date":"2015-12-28T20:01:02","date_gmt":"2015-12-28T17:01:02","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=271312"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=271312","title":{"rendered":"Objective-C integration testing \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0447\u0430\u0441\u0442\u0438 RSS \u0447\u0438\u0442\u0430\u043b\u043a\u0438"},"content":{"rendered":"<p>       \u0412 \u043f\u0440\u043e\u0448\u043b\u044b\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 \u044f \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b unit-\u0442\u0435\u0441\u0442\u044b, \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0440\u0435\u0447\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445. <br \/>  \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0435 \u0432\u044b\u0448\u0435\u043b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043c, \u043d\u043e \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043b \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b, \u044f \u0440\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0447\u0430\u0441\u0442\u0438 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/RSS\">RSS<\/a> Reader&#8217;\u0430.<br \/>  \u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u043b\u043a\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0440\u0430\u0431\u043e\u0442\u044b.<br \/>  \u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 CoreData.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\"  src=\"https:\/\/habrastorage.org\/files\/641\/d82\/f57\/641d82f5758146f9b3ba355001ffe38e.jpg\"\/><\/div>\n<h3>\u041f\u0430\u0440\u0430 \u0441\u043b\u043e\u0432 \u0442\u0435\u043e\u0440\u0438\u0438:<\/h3>\n<p>  Unit tests \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0432 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0438.<br \/>  Integration tests \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0447\u0430\u0441\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0432\u043c\u0435\u0441\u0442\u0435.<br \/>  <a name=\"habracut\"><\/a><br \/>  \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b \u0441 XCT, \u0442\u043e <a href=\"http:\/\/habrahabr.ru\/post\/258953\/\">\u0437\u0434\u0435\u0441\u044c<\/a> \u044f \u043f\u0440\u043e \u044d\u0442\u043e \u043f\u0438\u0441\u0430\u043b.<\/p>\n<p>  \u0411\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"http:\/\/habrahabr.ru\/company\/redmadrobot\/blog\/246551\/\">SOA (Service Oriented Architecture)<\/a>, \u0433\u0434\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f. \u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u2014 \u044d\u0442\u043e \u043f\u0435\u0440\u0432\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u044b\u0435 \u0446\u0435\u043b\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. <\/p>\n<p>  \u0422\u0430\u043a \u0436\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432 main.m, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0442\u0430\u0440\u0433\u0435\u0442\u0430.  <\/p>\n<pre><code class=\"objectivec\">int main(int argc, char * argv[]) {     @autoreleasepool {         Class appDelegateClass = (NSClassFromString(@&quot;XCTestCase&quot;) ? [RSTestingAppDelegate class] : [RSAppDelegate class]);         return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass));     } } <\/code><\/pre>\n<p>  \u0418 \u0441\u043e\u0437\u0434\u0430\u043d \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 RSTestCase \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u043a\u043b\u044e\u0447\u0438\u0432 \u0442\u0443\u0434\u0430 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043a\u043e\u0434.   <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041a\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c?<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\"> typedef void (^RSTestCaseAsync)(XCTestExpectation *expectation); ... ... ... - (void)asyncTest:(RSTestCaseAsync)async {     [self asyncTest:async timeout:5.0]; }  - (void)asyncTest:(RSTestCaseAsync)async timeout:(NSTimeInterval)timeout {     XCTestExpectation *expectation = [self expectationWithDescription:@&quot;block not call&quot;];     XCTAssertNotNil(async, @&quot;don't send async block!&quot;);     async(expectation);     [self waitForExpectationsWithTimeout:timeout handler:nil]; } <\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041d\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432 setUp \u043c\u0435\u0442\u043e\u0434\u0435<\/b><\/p>\n<div class=\"spoiler_text\">\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043c\u044b\u0441\u043b \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u2014 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 setUp. \u0412\u0435\u0434\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0434 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u043b\u044e\u0431\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0430 \u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0437\u0434\u0435\u0441\u044c \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u043f\u0440\u043e\u0432\u0430\u043b \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0430. \u041e\u0434\u043d\u0430\u043a\u043e \u0441\u0430\u043c \u043c\u0435\u0442\u043e\u0434 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u0435\u0441\u0442\u043e\u043c \u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0432 \u043d\u0435\u043c \u043d\u0435 \u0437\u0430\u043f\u0438\u0448\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443 \u0432 \u0442\u0430\u0431\u043b\u0438\u0446\u0443 \u0442\u0435\u0441\u0442\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435 \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0442\u0435\u0441\u0442 testInitAfterSetUp. \u0414\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d (\u0432 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435) \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0434\u043e\u0447\u0435\u0440\u043d\u0435\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u043f\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 setUp. \u041f\u0440\u043e\u0432\u0430\u043b \u044d\u0442\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0430 \u0441\u0438\u0433\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043e \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 setUp.  <\/div>\n<\/div>\n<p>  \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u044f \u0445\u0440\u0430\u043d\u044e \u0432 \u0433\u0440\u0443\u043f\u043f\u0435 IT, \u0430 \u043a\u043b\u0430\u0441\u0441\u044b \u0441 \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435\u043c IT.<br \/>  \u041c\u043e\u0434\u0443\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u044f \u0445\u0440\u0430\u043d\u044e \u0432 \u0433\u0440\u0443\u043f\u043f\u0435 Unit, \u0430 \u043a\u043b\u0430\u0441\u0441\u044b \u0441 \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435\u043c Test.<\/p>\n<h2>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u043e\u0437\u044c\u043c\u0435\u043c\u0441\u044f \u0437\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443<\/h2>\n<p>  \u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 <a href=\"https:\/\/cocoapods.org\">CocoaPods<\/a>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Podfile<\/b><\/p>\n<div class=\"spoiler_text\">platform :ios, &#8216;8.0&#8217;<br \/>  use_frameworks!<\/p>\n<p>  pod &#8216;AFNetworking&#8217;, &#8216;~&gt; 2.5.4&#8217;<br \/>  pod &#8216;XMLDictionary&#8217;, &#8216;~&gt; 1.4&#8217;<br \/>  pod &#8216;ReactiveCocoa&#8217;, &#8216;~&gt; 2.5&#8217;<br \/>  pod &#8216;BlocksKit&#8217;, &#8216;~&gt; 2.2.5&#8217;<br \/>  pod &#8216;MagicalRecord&#8217;, &#8216;~&gt; 2.3.0&#8217;<\/p>\n<p>  pod &#8216;MWFeedParser\/NSDate+InternetDateTime&#8217;<\/p>\n<p>  target &#8216;RSReaderTests&#8217; do<br \/>   pod &#8216;OHHTTPStubs&#8217;, &#8216;~&gt; 4.6.0&#8217;<br \/>   pod &#8216;OCMock&#8217;, &#8216;~&gt; 3.2&#8217;<br \/>  end  <\/div>\n<\/div>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b RSFeedServiceIT.m \u0438 \u043a\u043b\u0430\u0441\u0441 RSFeedServiceIT \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043b\u0435\u043d\u0442\u044b \u043d\u043e\u0432\u043e\u0441\u0442\u0435\u0439.   <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSFeedServiceIT.m<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &quot;RSTestCase.h&quot;   @interface RSFeedServiceIT : RSTestCaseIT @end   @implementation RSFeedServiceIT  - (void)setUp {     [super setUp];     \/\/ Put setup code here. This method is called before the invocation of each test method in the class. }  - (void)tearDown {     \/\/ Put teardown code here. This method is called after the invocation of each test method in the class.     [super tearDown]; }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u041d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0441\u043b\u0443\u0447\u0430\u0438<br \/>  1) \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c RSS<br \/>  2) \u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f<br \/>  3) \u0421\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d<\/p>\n<p>  \u0418 \u0442\u043e\u0433\u043e 3 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430.   <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0410 \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u0432\u0441\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0438\u0434\u0443\u0442 \u043d\u0430 \u043d\u0435\u0433\u043e?<\/b><\/p>\n<div class=\"spoiler_text\">\u0415\u0441\u043b\u0438 \u043f\u0438\u0448\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c 1 \u0442\u0435\u0441\u0442 \u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 RSS \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0442\u044c \u0438\u0437 \u0441\u043f\u0438\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0440\u0443\u043a\u0430\u043c\u0438 \u2014 \u0432\u0441\u0435 \u043b\u0438 \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u0441\u043b\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u0444\u0438\u0442\u0447\u0438 \u0443 \u0432\u0430\u0441 \u0438\u043b\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435?). \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u044c.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/files\/cd0\/44c\/294\/cd044c294a09492a80b458fa2578e20e.png\"\/>  <\/div>\n<\/div>\n<p>  \u0414\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u043d\u0443\u0436\u043d\u044b 2 \u043f\u043e\u043b\u044f. \u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438 url. \u0411\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u0442\u0435\u0441\u0442\u043e\u043c \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u0442\u043e.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSFeedServiceIT.m<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">@interface RSFeedServiceIT : RSTestCaseIT  @property (strong, nonatomic) RSFeedService *service; @property (strong, nonatomic) NSString *url;  @end  ... ... ...  - (void)setUp {     [super setUp];     \/\/ Put setup code here. This method is called before the invocation of each test method in the class.          self.service = [RSFeedService sharedInstance];     self.url = @&quot;http:\/\/images.apple.com\/main\/rss\/hotnews\/hotnews.rss&quot;; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u0422\u0435\u0441\u04421: \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c RSS<\/h4>\n<p>  OHHTTPStubs \u2014 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u0434\u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441. \u0413\u043e\u0432\u043e\u0440\u0438\u043c \u0447\u0442\u043e \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0434\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 rss_news.xml, Content-Type \u0431\u0443\u0434\u0435\u0442 application\/xml, \u0430 \u043a\u043e\u0434 \u043e\u0442\u0432\u0435\u0442\u0430 200 (OK).<br \/>  \u041f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0430 \u0432 \u0442\u0435\u0441\u0442\u0435, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0448\u043b\u0438, \u0430 \u0441\u0435\u0440\u0432\u0438\u0441 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043e\u0442\u0432\u0435\u0442 \u0438 \u0432\u044b\u0434\u0430\u043b 20 \u043d\u043e\u0432\u043e\u0441\u0442\u0435\u0439.<br \/>  \u0412\u044b\u0437\u043e\u0432 \u0431\u043b\u043e\u043a\u0430 failure \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043e\u0448\u0438\u0431\u043a\u0438 \u0442\u0435\u0441\u0442\u0430.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">testFeedFromURL<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#pragma mark test - (void)testFeedFromURL {     [self stubXmlFeed];          [self asyncTest:^(XCTestExpectation *expectation) {         @weakify(self);         [self.service feedFromStringUrl:self.url success:^(NSArray *itemNews) {             @strongify(self);             [expectation fulfill];             XCTAssertNotNil(itemNews);             XCTAssertEqual([itemNews count], 20);         } failure:^(NSError *error) {             @strongify(self);             [expectation fulfill];             XCTFail(@&quot;%@&quot;, error);         }];     }]; }  - (void)stubXmlFeed {     [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {         return YES;     } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {         NSString *xmlFeed = OHPathForFile(@&quot;rss_news.xml&quot;, [self class]);         NSDictionary *headers = @{                                   @&quot;Content-Type&quot; : @&quot;application\/xml&quot;                                   };         return [OHHTTPStubsResponse responseWithFileAtPath:xmlFeed statusCode:200 headers:headers];     }]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043b\u0430\u0441\u0441 RSTestCaseIT (\u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a \u043e\u0442 RSTestCase) \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u0431\u0440\u043e\u0441\u0430 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u0442\u0430\u0431\u0430 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0430.  <\/p>\n<pre><code class=\"objectivec\">- (void)tearDown {     [OHHTTPStubs removeAllStubs];     \/\/ Put teardown code here. This method is called after the invocation of each test method in the class.          [super tearDown]; } <\/code><\/pre>\n<p>  \u0422\u0430\u043a \u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 RSTestCaseIT \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0448\u0438\u0431\u043a\u0438 \u043d\u0430 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">stubHttpErrorDomain:code:userInfo<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">- (void)stubHttpErrorDomain:(NSString *)domain code:(NSInteger)code userInfo:(NSDictionary *)userInfo {     [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {         return YES;     } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {         NSError *error = [NSError errorWithDomain:domain code:code userInfo:userInfo];         return [OHHTTPStubsResponse responseWithError:error];     }]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u0422\u0435\u0441\u04422: \u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f<\/h4>\n<p>  \u0421\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a failure, \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443 \u0441 \u043a\u043e\u0434\u043e\u043c NSURLErrorNotConnectedToInternet \u0438 \u0434\u043e\u043c\u0435\u043d\u043e\u043c NSURLErrorDomain. \u0412\u044b\u0437\u043e\u0432 \u0431\u043b\u043e\u043a\u0430 success \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043e\u0448\u0438\u0431\u043a\u0438 \u0442\u0435\u0441\u0442\u0430.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">testFeedFromURLErrorInternet<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#pragma mark test - (void)testFeedFromURLErrorInternet {     [self stubHttpErrorDomain:NSURLErrorDomain code:NSURLErrorNotConnectedToInternet userInfo:nil];          [self asyncTest:^(XCTestExpectation *expectation) {         @weakify(self);         [self.service feedFromStringUrl:self.url success:^(NSArray *itemNews) {             @strongify(self);             [expectation fulfill];             XCTFail(@&quot;this is error&quot;);         } failure:^(NSError *error) {             @strongify(self);             [expectation fulfill];             XCTAssertEqualObjects([error domain], NSURLErrorDomain);             XCTAssertEqual([error code], NSURLErrorNotConnectedToInternet);         }];     }]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u0422\u0435\u0441\u04423: \u0421\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d<\/h4>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">testFeedFromURLErrorServerNotFound<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#pragma mark test - (void)testFeedFromURLErrorServerNotFound {     [self stubHttpErrorDomain:NSURLErrorDomain code:NSURLErrorCannotFindHost userInfo:nil];          [self asyncTest:^(XCTestExpectation *expectation) {         @weakify(self);         [self.service feedFromStringUrl:self.url success:^(NSArray *itemNews) {             @strongify(self);             [expectation fulfill];             XCTFail(@&quot;this is error&quot;);         } failure:^(NSError *error) {             @strongify(self);             [expectation fulfill];             XCTAssertEqualObjects([error domain], NSURLErrorDomain);             XCTAssertEqual([error code], NSURLErrorCannotFindHost);         }];     }]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u041a\u0430\u043a \u0432\u0438\u0434\u043d\u043e, \u0437\u0434\u0435\u0441\u044c \u043d\u0435 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u043b\u0443\u0447\u0430\u0438, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 \u043c\u0435\u0442\u043e\u0434\u0430 \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d ulr, \u043b\u0438\u0431\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d \u0431\u043b\u043e\u043a, \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u0447\u0430\u0441\u0442\u044c \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u0441\u0438\u0441\u0442\u0435\u043c\u0435.<\/p>\n<p>  \u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430. \u041a\u043e\u0434 \u0443\u043f\u0440\u043e\u0449\u0435\u043d, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e \u2014 \u043d\u0435\u0442 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u043e\u0433\u043e \u0443\u0440\u043e\u0432\u043d\u044f, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u0442\u044c \u043a\u043e\u0434.   <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSFeedService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &lt;Foundation\/Foundation.h&gt;   @interface RSFeedService : NSObject  + (instancetype)sharedInstance; - (void)feedFromStringUrl:(NSString *)url success:(RSItemsBlock)success failure:(RSErrorBlock)failure;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;RSFeedService.h&quot; #import &quot;RSFeedParser.h&quot;   @interface RSFeedService ()  @property (strong, nonatomic) RSFeedParser *parser; @property (strong, nonatomic) AFHTTPRequestOperationManager *transportLayer;  @end   @implementation RSFeedService  + (instancetype)sharedInstance {     static dispatch_once_t onceToken;     static RSFeedService *instance;     dispatch_once(&onceToken, ^{         instance = [[self alloc] init];         instance.parser = [RSFeedParser sharedInstance];         instance.transportLayer = [self createSimpleOperationManager];     });     return instance; }  + (AFHTTPRequestOperationManager *)createSimpleOperationManager {     AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];     manager.responseSerializer = [[AFXMLParserResponseSerializer alloc] init];     manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@&quot;application\/xml&quot;, @&quot;text\/xml&quot;,@&quot;application\/rss+xml&quot;]];     return manager; }  - (void)feedFromStringUrl:(NSString *)url success:(RSItemsBlock)success failure:(RSErrorBlock)failure {     @weakify(self);     [self.transportLayer GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {         @strongify(self);         NSDictionary *dom = [NSDictionary dictionaryWithXMLParser:responseObject];         NSArray *items = [self.parser itemFeed:dom];         success(items);     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {         failure(error);     }]; }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSFeedParser<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &lt;Foundation\/Foundation.h&gt;   @interface RSFeedParser : NSObject  + (instancetype)sharedInstance; - (NSArray *)itemFeed:(NSDictionary *)dom;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;RSFeedParser.h&quot; #import &lt;MWFeedParser\/NSDate+InternetDateTime.h&gt; #import &quot;RSFeedItem.h&quot;   NSString * const RSFeedParserChannel = @&quot;channel&quot;; NSString * const RSFeedParserItem = @&quot;item&quot;; NSString * const RSFeedParserTitle = @&quot;title&quot;; NSString * const RSFeedParserPubDate = @&quot;pubDate&quot;; NSString * const RSFeedParserDescription = @&quot;description&quot;; NSString * const RSFeedParserLink = @&quot;link&quot;;   @implementation RSFeedParser  + (instancetype)sharedInstance {     static dispatch_once_t onceToken;     static RSFeedParser *instance;     dispatch_once(&onceToken, ^{         instance = [[self alloc] init];     });     return instance; }  - (NSArray *)itemFeed:(NSDictionary *)dom {     NSDictionary *channel = dom[RSFeedParserChannel];     NSArray *items = channel[RSFeedParserItem];     return [items bk_map:^id(NSDictionary *item) {         NSString *title = item[RSFeedParserTitle];         NSString *description = item[RSFeedParserDescription];         NSString *pubDateString = item[RSFeedParserPubDate];         NSString *linkString = item[RSFeedParserLink];                  NSDate *pubDate = [NSDate dateFromInternetDateTimeString:pubDateString formatHint:DateFormatHintRFC822];         NSURL *link = [NSURL URLWithString:linkString];         return [RSFeedItem initWithTitle:title descriptionNews:description pubDate:pubDate link:link];     }]; }   @end <\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSFeedItem<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">@interface RSFeedItem : NSObject  @property (copy, nonatomic, readonly) NSString *title; @property (copy, nonatomic, readonly) NSString *descriptionNews; @property (strong, nonatomic, readonly) NSDate *pubDate; @property (strong, nonatomic, readonly) NSURL *link;  + (instancetype)initWithTitle:(NSString *)title descriptionNews:(NSString *)descriptionNews pubDate:(NSDate *)pubDate link:(NSURL *)link; - (instancetype)initWithTitle:(NSString *)title descriptionNews:(NSString *)descriptionNews pubDate:(NSDate *)pubDate link:(NSURL *)link;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;RSFeedItem.h&quot;   @interface RSFeedItem ()  @property (copy, nonatomic, readwrite) NSString *title; @property (copy, nonatomic, readwrite) NSString *descriptionNews; @property (strong, nonatomic, readwrite) NSDate *pubDate; @property (strong, nonatomic, readwrite) NSURL *link;  @end   @implementation RSFeedItem  + (instancetype)initWithTitle:(NSString *)title descriptionNews:(NSString *)descriptionNews pubDate:(NSDate *)pubDate link:(NSURL *)link {     return [[self alloc] initWithTitle:title descriptionNews:descriptionNews pubDate:pubDate link:link]; }  - (instancetype)initWithTitle:(NSString *)title descriptionNews:(NSString *)descriptionNews pubDate:(NSDate *)pubDate link:(NSURL *)link {     self = [super init];     if (self != nil) {         self.title = title;         self.descriptionNews = descriptionNews;         self.pubDate = pubDate;         self.link = link;     }     return self; }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<h2>\u0410 \u0433\u0434\u0435 CoreData?<\/h2>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0434\u0440\u0443\u0433\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u2014 \u0440\u0430\u0431\u043e\u0442\u0430 \u0441\u043e \u0441\u043f\u0438\u0441\u043a\u043e\u043c RSS.<br \/>  1) \u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a RSS<br \/>  2) \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c RSS<br \/>  3) \u0423\u0434\u0430\u043b\u0438\u0442\u044c RSS<br \/>  4) \u041f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u044e\u0442\u0441\u044f 2 RSS \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430.<\/p>\n<p>  \u041a\u0430\u043a \u0432\u0430\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043f\u0443\u043d\u043a\u0442? \u0410 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0434\u043e\u2026 \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043a\u0430\u043a \u0440\u0430\u0437 \u044d\u0442\u043e \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e (\u0441\u043f\u0430\u0441\u0438\u0431\u043e <a href=\"http:\/\/ocmock.org\">OCMock<\/a>). <br \/>  \u041d\u0430\u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u0435\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 3 \u043f\u0443\u043d\u043a\u0442\u0430, \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 <a href=\"http:\/\/habrahabr.ru\/post\/215033\/\">ReactiveCocoa<\/a><\/p>\n<p>  \u0412 \u043c\u0435\u0442\u043e\u0434\u0435 setUp \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0440\u0435\u0436\u0438\u043c \u0434\u043b\u044f MagicalRecord &#8216;in-memory&#8217;, \u0442\u0430\u043a \u043d\u0430\u043c \u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043e \u043f\u043e\u0432\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0438 \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445.<br \/>  \u0422\u0430\u043a \u0436\u0435 \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f 4\u0433\u043e \u043f\u0443\u043d\u043a\u0442\u0430 \u0434\u0435\u043b\u0430\u0435\u043c \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e\u0435 \u043c\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<br \/>  \u0412 \u043c\u0435\u0442\u043e\u0434\u0435 tearDown \u0447\u0438\u0441\u0442\u0438\u043c MagicalRecord, \u0438 \u0447\u0438\u0441\u0442\u0438\u043c \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e\u0435 \u043c\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSLinkServiceIT.m setUp\/tearDown<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">@interface RSLinkServiceIT : RSTestCaseIT  @property (strong, nonatomic) RSLinkService *service; @property (strong, nonatomic) id mockUserDefaults;  @end   @implementation RSLinkServiceIT  - (void)setUp {     [super setUp];     \/\/ Put setup code here. This method is called before the invocation of each test method in the class.          [MagicalRecord setupCoreDataStackWithInMemoryStore];     self.service = [RSLinkService sharedInstance];          id userDefaults = [NSUserDefaults standardUserDefaults];     [userDefaults setBool:YES forKey:RSHasBeenAddStandardLink];     self.mockUserDefaults = OCMPartialMock(userDefaults); }  - (void)tearDown {     [MagicalRecord cleanUp];     [self.mockUserDefaults stopMocking];     \/\/ Put teardown code here. This method is called after the invocation of each test method in the class.          [super tearDown]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u0422\u0435\u0441\u0442 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0443\u043d\u043a\u0442\u0430 4<\/h4>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">testOnFirstRunHave2Link<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#pragma mark test - (void)testOnFirstRunHave2Link {     OCMStub([self.mockUserDefaults boolForKey:RSHasBeenAddStandardLink]).andReturn(NO);          [self asyncTest:^(XCTestExpectation *expectation) {         @weakify(self);         [self.service list:^(NSArray *items) {             @strongify(self);             [expectation fulfill];             XCTAssertEqual([items count], 2);         } failure:^{             @strongify(self);             [expectation fulfill];             XCTFail(@&quot;error&quot;);         }];     } timeout:0.1]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u043e\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435\/\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\/\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 RSS \u0441\u0441\u044b\u043b\u043e\u043a.<br \/>  \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u0443 \u0441\u0441\u044b\u043b\u043e\u043a, \u0443\u0434\u0430\u043b\u0438\u043c \u043e\u0434\u043d\u0443 \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0442\u0435\u0445, \u0447\u0442\u043e \u0438\u043c\u0435\u0435\u043c. \u0421\u0435\u0440\u0432\u0438\u0441 \u0438\u043c\u0435\u0435\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 (\u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u0440\u043e\u0449\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438), \u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430. \u041f\u043e \u044d\u0442\u043e\u043c\u0443 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f ReactiveCocoa \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">testList<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#pragma mark test - (void)testList {     [self asyncTest:^(XCTestExpectation *expectation) {         [self asyncTestList:expectation];     } timeout:0.1]; }  - (void)asyncTestList:(XCTestExpectation *)expectation {     NSString *rss1 = @&quot;http:\/\/news.rambler.ru\/rss\/scitech1\/&quot;;     NSString *rss2 = @&quot;http:\/\/news.rambler.ru\/rss\/scitech2\/&quot;;          RACSignal *signalAdd1 = [self createSignalAddRSS:rss1];     RACSignal *signalAdd2 = [self createSignalAddRSS:rss2];     RACSignal *signalRemove = [self createSignalRemove:rss1];     RACSignal *signalList = [self createSignalList];          [[[[signalAdd1 flattenMap:^RACStream *(id _) {         return signalAdd2;     }] flattenMap:^RACStream *(id _) {         return signalRemove;     }] flattenMap:^RACStream *(id _) {         return signalList;     }] subscribeNext:^(NSArray *items) {         [expectation fulfill];         XCTAssertEqual([items count], 1);         XCTAssertEqualObjects(items[0], rss2);     } error:^(NSError *error) {         [expectation fulfill];         XCTFail(@&quot;%@&quot;, error);     }]; }  - (RACSignal *)createSignalAddRSS:(NSString *)rss {     @weakify(self);     return [RACSignal createSignal:^RACDisposable *(id&lt;RACSubscriber&gt; subscriber) {         @strongify(self);         [self.service add:rss success:^{             [subscriber sendNext:nil];             [subscriber sendCompleted];         } failure:^(NSError *error) {             @strongify(self);             XCTFail(@&quot;%@&quot;, error);         }];         return nil;     }]; }  - (RACSignal *)createSignalRemove:(NSString *)rss {     @weakify(self);     return [RACSignal createSignal:^RACDisposable *(id&lt;RACSubscriber&gt; subscriber) {         @strongify(self);         [self.service remove:rss success:^{             [subscriber sendNext:nil];             [subscriber sendCompleted];         } failure:^(NSError *error) {             @strongify(self);             XCTFail(@&quot;%@&quot;, error);         }];         return nil;     }]; }  - (RACSignal *)createSignalList {     @weakify(self);     return [RACSignal createSignal:^RACDisposable *(id&lt;RACSubscriber&gt; subscriber) {         @strongify(self);         [self.service list:^(NSArray *items) {             [subscriber sendNext:items];             [subscriber sendCompleted];         } failure:^{             [subscriber sendError:nil];             [subscriber sendCompleted];         }];         return nil;     }]; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u0434<\/h4>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSLinkService<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &lt;Foundation\/Foundation.h&gt;   @interface RSLinkService : NSObject  + (instancetype)sharedInstance;  - (void)add:(NSString *)link success:(RSEmptyBlock)success failure:(RSErrorBlock)failure; - (void)list:(RSItemsBlock)callback failure:(RSEmptyBlock)failure; - (void)remove:(NSString *)link success:(RSEmptyBlock)success failure:(RSErrorBlock)failure;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;RSLinkService.h&quot; #import &quot;RSLinkDAO.h&quot;   @interface RSLinkService ()  @property (strong, nonatomic) RSLinkDAO *dao;  @end   @implementation RSLinkService  + (instancetype)sharedInstance {     static dispatch_once_t onceToken;     static RSLinkService *instance;     dispatch_once(&onceToken, ^{         instance = [[self alloc] init];         instance.dao = [RSLinkDAO sharedInstance];     });     return instance; }  - (void)add:(NSString *)link success:(RSEmptyBlock)success failure:(RSErrorBlock)failure {     [self.dao add:link];     success(); }  - (void)list:(RSItemsBlock)callback failure:(RSEmptyBlock)failure {     NSArray *list = [self.dao list];     callback(list); }  - (void)remove:(NSString *)link success:(RSEmptyBlock)success failure:(RSErrorBlock)failure {     [self.dao remove:link];     success(); }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RSLinkDAO<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &lt;Foundation\/Foundation.h&gt;   @interface RSLinkDAO : NSObject  + (instancetype)sharedInstance;  - (void)add:(NSString *)link; - (NSArray *)list; - (void)remove:(NSString *)link;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;RSLinkDAO.h&quot; #import &quot;RSLinkEntity.h&quot; #import &lt;MagicalRecord\/MagicalRecord.h&gt; #import &quot;NSString+RS_RSS.h&quot;   @interface RSLinkDAO () @end   @implementation RSLinkDAO  + (instancetype)sharedInstance {     static dispatch_once_t onceToken;     static RSLinkDAO *instance;     dispatch_once(&onceToken, ^{         instance = [[self alloc] init];     });     return instance; }  - (void)add:(NSString *)link {     NSString *url = [link convertToBaseHttp];     RSLinkEntity *entity = [self linkToLinkEntity:url];     [entity.managedObjectContext MR_saveToPersistentStoreAndWait]; }  - (NSArray *)list {     NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];     if (![standardUserDefaults boolForKey:RSHasBeenAddStandardLink]) {         [self addStandartLink];         [standardUserDefaults setBool:YES forKey:RSHasBeenAddStandardLink];         [standardUserDefaults synchronize];     }          NSArray *all = [RSLinkEntity MR_findAll];     return [self linkEntityToLink:all]; }  - (void)addStandartLink {     RSLinkEntity *entity = [self linkToLinkEntity:@&quot;http:\/\/developer.apple.com\/news\/rss\/news.rss&quot;];     [entity.managedObjectContext MR_saveToPersistentStoreAndWait];          RSLinkEntity *entity1 = [self linkToLinkEntity:@&quot;http:\/\/news.rambler.ru\/rss\/world&quot;];     [entity1.managedObjectContext MR_saveToPersistentStoreAndWait]; }  - (void)remove:(NSString *)link {     RSLinkEntity *entity = [self entityWithLink:link];     [entity MR_deleteEntity];     [entity.managedObjectContext MR_saveToPersistentStoreAndWait]; }   #pragma mark - convert  - (NSArray *)linkEntityToLink:(NSArray *)entitys {     return [entitys bk_map:^id(RSLinkEntity *entity) {         return entity.link;     }]; }  - (RSLinkEntity *)linkToLinkEntity:(NSString *)link {     RSLinkEntity *entity = [RSLinkEntity MR_createEntity];     entity.link = link;     return entity; }  - (RSLinkEntity *)entityWithLink:(NSString *)link {     return [RSLinkEntity MR_findFirstByAttribute:@&quot;link&quot; withValue:link]; }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">NSString+RS_RSS<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"objectivec\">#import &lt;Foundation\/Foundation.h&gt;   @interface NSString (RS_RSS)  - (instancetype)convertToBaseHttp;  @end <\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"objectivec\">#import &quot;NSString+RS_RSS.h&quot;   @implementation NSString (RS_RSS)  - (instancetype)convertToBaseHttp {     NSRange rangeHttp = [self rangeOfString:@&quot;http:\/\/&quot;];     NSRange rangeHttps = [self rangeOfString:@&quot;https:\/\/&quot;];     if (rangeHttp.location != NSNotFound || rangeHttps.location != NSNotFound) {         return self;     }          return [NSString stringWithFormat:@&quot;http:\/\/%@&quot;, self]; }  @end <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u041a\u0430\u043a \u0443\u0436\u0435 \u043f\u0438\u0441\u0430\u043b, \u0441\u0442\u0430\u0440\u0430\u043b\u0441\u044f \u0443\u0431\u0440\u0430\u0442\u044c \u043b\u0438\u0448\u043d\u0438\u0439 \u043a\u043e\u0434, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u0435\u0441\u0442\u0430\u0445.<br \/>  \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043b\u0438 \u0441\u0446\u0435\u043f\u043b\u0435\u043d\u044b \u0432\u0430\u0448\u0438 \u0447\u0430\u0441\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<br \/>  \u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 <a href=\"https:\/\/github.com\/ajjnix\/RSSReader\">\u0437\u0434\u0435\u0441\u044c<\/a>.               <\/p>\n<div class=\"clear\"><\/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=\"http:\/\/habrahabr.ru\/post\/272967\/\"> http:\/\/habrahabr.ru\/post\/272967\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>       \u0412 \u043f\u0440\u043e\u0448\u043b\u044b\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 \u044f \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b unit-\u0442\u0435\u0441\u0442\u044b, \u0432 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u0440\u0435\u0447\u044c \u043f\u043e\u0439\u0434\u0435\u0442 \u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445. <br \/>  \u0427\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0435 \u0432\u044b\u0448\u0435\u043b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043c, \u043d\u043e \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043b \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b, \u044f \u0440\u0435\u0448\u0438\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0447\u0430\u0441\u0442\u0438 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/RSS\">RSS<\/a> Reader&#8217;\u0430.<br \/>  \u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u043f\u043e\u0434\u0434\u0435\u043b\u043a\u0430 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432 \u0440\u0430\u0431\u043e\u0442\u044b.<br \/>  \u0411\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 CoreData.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\"  src=\"https:\/\/habrastorage.org\/files\/641\/d82\/f57\/641d82f5758146f9b3ba355001ffe38e.jpg\"\/><\/div>\n<h3>\u041f\u0430\u0440\u0430 \u0441\u043b\u043e\u0432 \u0442\u0435\u043e\u0440\u0438\u0438:<\/h3>\n<p>  Unit tests \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 \u0432 \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u0438.<br \/>  Integration tests \u2014 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u0447\u0430\u0441\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0432\u043c\u0435\u0441\u0442\u0435.  <\/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-271312","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/271312","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=271312"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/271312\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=271312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=271312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=271312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}