2017-04-24 36 views
0

我的应用程序需要从应用程序的几个不同位置下载我的网站上的文件,因此编写函数以完成下载一次,将其放入其自己的类并从每个ViewController调用该函数似乎是有意义的。到目前为止,很好,一切正常。正在下载,正确地下载了文件print从多个视图控制器调用文件下载功能;如何将结果返回给那些VC的?

问题出现在下载函数发送一个“成功”或“失败”消息返回给调用它的ViewController时,以便VC可以做出相应的反应 - 更新显示,关闭下载对话框, 随你。如何做到这一点是我卡住的地方。

我有什么:

每个ViewControllerTwo和ViewControllerThree(这是相同的,现在,除了要求从我的服务器不同的文件除外)的调用下载功能这样的:

Downloader.load(url: urlForFileA!, to: localDestinationFileA, callingViewControllerNumber: 2) 

的代码下载器功能(目前同步,但最终会变成异步)看起来像这样(在其自己的Downloader类中):

class func load(url: URL, to localUrl: URL, callingViewControllerNumber: Int) { 
    let sessionConfig = URLSessionConfiguration.default 
    let session = URLSession(configuration: sessionConfig) 
    let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData) 

    let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in 
     if let tempLocalUrl = tempLocalUrl, error == nil { 
      // Got a file, might be a 404 page... 
      if let statusCode = (response as? HTTPURLResponse)?.statusCode { 
       print("Success downloading: \(statusCode)") 
       if statusCode == 404 { 
        // ERROR -- FILE NOT FOUND ON SERVER 
        returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .fileNotFound, errorMessage: "File not Found, 404 error") 
       } 
      } 
      do { 
       try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl) 
       // SUCCESS! 
       returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .success, errorMessage: "") 
      } catch (let writeError) { 
       returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .movingError, errorMessage: "\(writeError)") 
      } 
     } else { 
      returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .downloadFailed, errorMessage: "Grave Unexplained Failure") 
     } 
    } 
    task.resume() 
} 

这部分工作。

returnToCaller功能是不可否认的难看的(好吧,非常非常丑陋的)的方式来送东西回调用视图控制器:

class func returnToCaller(sourceIdent : Int, successStatus : downloadSuccessStatusEnum, errorMessage : String) { 
    switch sourceIdent { 
    case 2: 
     ViewControllerTwo().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage) 
    case 3: 
     ViewControllerThree().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage) 
    default: 
     fatalError("Unknown sourceIdent of \(sourceIdent) passed to func returnToCaller") 
    } 
} 

的问题是,当returnFromDownload功能在原来的ViewController被调用,它不知道VC中加载的任何东西 - 我去改变标签的背景颜色,并得到运行时错误,标签为nil。这个标签存在,但是这个对ViewController代码的调用与正在运行的,实例化的VC本身隔离。 (可能是那里的错误词汇 - 道歉。)代码运行并且可以print,但在与视图本身中的任何内容进行交互时出错。

我对此的处理越多,我就越不自信,我在这里的正确轨道上,而我对Swift的有限经验还不足以看到需要发生什么,以便下载功能可以做到所有的工作“在这里”,然后向调用的VC返回成功/失败消息,以便VC可以使用它。

This question似乎在问类似的东西;那里的一个答案并没有解决我如何在提交的VC中获取代码的根本问题,这个问题再次发生在VC之外发生的结果(经理批准他的情况下,我的下载) 。

不要求我的代码被重写(除非它是一个快速修复),但需要指向正确的方向。非常感谢!

+1

如果我正确地阅读这篇文章,您希望 - 当天回来 - 被称为“松散耦合”代码。在Swift之前的日子里,另一个名字可能是KVC编码。基本上,你想尝试下载*异步*,并将结果发回给调用它的VC。至少有两种方法可以想到。 (1)可能最正确的方法是将观察者添加到默认NSNotificationCenter(http://stackoverflow.com/questions/24049020/nsnotificationcenter-addobserver-in-swift)。 (2)另一种方式 - 不太优雅,但它可能完成工作 - 是使用UIButton的sendAction方法。 – dfd

回答

1

你想要的东西可以很容易地用闭包完成。

的第一步是到另一个参数添加到您的load方法和删除您callingViewController PARAM:

class func load(url: URL, to localUrl: URL, completion: (downloadSuccessStatusEnum, String) -> Void)

这将允许你打电话completion,而不是returnToCaller当你的方法完成,像这样:

DispatchQueue.main.async { 

    completion(.success, "Insert your error message here") 
} 

最后,要调用此方法,只需在VC中调用方法即可:

Downloader.load(url: nameOfYourURL, to: destinationName) { statusEnum, errorString in 
    // Insert your code you want after the request completes here 
} 
+0

非常好!编译器似乎建议闭包应该是逃避,并基于[这个答案](http://stackoverflow.com/questions/42214840/swift-3-closure-use-of-non-escaping-parameter-may-允许它逃跑),这似乎是有道理的。你会同意吗? – ConfusionTowers

+0

这是有效的!非常奇怪的是,在请求完成后运行的代码中,为了成功返回,我将标签的背景更改为绿色,然后“打印”文件内容(几页纯文本),并在打印发生时立即,标签更改颜色(至少在模拟器中)前大约需要5秒钟。我应该做的任何事情,以便视图立即作出反应?非常感谢您的帮助! – ConfusionTowers

+0

检查以确保您的响应正在主线程上返回。如果不是,则需要将其发送到主线程。 – AdamPro13

相关问题