Generic delegate response handlers - swift

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)

Related

Observe generic values with Combine

Take this case of a type constrained class Parameter, wrapping a value of given type.
Parameter conforms to the AnyParameter so it can be passed anywhere in the app without knowing the type. Parameters can be displayed in value cells AnyValueCell
How would you do to observe the change without having to know the underlying value type? It would be nice to avoid the code repetition in the value cell updateObserver function
Could AnyPublisher can be used here and how?
import UIKit
import Combine
print("Hello Playground")
protocol AnyParameter {
var anyValue: Any { get }
func set(value: Any)
}
protocol ParameterProtocol: AnyParameter {
associatedtype ValueType
var value: ValueType { get }
func set(value: ValueType)
}
public class Parameter<T>: ParameterProtocol {
typealias ValueType = T
#Published var value: T
var anyValue: Any { value }
init(value: T) {
self.value = value
}
func set(value: Any) {
guard let value = value as? T else { return }
set(value: value)
}
func set(value: T) {
self.value = value
}
}
public class AnyValueCell {
var parameter: AnyParameter {
didSet {
updateObserver()
}
}
var observer: AnyCancellable?
init(parameter: AnyParameter) {
self.parameter = parameter
updateObserver()
}
func updateObserver() {
observer?.cancel()
// This is the point of the question - How to make this generic?
// ---->
if let p = parameter as? Parameter<Int> {
observer = p.$value.sink() { value in
print("Update Cell -> \(value)")
}
return
}
if let p = parameter as? Parameter<Double> {
observer = p.$value.sink() { value in
print("Update Cell -> \(value)")
}
return
}
if let p = parameter as? Parameter<Bool> {
observer = p.$value.sink() { value in
print("Update Cell -> \(value)")
}
return
}
// <----
print("Wrong param type")
}
}
let intParam = Parameter<Int>(value: 42)
let doubleParam = Parameter<Double>(value: 3.14)
let boolParam = Parameter<Bool>(value: false)
var params: [AnyParameter] = [intParam, doubleParam, boolParam]
print ("--> Init Cells")
let cells: [AnyValueCell] = params.map { AnyValueCell(parameter: $0) }
print ("--> Change values")
intParam.set(value: 21)
doubleParam.set(value: 1.618)
boolParam.set(value: true)
Result, as expected:
Hello Playground
--> Init Cells
Update Cell -> 42
Update Cell -> 3.14
Update Cell -> false
--> Change values
Update Cell -> 21
Update Cell -> 1.618
Update Cell -> true
Add an anyValuePublisher property. You can (and maybe should) add it to AnyParameter, or you can define it in a separate protocol like this:
protocol AnyParameterPublishing: AnyParameter {
var anyValuePublisher: AnyPublisher<Any, Never> { get }
}
extension Parameter: AnyParameterPublishing {
var anyValuePublisher: AnyPublisher<Any, Never> {
return $value.map { $0 as Any }.eraseToAnyPublisher()
}
}
Then you can use it like this:
class AnyValueCell {
// ...
func updateObserver() {
guard let publishing = (parameter as? AnyParameterPublishing) else {
print("Wrong param type")
return
}
observer = publishing.anyValuePublisher
.sink { print("Update Cell -> \($0)") }
}
}

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?
}

Generic types error : Cannot explicitly specialise a generic type

I'm trying to create a generic function in RequestManager that convert the received JSON from server to the specified type through the ServiceManager. This is my code:
RequestManager:
typealias ResultResponseManager = (_ data: AnyObject?, _ error: ErrorPortage?) -> Void
typealias SuccessResponseManager = (_ success: Bool, _ error: ErrorPortage?) -> Void
typealias objectBlock<T:GenericModel> = (_ object: T?, _ error: ErrorPortage?) -> Void
extension RequestManager {
static func getObject<T: GenericModel>(endpoint: String, completionBlock: objectBlock<T>? = nil){
RequestHelper(url: "\(getAPIURL())\(endpoint))")
.performJSONLaunchRequest { (result, error) in
if let result = result as? NSDictionary,
error == nil {
let object = T(dic: result)
completionBlock?(object, nil)
}
else {
completionBlock?(nil, error)
}
}
}
}
ServiceManger:
typealias ObjectResult = (GenericModel?, ErrorPortage?) -> Void
typealias ObjectsResult = ([GenericModel]?, ErrorPortage?) -> Void
extension ServiceManager {
static func getUser(_ id: Int? = nil, _ completion: ObjectResult? = nil) {
guard let userId: Int = id ?? UserManager.shared.userId else {
return
}
RequestManager.getObject<User>(endpoint: "users/\(userId)") { (user, error) in
if user = user {
//update userdefault
if userId == UserManager.shared.userId {
UserDefaults.standard.set(result, forKey: "currentUser")
}
}
}
}
}
On the line RequestManager.getObject<User> ... I'm getting this error:
Cannot explicitly specialise a generic type
So what did I miss here?
The problem was solved thanks to luk2302
Update
Any idea how to improve this code maybe or make it more clean!
NB: This is not an issue it's just about good programming habits
Compare https://stackoverflow.com/a/35372990/2442804 - you are not allowed to specify the type constraint "by hand". You have to make the compiler infer it. Do that via:
RequestManager.getObject(endpoint: "users/\(userId)") { (user : User?, error) in
// ... your normal code

Cast object to generic

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: "")