Skip to content

Instantly share code, notes, and snippets.

@subdan
Created January 11, 2019 19:24
Show Gist options
  • Save subdan/0df098f0b9f1991f09e4fd6e9ce08e16 to your computer and use it in GitHub Desktop.
Save subdan/0df098f0b9f1991f09e4fd6e9ce08e16 to your computer and use it in GitHub Desktop.
import Foundation
import CoreData
class StationService {
static var shared: StationService = StationService()
private init() {}
private let transport = StationTransport()
private let parser = StationParser()
private let persistence = StationPersistence()
private var stations: [Station]?
private var lastUpdated: Double = 0.0
/**
Obtains stations from the local disk
- Returns: An array of Station
*/
func obtainLocalStations() -> [Station] {
// 1. Get [StationEntity]
let stationEntities: [StationEntity] = persistence.load()
// 2. Map [StationEntity] to [Station]
stations = stationEntities.compactMap({ stationEntity -> Station? in
let id = Int(stationEntity.id)
guard let name = stationEntity.name else {
return nil
}
guard let urlString = stationEntity.url, let url = URL(string: urlString) else {
return nil
}
guard let countryString = stationEntity.country, let country = StationCountry(rawValue: countryString) else {
return nil
}
guard let imageURLString = stationEntity.image, let image = URL(string: imageURLString) else {
return nil
}
let station = Station(id: id, name: name, url: url, country: country, image: image)
station.isFavorite = stationEntity.isFavorite
return station
})
// 3. Get info about when the local data was updated
lastUpdated = UserDefaults.standard.double(forKey: "lastUpdated")
return stations!
}
/// Returns loaded stations
func obtainCurrentStations() -> [Station]? {
return stations
}
/// Updates isFavorite property of Station
func updateIsFavorite(for station: Station, isFavorite: Bool) {
let fetch: NSFetchRequest<StationEntity> = StationEntity.fetchRequest()
fetch.predicate = NSPredicate(format: "url == %@", argumentArray: [station.url.absoluteString])
do {
let result = try CoreDataStack.shared.context.fetch(fetch)
if let stationEntity = result.first {
stationEntity.isFavorite = isFavorite
CoreDataStack.shared.save()
}
} catch {
print(error.localizedDescription)
}
}
/**
Obtains stations from the web server and sync with local data
- Parameter completion: Calls when new stations available
*/
func fetchStations(completion: @escaping ((FetchStationsResult) -> Void)) {
transport.getStations { [unowned self] data, error in
guard error == nil else {
DispatchQueue.main.async {
completion(.error())
}
return
}
// If data exists
guard let data = data else {
DispatchQueue.main.async {
completion(.value(stations: nil))
}
return
}
// If parse successfull
guard let parseResult = self.parser.parse(data: data) else {
DispatchQueue.main.async {
completion(.value(stations: nil))
}
return
}
let currentLastUpdated = UserDefaults.standard.double(forKey: "lastUpdated")
// If lastUpdated changed
if currentLastUpdated != 0 && currentLastUpdated == parseResult.lastUpdated {
DispatchQueue.main.async {
completion(.value(stations: nil))
}
return
}
if let stations = self.stations {
// Save IDs of favorite stations
let favoriteStationsIds = stations.compactMap({ station -> Int? in
return station.isFavorite ? station.id : nil
})
self.stations = parseResult.stations.map({ station -> Station in
station.isFavorite = favoriteStationsIds.contains(station.id)
return station
})
} else {
self.stations = parseResult.stations
}
UserDefaults.standard.set(parseResult.lastUpdated, forKey: "lastUpdated")
// Rewrite stations to disk
self.persistence.deleteAll()
DispatchQueue.main.async {
if let stations = self.stations {
self.persistence.save(stations: stations)
}
}
DispatchQueue.main.async {
completion(.value(stations: self.stations))
}
}
}
}
enum FetchStationsResult {
case value(stations: [Station]?)
case error()
}
private class StationTransport {
fileprivate enum Constants {
static let stationListURLString = "##########"
}
func getStations(_ completion: @escaping (Data?, Error?) -> Void) {
guard let url = URL(string: Constants.stationListURLString) else {
return
}
let dataTask = URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
guard error == nil else {
completion(nil, error)
return
}
completion(data, nil)
}
dataTask.resume()
}
}
private class StationParser {
func parse(data: Data) -> (stations: [Station], lastUpdated: Double)? {
let decoder = JSONDecoder()
do {
let response = try decoder.decode(StationListResponse.self, from: data)
var country = StationCountry.UA
let mapper: (StationListResponse.Station) -> Station = { (rawStation) -> Station in
let station = Station(id: Int(rawStation.id_ex)!,
name: rawStation.name,
url: URL(string: rawStation.stream)!,
country: country,
image: URL(string: rawStation.icon)!)
return station
}
let stationsUA = response.items_ua.map(mapper)
country = StationCountry.RU
let stationsRU = response.items_ru.map(mapper)
let lastUpdated = Double(response.last_updated)!
return (stationsRU + stationsUA, lastUpdated)
} catch let error {
print(error.localizedDescription)
}
return nil
}
}
struct StationListResponse: Decodable {
let ads: String
let items_ru: [StationListResponse.Station]
let items_ua: [StationListResponse.Station]
let last_updated: String
struct Station: Decodable {
let name: String
let id_ex: String
let icon: String
let stream: String
}
}
private class StationPersistence {
/// Loads stations from the disk
func load() -> [StationEntity] {
let fetchRequest: NSFetchRequest<StationEntity>
if #available(iOS 10.0, *) {
fetchRequest = StationEntity.fetchRequest()
} else {
fetchRequest = NSFetchRequest(entityName: "StationEntity")
}
let sortByName = NSSortDescriptor(key: "id", ascending: true)
fetchRequest.sortDescriptors = [sortByName]
do {
let result = try CoreDataStack.shared.context.fetch(fetchRequest)
return result
} catch {
print(error.localizedDescription)
return [StationEntity]()
}
}
/// Deletes all stations from the disk
func deleteAll() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult>
if #available(iOS 10.0, *) {
fetchRequest = StationEntity.fetchRequest()
} else {
fetchRequest = NSFetchRequest(entityName: "StationEntity")
}
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try CoreDataStack.shared.context.execute(batchDeleteRequest)
} catch {
fatalError("Failed to execute request: \(error)")
}
}
/// Saves stations to the disk
func save(stations: [Station]) {
guard stations.count > 0 else { return }
for station in stations {
let stationEntity: StationEntity
if #available(iOS 10, *) {
stationEntity = StationEntity(context: CoreDataStack.shared.context)
} else {
let newEntity = NSEntityDescription.insertNewObject(forEntityName: "StationEntity", into: CoreDataStack.shared.context)
if let entity = newEntity as? StationEntity {
stationEntity = entity
} else {
continue
}
}
stationEntity.id = Int64(station.id)
stationEntity.name = station.name
stationEntity.url = station.url.absoluteString
stationEntity.country = station.country.rawValue
stationEntity.image = station.image.absoluteString
}
CoreDataStack.shared.save()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment