[ad_1]
I tend of over complicating issues, and I am scared this might be a type of instances. The code works, however is it good / scalable?
Right here is the duty: create a information aggregation app that gathers information from
a number of information sources. Every information supply needs to be thought-about a third
social gathering API that we will not change or modify.Necessities:
- The app ought to fetch distant information from three endpoints (information is in JSON format). Every endpoint returns a distinct format of JSON information,
however all of them have a picture URL, title, and subtitle/textual content.- In any case the info is loaded, the app ought to show it in an inventory the place every merchandise has a title, subtitle, and picture.
- The information needs to be cached. Cache stale occasions are: endpoint A – 15 sec, B – 30 sec, C – 60 sec.
- The app ought to have a refresh possibility (e.g. pull to refresh). Refreshing ought to nonetheless respect cache stale occasions.
What will we care about:
- Clear structure. You’ll be able to select any structure you want, e.g. MVVM.
- Ensure that the app is extendable. What if we plan so as to add 10 extra information sources?
- Error and edge case dealing with. What if one of many information sources didn’t load? All of them?
- You should use any third social gathering libs you want and discover them cheap.
class Presenter {
let sourceA = "https://res.cloudinary.com/woodspoonstaging/uncooked/add/v1645473019/assignments/mobile_homework_datasource_a_tguirv.json"
let sourceB = "https://res.cloudinary.com/woodspoonstaging/uncooked/add/v1645473019/assignments/mobile_homework_datasource_b_x0farp.json"
let sourceC = "https://res.cloudinary.com/woodspoonstaging/uncooked/add/v1645517666/assignments/mobile_homework_datasource_c_fqsu4l.json"
let dispatchGroup = DispatchGroup()
var newsItems = [NewsItem]()
let notifyView = PublishSubject<Void>()
public func getItems() {
newsItems.removeAll()
receiveSource(ofType: SourceA.self)
receiveSource(ofType: SourceB.self)
receiveSource(ofType: SourceC.self)
dispatchGroup.notify(queue: .foremost) { [weak self] in
guard let self = self else { return }
self.notifyView.onNext(())
}
}
personal func receiveSource<T:Codable>(ofType kind: T.Sort) {
dispatchGroup.enter()
swap kind {
case is SourceA.Sort:
let sourceParams = SourceParams(url: sourceA,
key: "SourceA",
expiry: 15)
receiveSource(sourceType: kind, params: sourceParams) { [weak self] lead to
guard let self = self else { return }
swap consequence {
case .success(let supply):
var array = [NewsItem]()
array = self.parseSourceA(supply as! SourceA)
self.newsItems.append(contentsOf: array)
self.dispatchGroup.depart()
case .failure(_):
self.dispatchGroup.depart()
}
}
case is SourceB.Sort:
let sourceParams = SourceParams(url: sourceB,
key: "SourceB",
expiry: 30)
receiveSource(sourceType: kind, params: sourceParams) { [weak self] lead to
guard let self = self else { return }
swap consequence {
case .success(let supply):
var array = [NewsItem]()
array = self.parseSourceB(supply as! SourceB)
self.newsItems.append(contentsOf: array)
self.dispatchGroup.depart()
case .failure(_):
self.dispatchGroup.depart()
}
}
case is SourceC.Sort:
let sourceParams = SourceParams(url: sourceC,
key: "SourceC",
expiry: 60)
receiveSource(sourceType: kind, params: sourceParams) { [weak self] lead to
guard let self = self else { return }
swap consequence {
case .success(let supply):
var array = [NewsItem]()
array = self.parseSourceC(supply as! SourceC)
self.newsItems.append(contentsOf: array)
self.dispatchGroup.depart()
case .failure(_):
self.dispatchGroup.depart()
}
}
default:
break
}
}
personal func receiveSource<T:Codable>(sourceType: T.Sort, params: SourceParams, completion: @escaping (_ consequence: Swift.Consequence<T, Error>) -> Void) {
// Get Parameters
let key = params.key
let expiry = params.expiry
let sourceURL = params.url
let diskConfig = DiskConfig(title: key, expiry: .seconds(expiry))
let memoryConfig = MemoryConfig(expiry: .seconds(expiry))
do {
let storage = strive Storage(diskConfig: diskConfig,
memoryConfig: memoryConfig,
transformer: TransformerFactory.forCodable(ofType: sourceType))
// Verify storage if merchandise exists & not expired
// If true, return objects
if strive storage.existsObject(forKey: key) && !storage.isExpiredObject(forKey: key) {
let supply = strive storage.object(forKey: key)
completion(.success(supply))
} else { // if false, request, then return objects
Alamofire.request(sourceURL).responseString { response in
do {
let jsonDecoder = JSONDecoder()
if let information = response.information {
let merchandise = strive jsonDecoder.decode(sourceType, from: information)
strive storage.setObject(merchandise, forKey: key)
completion(.success(merchandise))
return
}
} catch {
completion(.failure(error))
}
}
}
} catch {
completion(.failure(error))
}
}
personal func parseSourceA(_ supply: SourceA) -> [NewsItem] {
if let tales = supply.tales {
let array = tales.map { NewsItem(title: $0.title,
subtitle: $0.subtitle,
picture: $0.imageUrl) }
return array
} else {
return []
}
}
personal func parseSourceB(_ supply: SourceB) -> [NewsItem] {
if let objects = supply.metadata?.innerdata {
let array = objects.map { NewsItem(title: $0.articlewrapper?.header,
subtitle: $0.articlewrapper?.articlewrapperDescription,
picture: $0.image) }
return array
} else {
return []
}
}
personal func parseSourceC(_ supply: SourceC) -> [NewsItem] {
let array = supply.map { NewsItem(title: $0.topLine,
subtitle: ($0.subLine1 ?? "") + ($0.subline2 ?? ""),
picture: $0.picture) }
return array
}
}
[ad_2]
