jjzjj

ios - 具有异步网络请求的 ReactiveCocoa 排序

coder 2023-07-29 原文

我正在构建一个演示应用程序并试图符合 ReactiveCocoa design pattern越多越好。以下是该应用的作用:

  • 找到设备的位置
  • 每当位置键改变时,获取:
    • 当前天气
    • 每小时预报
    • 每日预报

所以顺序是 1) 更新位置 2) 合并所有 3 个天气数据。我构建了一个 WeatherManager 单例,它公开了天气对象、位置信息和手动更新的方法。此单例符合 CLLocationManagerDelegate 协议(protocol)。位置代码非常基本,所以我将其省略。唯一真正感兴趣的是:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
    // omitting accuracy & cache checking
    CLLocation *location = [locations lastObject];
    self.currentLocation = location;
    [self.locationManager stopUpdatingLocation];
}

获取天气状况非常相似,因此我构建了一个方法来生成 RACSignal 以从 URL 获取 JSON。

- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (! error) {
                NSError *jsonError = nil;
                id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
                if (! jsonError) {
                    [subscriber sendNext:json];
                }
                else {
                    [subscriber sendError:jsonError];
                }
            }
            else {
                [subscriber sendError:error];
            }

            [subscriber sendCompleted];
        }];

        [dataTask resume];

        return [RACDisposable disposableWithBlock:^{
            [dataTask cancel];
        }];
    }];
}

这有助于我保持我的方法简洁明了,所以现在我有 3 个简短的方法来构建 URL 并返回 RACSignal。这里的好处是我可以创建副作用来解析 JSON 并分配适当的属性(注意:我在这里使用 Mantle)。

- (RACSignal *)fetchCurrentConditions {
    // build URL
    return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
        // simply converts JSON to a Mantle object
        self.currentCondition = [MTLJSONAdapter modelOfClass:[CurrentCondition class] fromJSONDictionary:json error:nil];
    }];
}

- (RACSignal *)fetchHourlyForecast {
    // build URL
    return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
        // more work
    }];
}

- (RACSignal *)fetchDailyForecast {
    // build URL
    return [[self fetchJSONFromURL:url] doNext:^(NSDictionary *json) {
        // more work
    }];
}

最后,在我的单例的 -init 中,我在位置上设置了 RAC 观察器,因为每次位置改变时我都想获取和更新天气。

[[RACObserve(self, currentLocation)
 filter:^BOOL(CLLocation *newLocation) {
     return newLocation != nil;
 }] subscribeNext:^(CLLocation *newLocation) {
     [[RACSignal merge:@[[self fetchCurrentConditions], [self fetchDailyForecast], [self fetchHourlyForecast]]] subscribeError:^(NSError *error) {
         NSLog(@"%@",error.localizedDescription);
     }];
 }];

一切正常,但我担心我偏离了 Reactive 方式来构建我的获取和属性分配。我尝试使用 -then: 进行排序,但无法真正按照我的意愿进行设置。

我还试图找到一种干净的方法来将异步获取的结果绑定(bind)到我的单例的属性,但在实现它时遇到了麻烦。我无法弄清楚如何“扩展”获取的 RACSignal(注意:这就是每个 -doNext: 想法的来源)。

任何有助于解决这个问题的帮助或资源都会非常有用。谢谢!

最佳答案

-fetch 方法似乎不适合产生有意义的副作用,这让我认为您的 WeatherManager 类混淆了两个不同的东西:

  1. 获取最新数据的网络请求
  2. 该数据的有状态存储和呈现

这很重要,因为第一个问题是无状态的,而第二个问题几乎完全是有状态的。例如,在 GitHub for Mac 中,我们使用 OCTClient执行网络,然后将返回的用户数据存储在“持久状态管理器”单例中。

像这样分解后,我想它会更容易理解。您的状态管理器可以与网络客户端交互以启动请求,然后状态管理器可以订阅这些请求并应用副作用。

首先,让我们使 -fetch… 方法成为无状态的,方法是重写它们以使用转换而不是副作用:

- (RACSignal *)fetchCurrentConditions {
    // build URL
    return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
        return [MTLJSONAdapter modelOfClass:[CurrentCondition class] fromJSONDictionary:json error:nil];
    }];
}

然后,您可以使用这些无状态方法并在更合适的地方向它们注入(inject)副作用:

- (RACSignal *)updateCurrentConditions {
    return [[self.networkClient
        // If this signal sends its result on a background thread, make sure
        // `currentCondition` is thread-safe, or make sure to deliver it to
        // a known thread.
        fetchCurrentConditions]
        doNext:^(CurrentCondition *condition) {
            self.currentCondition = condition;
        }];
}

并且,要更新所有这些,您可以使用 +merge:(就像在您的示例中一样)结合 -flattenMap: 将位置值映射到新的工作信号:

[[[RACObserve(self, currentLocation)
    ignore:nil]
    flattenMap:^(CLLocation *newLocation) {
        return [RACSignal merge:@[
            [self updateCurrentConditions],
            [self updateDailyForecast],
            [self updateHourlyForecast],
        ]];
    }]
    subscribeError:^(NSError *error) {
        NSLog(@"%@", error);
    }];

或者,要在 currentLocation 更改时自动取消正在进行的更新,请将 -flattenMap: 替换为 -switchToLatest:

[[[[RACObserve(self, currentLocation)
    ignore:nil]
    map:^(CLLocation *newLocation) {
        return [RACSignal merge:@[
            [self updateCurrentConditions],
            [self updateDailyForecast],
            [self updateHourlyForecast],
        ]];
    }]
    switchToLatest]
    subscribeError:^(NSError *error) {
        NSLog(@"%@", error);
    }];

(来自 ReactiveCocoa/ReactiveCocoa#786 的原始回复)。

关于ios - 具有异步网络请求的 ReactiveCocoa 排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18705901/

有关ios - 具有异步网络请求的 ReactiveCocoa 排序的更多相关文章

  1. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  2. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  4. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  5. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  6. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  7. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  8. ruby-on-rails - Rails 3.1 中具有相同形式的多个模型? - 2

    我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

随机推荐