I have an app with a SplitViewController
. The MasterViewController
is a UITableViewController
. And the DetailViewController
is a UIViewController
with a MapView
.
I'm calling an API to get a list of events. In the tableView, I want to display the names and the addresses of the events. And in the mapView, I want to mark the locations of those events.
I have a class called DataLoader
in which I call the API, parse the JSON object and create Event
objects and put them in an array successfully.
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)
}
}
}
I have a delegate called DataLoaderDelegate
with a method called func eventDataReceived(items: [Event]?, error: NSError?)
which is called after all the event objects are inserted into an array so I can pass it through that method.
I need this method implemented in both the ListViewController
where I populate the tableView and MapViewController
where I populate the mapView. So I have implemented the eventDataReceived
delegate method in both view controllers in order to get the events objects.
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")
}
}
But here's the problem. Only the implementation in the ListViewController
gets executed. I think the reason is I create a new instance of DataLoader
class in the MapViewController
.
What should I do to have one instance of DataLoader
across the entire application and receive its delegate method calls from all view controllers?
I've uploaded a demo project to my Dropbox if you need to run it to get a better idea.
Thanks.
This is a best-practice case for a Singleton-pattern Your DataLoader should implement a static let holding the reference to the same object of self.
static let sharedInstance = DataLoader()
When you want the reference to constantly the same singleton-object. Call it with DataLoader.sharedInstance
Make sure to always use .sharedInstance then and do not forget to wipe any calls of the standard initializer of the class, because it will still make new instances unless you block this behavior programmatically.
Explained
Only inserting the singleton constant (shown above) will NOT make DataLoader class always return the singleton instance. The existing initializer calls like:
var myDataLoader = DataLoader()
will still instanciate a new object, everytime they are called. The singleton instance is reached with:
var mySingletonDataLoader = DataLoader.sharedInstance
You could change your standard (non singleton) initializers like:
init() {
NSLog("No instances allowed, please use .sharedInstance for a singleton")
}
Complete solution
The singleton is only one piece to solve the whole problem. The used delegate-pattern works great for sending information from one object to another and therefore delegating the work. The problem from the questioner needs another broadcasting mechanism from one object to many other object. Therefore he decided to implement the Observer-pattern with NSNotificationCenter
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With