Cast object to generic - swift

I am creating a function to be used in different occasions. But for this, I need to Cast the return of a function to the Object that I pass as generic in this main function.
func makeRequestToApi<T>(object: T, url: String) {
Alamofire.request(.GET, url).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
let object: [T] = self.createProductObject(data) as Any as! [T]
dispatch_async(dispatch_get_main_queue()) {
self.delegate?.networkingDidUpdate(object)
}
}
}
}
}
I thought that I only need to call this way:
networkingController.makeRequestToApi(Product, url: Urls.menu)
This function will return an array of products self.createProductObject(data) -> [Product]
But Xcode make me add .self to the first parameter in makeRequestToApi
networkingController.makeRequestToApi(Product.self, url: Urls.menu)
This way, as I see, Swift will not convert the return of my class to Product as I need it.
Anyone knows what I need to do?
Thank you.

You probably want something like this:
func makeRequestToApi<T>(create: JSON -> [T], url: String) {
Alamofire.request(.GET, url).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
let object = create(data)
dispatch_async(dispatch_get_main_queue()) {
self.delegate?.networkingDidUpdate(object)
}
}
}
}
}
makeRequestToApi(createProductObject, url: Urls.menu)
EDIT: This compiles for me (you probably have to adjust your delegate method):
import Foundation
struct Product {}
protocol Delegate : class {
func networkingDidUpdate<T>(obj: [T])
}
class Test {
weak var delegate : Delegate?
func makeRequestToApi<T>(create: JSON -> [T], url: String) {
Alamofire.request(.GET, url).responseJSON { request in
guard let json = request.result.value else { return }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let object = create(JSON(json))
dispatch_async(dispatch_get_main_queue()) {
self.delegate?.networkingDidUpdate(object)
}
}
}
}
func createProductObject(json: JSON) -> [Product] {
return [Product()]
}
}
let test = Test()
test.makeRequestToApi(test.createProductObject, url: "")

Related

Generic delegate response handlers

I've got a class currently something like this
class Client {
var responseOneDelegate: ResponseOneDelegate?
var responseTwoDelegate: ResponseTwoDelegate?
...
func onData(forMessageType messageType: MessageType, data: Data) {
switch messageType {
case .responseOne:
let response = JSONDecoder().decode(Response<ResultForResponseOne>.self, from: data)
responseOneDelegate?.received(response: response)
case .responseTwo:
let response = JSONDecoder().decode(Response<ResultForResponseTwo>.self, from: data)
responseTwoDelegate?.received(response: response)
}
}
}
protocol ResponseOneDelegate {
func received(response: Response<ResultForResponseOne>)
}
protocol ResponseTwoDelegate {
func received(response: Response<ResultForResponseTwo>)
}
With the idea that a class can be one or multiple delegates
class Handler: ResponseOneDelegate, ResponseTwoDelegate {
func received(response: Response<ResultForResponseOne>) { }
func received(response: Response<ResultForResponseTwo>) { }
}
This seems to be screaming out to be generalised as there will be quite a lot of responses in this format, but I can't quite figure out how to do it
I've tried using a generic type to make just a single delegate
protocol ResponseDelegate: AnyObject {
associatedtype T
func received(response: Response<T>)
}
It doesn't seem possible to store the delegates in Client in [MessageType: ResponseDelegate] so with the idea of the generic delegate I'm not sure how I'd store the references of the delegates? Maybe I'd just have to cast them before calling?
How would you generalise this?
Functions may be helpful here, in cases where you have a protocol with just a single function in it, and few types that implement the protocol.
Here's an example of the idea:
class Client {
var handle: ((Data) -> Bool)
init(handle: #escaping ((Data) -> Bool)) {
self.handle = handle
}
func received(data: Data) {
handle(data)
}
}
let ints = { (data: Data) -> Bool in
guard let i = try? JSONDecoder().decode(Int.self, from: data) else {
return false
}
print("handle \(i)")
return true // if handled
}
let strings = { (data: Data) -> Bool in
guard let str = try? JSONDecoder().decode(String.self, from: data) else {
return false
}
// handle Strings
print("handle \(str)")
return true
}
let intOrString = { (data: Data) -> Bool in
ints(data) ||
strings(data)
}
func handleMany(handlers: ((Data) -> Bool)...) -> (Data) -> Bool {
return { data in
for handle in handlers {
if handle(data) {
return true
}
}
return false
}
}
let intsOrStrings = handleMany(handlers: ints, strings)
let aOrBClient = Client(handle: intsOrStrings)
let aClient = Client(handle: ints)

Passing server response to ViewModel MVVM

I have been trying to pass the response of my AF.request statement to my viewModel but am unable to understand still? I need to pass my response to my ViewModel and then use it to display in the tableView.
This is my Service Class:
Service
class Service {
fileprivate var baseUrl = ""
//https://api.themoviedb.org/3/tv/76479?api_key=3d0cda4466f269e793e9283f6ce0b75e&language=en-US
init(baseUrl: String) {
self.baseUrl = baseUrl
}
var tvShowDetails = TVShowModel()
func getTVShowDeet(completionHandler: #escaping ()-> TVShowModel){
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else {return}
return tvShow
print("printing response", tvShow)
}
}
}
ViewModel
func getTVShowDetails(){
service.getTVShowDeet{
print(self.response)
self.delegate?.reloadTable()
self.headerDelegate?.configureHeader()
print("prinitn respinse in VM", self.response)
}
}
Model
struct TVShowModel : Decodable {
let id : Int?
let original_name : String?
let overview : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case original_name = "original_name"
case overview = "overview"
}
init(){
id = nil
original_name = nil
overview = nil
}
}
Networking requests are asynchronous meaning we don't know when they'll complete so we need to use completion handlers instead of returning from the function (unless you use Async/Await). Something along the lines of this should work:
Service
func getTVShowDeet(completionHandler: #escaping (TVShowModel) -> Void) {
let request = AF.request(self.baseUrl)
.validate()
.responseDecodable(of: TVShowModel.self) { (response) in
guard let tvShow = response.value else { return }
completionHandler(tvShow)
}
}
ViewModel
func getTVShowDetails() {
service.getTVShowDeet { [weak self] tvShow in
// Here you may need to store the tvShow object somewhere to use in your tableView datasource.
self?.delegate?.reloadTable()
self?.headerDelegate?.configureHeader()
}
}

Swift Realm Results Convert to Model

Is there any simple way to map a Realm request to a Swift Model (struct) when it is just a single row?
When it is an array of data I can do something like this and work with the array. This is not working on a single row.
func toArray<T>(ofType: T.Type) -> [T] {
return compactMap { $0 as? T }
}
But what is best to do when just a single row of data?
my databases are big so doing it manually is just a pain and ugly.
It would also be nice when the Swift Model is not 100% the same as the Realm Model. Say one has 30 elements and the other only 20. Just match up the required data.
Thank you.
On my apps I m using this class to do all actions. I hope that's a solution for your situation. There is main actions for realm.
Usage
class Test: Object {
var name: String?
}
class ExampleViewController: UIViewController {
private let realm = CoreRealm()
override func viewDidLoad() {
super.viewDidLoad()
let data = realm.getArray(selectedType: Test.self)
}
import RealmSwift
class CoreRealm {
// Usage Example:
// let testObject = RealmExampleModel(value: ["age":1 , "name":"Name"])
// let testSubObject = TestObject(value: ["name": "FerhanSub", "surname": "AkkanSub"])
// testObject.obje.append(testSubObject)
let realm = try! Realm()
func deleteDatabase() {
try! realm.write {
realm.deleteAll()
}
}
func delete<T: Object>(selectedType: T.Type) {
try! realm.write {
let object = realm.objects(selectedType)
realm.delete(object)
}
}
func delete<T: Object>(selectedType: T.Type, index: Int) {
try! realm.write {
let object = realm.objects(selectedType)
realm.delete(object[index])
}
}
func add<T: Object>(_ selectedObject: T) {
do {
try realm.write {
realm.add(selectedObject)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
// return Diretly object
func getArray<T: Object>(selectedType: T.Type) -> [T]{
let object = realm.objects(selectedType)
var array = [T]()
for data in object {
array.append(data)
}
return array
}
func getObject<T: Object>(selectedType: T.Type, index: Int) -> T{
let object = realm.objects(selectedType)
var array = [T]()
for data in object {
array.append(data)
}
return array[index]
}
// return Result tyle
func getResults<T: Object>(selectedType: T.Type) -> Results<T> {
return realm.objects(selectedType)
}
func getResult<T: Object>(selectedType: T.Type) -> T? {
return realm.objects(selectedType).first
}
func createJsonToDB<T: Object>(jsonData data: Data, formatType: T.Type) {
// let data = "{\"name\": \"San Francisco\", \"cityId\": 123}".data(using: .utf8)!
let realm = try! Realm()
try! realm.write {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
realm.create(formatType, value: json, update: .modified)
} catch {
print("Json parsing error line 65")
}
}
}
}

How to overcome the error of "Generic parameter 'T' is not used in function signature"?

I'm trying to convert the following to be generic.
extension RLMOrganization: DataProvider {
func getLastSyncToken() -> String {
let lastUpdated: RLMOrganization? = self.findAll(sortedBy: "syncToken").last
if let syncToken = lastUpdated?.syncToken {
return syncToken
} else {
return "00000000000000000000000000000000"
}
}
}
And have tried this:
protocol DataProvider: DatabaseLayer {
associatedtype T: Object
func findAll<T: Object>(sortedBy key: String) -> [T]
}
extension DataProvider {
func findAll<T: Object>(sortedBy key: String) -> [T] {
let database = self.getDatabase()
if let allObjects = database?.objects(T.self) {
let results = allObjects.sorted(byKeyPath: key, ascending: true)
return Array(results)
}
return []
}
func getLastSyncToken<T: Object>() -> String {
let lastUpdated = self.findAll(sortedBy: "syncToken").last as? T
if let value = lastUpdated?.value(forKey: "syncToken") { // get value from object by string name
let syncToken = value as! String
return syncToken
} else {
return "00000000000000000000000000000000"
}
}
...
But can't seem to overcome the error of:
Generic parameter 'T' is not used in function signature
I would think the compiler has everything it needs to determine type usage.
Below works for me, I don't know how findAll is defined but the problem is the reference to self as I see it so you need to define T there using associatedtype.
protocol DataProvider: DatabaseLayer {
associatedtype T: Object
func findAll(sortedBy: String) -> T?
}

Hold reference to downloaded DynamoDB data

I have a class holding a DynamoDB model (I cut the # of variables for brevity, but they're all Optional Strings:
import AWSCore
import AWSDynamoDB
#objcMembers class Article: AWSDynamoDBObjectModel, AWSDynamoDBModeling {
var _articleSource: String?
class func dynamoDBTableName() -> String {
return "article"
}
class func hashKeyAttribute() -> String {
return "_articleId"
}
class func rangeKeyAttribute() -> String {
return "_articleUrl"
}
override class func jsonKeyPathsByPropertyKey() -> [AnyHashable: Any] {
return [
"_articleSource" : "articles.articleSource",
]
}
}
In my View Controller, I'm downloading data from the table and storing each article in an array like this:
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
var allArticles = [AnyObject]()
func getArticles(completed: #escaping DownloadComplete) {
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 50
self.dynamoDbObjectMapper.scan(Article.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
} else if let paginatedOutput = task.result {
for article in paginatedOutput.items as! [Article] {
self.allArticles.append(article)
}
}
return(self.allArticles)
})
completed()
}
When I try to work with the data that should be stored in allArticles the array is empty. However, the array holds articles when I break execution in the download block where articles are being appended. How can I hold reference to the downloaded data? My use of a completion block was my attempt.
Edit: allArticles is of type [AnyObject] because I'm attempting to store objects from 3 different classes total in the same array to make it easier to work with in a TableView
The array wasn't empty after all, I just didn't realize this was all async (duh...)
I just needed:
DispatchQueue.main.async {
self.tableView.reloadData()
}
in place of completed() in the getArticles() func