从多个视图控制器接收委托方法调用

Posted

技术标签:

【中文标题】从多个视图控制器接收委托方法调用【英文标题】:Receive delegate method calls from multiple view controllers 【发布时间】:2015-05-10 09:16:33 【问题描述】:

我有一个带有SplitViewController 的应用程序。 MasterViewControllerUITableViewControllerDetailViewControllerUIViewControllerMapView

我正在调用 API 来获取事件列表。在 tableView 中,我想显示事件的名称和地址。在 mapView 中,我想标记这些事件的位置。

我有一个名为 DataLoader 的类,我在其中调用 API,解析 JSON 对象并创建 Event 对象并将它们成功放入数组中。

import Foundation
import SwiftyJSON

@objc protocol DataLoaderDelegate 
    func eventDataReceived(items: [Event]?, error: NSError?)


public class DataLoader 

    private let api = ApiClient()

    var delegate: DataLoaderDelegate?

    init()  

    public func fetchEvents() 
        api.getEvents( (data) -> Void in
            let json = JSON(data)
            self.processEventData(json["data"])
        , failure:  (error) -> Void in
            println("Error fetching events: \(error?.localizedDescription)")
            self.delegate?.eventDataReceived(nil, error: error)
        )
    

    private func processEventData(data: JSON) 
        var events = [Event]()

        if let eventsArray = data.array 
            for eventObj in eventsArray 
                let event = Event(
                    id: eventObj["id"].int!,
                    type: EventType(rawValue: eventObj["type"].int!)!,
                    location: eventObj["location"].string!,
                    status: EventStatus(rawValue: eventObj["status"].int!)!
                )
                event.allDay = eventObj["allDay"].int!
                event.title = eventObj["title"].string
                event.description = eventObj["description"].string
                event.latitude = eventObj["lat"].double
                event.longitude = eventObj["lng"].double
                event.startDate = NSDate(string: eventObj["start"].string!)
                event.endDate = NSDate(string: eventObj["end"].string!)
                event.createdAtDate = NSDate(string: eventObj["created_at"].string!)
                event.updatedAtDate = NSDate(string: eventObj["updated_at"].string!)
                event.creatorEmail = eventObj["creatorEmail"].string
                event.organizerEmail = eventObj["organizerEmail"].string
                event.googleCalendarId = eventObj["google_cal_id"].string!
                event.googleEventId = eventObj["google_event_id"].string!
                event.htmlLink = eventObj["htmlLink"].string!

                events.append(event)
            

            // Order the events by the startDate value
            events.sort( $0.startDate!.timeIntervalSince1970 < $1.startDate!.timeIntervalSince1970 )

            self.delegate?.eventDataReceived(events, error: nil)
        
    

我有一个名为 DataLoaderDelegate 的委托和一个名为 func eventDataReceived(items: [Event]?, error: NSError?) 的方法,在将所有事件对象插入数组后调用该方法,以便我可以通过该方法传递它。

我需要在填充 tableView 的 ListViewController 和填充 mapView 的 MapViewController 中实现此方法。所以我在两个视图控制器中都实现了eventDataReceived委托方法来获取事件对象。

ListViewController.swift

import UIKit

class ListViewController: UITableViewController, DataLoaderDelegate 

    let loader = DataLoader()

    private var events = [Event]()

    override func viewDidLoad() 
        super.viewDidLoad()

        loader.delegate = self
        loader.fetchEvents()
    

    // MARK: - Table view data source
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return events.count
    

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell

        let event = events[indexPath.row]
        cell.textLabel?.text = event.title
        cell.detailTextLabel?.text = event.location

        return cell
    

    // MARK: - DataLoaderDelegate
    func eventDataReceived(items: [Event]?, error: NSError?) 
        println("ListViewController got \(items?.count) events")
        if let events = items 
            self.events = events
            tableView.reloadData()
        
    

MapViewController.swift

import UIKit

class MapViewController: UIViewController, DataLoaderDelegate 

    let loader = DataLoader()

    override func viewDidLoad() 
        super.viewDidLoad()

        loader.delegate = self
    

    // MARK: - DataLoaderDelegate
    func eventDataReceived(items: [Event]?, error: NSError?) 
        println("MapViewController got \(items?.count) events")
    

但这就是问题所在。只有ListViewController 中的实现被执行。我认为原因是我在MapViewController 中创建了DataLoader 类的新实例。

我应该怎么做才能在整个应用程序中拥有一个DataLoader 实例并从所有视图控制器接收其委托方法调用?

如果您需要运行它以获得更好的想法,我已经将一个演示项目上传到我的Dropbox。

谢谢。

【问题讨论】:

【参考方案1】:

这是Singleton-pattern 的最佳实践案例 你的 DataLoader 应该实现一个静态的 let 来保存对同一个 self 对象的引用。

static let sharedInstance = DataLoader()

当您希望引用始终相同的单例对象时。用DataLoader.sharedInstance调用它

确保始终使用 .sharedInstance 并且不要忘记擦除类的标准初始化程序的任何调用,因为除非您以编程方式阻止此行为,否则它仍会创建新实例。

解释

仅插入单例常量(如上所示)不会使 DataLoader 类始终返回单例实例。现有的初始化程序调用如下:

   var myDataLoader = DataLoader()

每次调用它们时仍会实例化一个新对象。单例实例通过以下方式到达:

   var mySingletonDataLoader = DataLoader.sharedInstance

您可以更改标准(非单例)初始化程序,例如:

init() 
NSLog("No instances allowed, please use .sharedInstance for a singleton")

完整的解决方案

单例只是解决整个问题的一块。使用的委托模式非常适合将信息从一个对象发送到另一个对象,从而委派工作。 提问者的问题需要另一种从一个对象到许多其他对象的广播机制。因此他决定用NSNotificationCenter来实现Observer-pattern

【讨论】:

谢谢。我会试试的。顺便问一下,你能详细说明一下这部分吗? > 擦除类的标准初始化程序的任何调用。如果可以的话,一个例子会很有帮助。 谢谢。我进行了您提到的必要更改,但是当我从视图控制器引用属性(delegate 属性)和方法(fetchEvents())时,我仍然在init 方法中收到错误消息。 Here 是我的代码。 每次调用DataLoader.sharedInstance.delegate 都会覆盖旧的委托。只会调用最新的代表。单例模式是正确的方法,但委托可能不是正确的方法。您应该使用Observer-pattern 我使用了NSNotificationCenter,它可以工作。谢谢。 很高兴听到这个消息。好办法。我更新了答案以融合您的信息

以上是关于从多个视图控制器接收委托方法调用的主要内容,如果未能解决你的问题,请参考以下文章

从应用委托类调用视图控制器的方法

如何从视图控制器调用所有方法和函数到应用程序委托?

从 tableView 委托类中调用视图控制器 segue

.reloadData() 没有调用 NSTableView 委托方法

未调用拆分视图弹出框委托方法

UITableViewController 作为 ChildViewController 不调用委托方法