iOS 应用架构实现 MVVM、网络和蓝牙,如何实现?

Posted

技术标签:

【中文标题】iOS 应用架构实现 MVVM、网络和蓝牙,如何实现?【英文标题】:iOS App architecture implementing MVVM, Networking and Bluetooth, how? 【发布时间】:2016-05-27 20:11:16 【问题描述】:

问题:

我目前在Swift 中开发 ios 移动应用程序时遇到问题,该应用程序使用:

BTLE:连接到外围设备并向其发送/接收数据。 Networking:如果外围设备连接到网络(无线和/或以太网),则通过 BTLE 进行的通信“可能”通过网络进行。 Model-View-ViewModel架构 RxSwift

关于应用程序:

它从 蓝牙设置 视图开始,引导用户完成与外围设备配对的过程(与 TabBarController 不相交)。

与设备配对成功后,iOS App向设备请求所有配置,发送为JSON

这个JSON 包含不同的Model 信息(编程),应用程序显示给用户进行操作,需要以某种方式存储在Singleton 庄园的数组中,view-model 可以请求任何向用户显示的索引。

收到所有数据后,Bluetooth View 关闭并显示 TabBarView

当前示例:

与此应用相关的一个很好的例子是Apple Watch 和相关的 iOS 应用,它允许您配置所有内容。我不得不做一些相同的概念。

来自blog post 的另一个很好的示例应用程序,他们正在做类似于我想要实现的事情。不过,我遇到的不同之处在于它们对 MVVM 的依赖注入设置(以及其他类似的示例)。我使用了一个故事板,因为他们在AppDelegate 中以编程方式实例化了他们的视图控制器。

还有我的问题……

如何在没有NSNotificationsPrepareForSegues 的情况下(有效地)将数据从BluetoothView 传递到TabBarView?请记住,我打算将库 RxSwift 用于异步事件处理和事件/数据流。我试图让这个应用程序尽可能无状态。

这个blog post 中的Servers 是检索view-models 和/或更新它们的好习惯吗?

【问题讨论】:

使用委托协议传递数据。 @sschale 但是 BTLEViews 和 TabBarView 是分离的,它们彼此不知道。并且不代表传递只会向后工作吗?从技术上讲,这将是一个“前进”运动。我正在尝试使其尽可能可测试,同时保留在 MVVM 架构中。 【参考方案1】:

我发现,当使用 RxSwift 时,“view-model”最终是一个单一的纯函数,它从输入 UI 参数中获取可观察的参数并返回可观察的参数,然后绑定到输出的 UI 元素。

tutorial videos for cycle.js 是真正帮助我理解 Rx 的东西。

至于你的具体难题...

您所做的不一定是“向前”运动。这样看... TabBarView 需要一些数据,它并不关心这些数据来自哪里。所以让 TabBarView 访问一个函数,该函数返回一个包含必要数据的可观察对象。该关闭将显示蓝牙视图,建立连接,获取必要的数据,然后关闭蓝牙视图并使用所需的数据调用onNext

查看this gist 可能有助于理解我在说什么。授予 gist 使用 PromiseKit 而不是 RxSwift,但可以使用相同的原理(而不是 fulfill,您可能想调用 onNext 然后 onCompletion。)在 gist 中,视图控制器只需要数据调用一个函数并订阅结果(在这种情况下,结果包含一个 UIImage。)该函数的工作是确定哪些图像源可用,询问用户他们想从哪个源检索图像并呈现适当的视图控制器来获取图像。

gist 目前的内容如下:

//
//  UIViewController+GetImage.swift
//
//  Created by Daniel Tartaglia on 4/25/16.
//  Copyright © 2016 MIT License
//

import UIKit
import PromiseKit


enum ImagePickerError: ErrorType 
    case UserCanceled


extension UIViewController 

    func getImage(focusView view: UIView) -> Promise<UIImage> 
        let proxy = ImagePickerProxy()
        let cameraAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.Camera) ? nil : UIAlertAction(title: "Camera", style: .Default)  _ in
            let controller = UIImagePickerController()
            controller.delegate = proxy
            controller.allowsEditing = true
            controller.sourceType = .Camera
            self.presentViewController(controller, animated: true, completion: nil)
        
        let photobinAction: UIAlertAction? = !UIImagePickerController.isSourceTypeAvailable(.PhotoLibrary) ? nil : UIAlertAction(title: "Photos", style: .Default)  _ in
            let controller = UIImagePickerController()
            controller.delegate = proxy
            controller.allowsEditing = false
            controller.sourceType = .PhotoLibrary
            self.presentViewController(controller, animated: true, completion: nil)
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)

        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
        if let cameraAction = cameraAction 
            alert.addAction(cameraAction)
        
        if let photobinAction = photobinAction 
            alert.addAction(photobinAction)
        
        alert.addAction(cancelAction)
        let popoverPresentationController = alert.popoverPresentationController
        popoverPresentationController?.sourceView = view
        popoverPresentationController?.sourceRect = view.bounds
        presentViewController(alert, animated: true, completion: nil)
        let promise = proxy.promise
        return promise.always 
            self.dismissViewControllerAnimated(true, completion: nil)
            proxy.retainCycle = nil
        
    



private final class ImagePickerProxy: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate 

    let (promise, fulfill, reject) = Promise<UIImage>.pendingPromise()
    var retainCycle: ImagePickerProxy?

    required override init() 
        super.init()
        retainCycle = self
    

    @objc func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) 
        let image = (info[UIImagePickerControllerEditedImage] as? UIImage) ?? (info[UIImagePickerControllerOriginalImage] as! UIImage)
        fulfill(image)
    

    @objc func imagePickerControllerDidCancel(picker: UIImagePickerController) 
        reject(ImagePickerError.UserCanceled)
    

【讨论】:

感谢您的回复,谢谢。让我看看我能不能总结一下你所说的。基本上将TabBarController 保留为InitialViewController,当它“加载”时,调用闭包来呈现BluetoothViewController,这将返回必要的数据。假设相同的“设置”可以用于网络通信(包装闭包函数以返回数据)是否安全? 当然可以。无论异步数据来自用户还是网络,同样的想法都可以正常工作。 RxCocoa 已经有了从网络中检索数据的方法。另一篇关于 Rx 的好文章 yarikx.github.io/NotRxJava

以上是关于iOS 应用架构实现 MVVM、网络和蓝牙,如何实现?的主要内容,如果未能解决你的问题,请参考以下文章

IOS面向协议的MVVM架构介绍(来着网络的链接)

使用 MVVM,如何在低级服务和视图模型之间建立通信线路?

iOS应用千万级架构:MVVM框架

iOS 中使用 MVVM,复杂的 Cell 的 ViewModel 应该如何去写

如何使用 MVVM 架构实现 Firebase Google SignIn?

iOS架构:MVVM设计模式+RAC响应式编程