How to make my ReferenceWritableKeyPath class generic - ios

I am using ReferenceWritableKeyPath to modify properties in a model class. This works fine, however, I now want to make my code a bit more generic so that I can modify properties from different classes.
Here is my current code:
final class MyListener {
private let reference: String
private var listener: DatabaseHandle?
private let backendClient: BackendClient
private weak var model: MyModel?
func listenToAndUpdate<T>(_ property: ReferenceWritableKeyPath<MyModel, T?>) {
guard listener == nil else {
return
}
listener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<T, RequestError>) in
switch result {
case .success(let value):
self?.model?[keyPath: property] = value
case .failure:
self?.model?[keyPath: property] = nil
}
}
}
}
I essentially need to have something generic in the model property, but also in the Root of the ReferenceWritableKeyPath. How can I do this? I tried using generics on the class but it wouldn't allow me to have a weak reference to a generic property.

You were on the right track by making the MyListener class generic, but you also need to put a type constraint on the generic type to ensure that it can only be a class, which can have a weak reference. All classes conform to AnyObject, so it is a sufficient type constraint for your case.
final class MyListener<Model: AnyObject> {
private let reference: String
private var listener: DatabaseHandle?
private let backendClient: BackendClient
private weak var model: Model?
func listenToAndUpdate<T>(_ property: ReferenceWritableKeyPath<Model, T?>) {
guard listener == nil else {
return
}
listener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<T, RequestError>) in
switch result {
case .success(let value):
self?.model?[keyPath: property] = value
case .failure:
self?.model?[keyPath: property] = nil
}
}
}
}

Related

SwiftUI ViewModel how to initialized empty single struct?

In my ViewModel I'm fetching data from API and I want to populate my variable with the data, however when I declare the variable, it returns error.
ViewModel.swift
class PromoDetailViewModel: ObservableObject, ModelService {
var apiSession: APIService
#Published var dataArray = [BannerDetailResData]() // This is okay
#Published var data = BannerDetailResData // This returns error
// Error message is:
// Expected member name or constructor call after type name
// Add arguments after the type to construct a value of the type
// Use .self to reference the type object
init(apiSession: APIService = APISession()) {
self.apiSession = apiSession
}
func getPromoDetail() {
let cancellable = self.getBannerDetail(bannerID: bannerID)
.sink(receiveCompletion: { result in
switch result {
case .failure(let error):
print("Handle error: \(error)")
case .finished:
break
}
}) { (result) in
if result.statusCode == 200 {
self.data = result.data
}
self.isLoading = false
}
cancellables.insert(cancellable)
}
}
BannerDetailResData.swift
struct BannerDetailResData: Codable, Hashable {
let bannerId: String
let bannerImg: String
let startDate: String
let endDate: String
}
Why is it when I declare as BannerDetailResData, it works perfectly? What is the correct way of declaring this single struct object? Thank you in advance
Make it optional
#Published var data:BannerDetailResData?

Add a spinner on making a moya request using RxSwift and mvvm and dismiss it when user receives a response

I have an app where I am trying to implement RxSwift using MVVM.
I have the SignInViewModel where I am doing the validation and I am updating the login observable with the rest response boolean that I am listening to .
In the controller class when ever the validations pass the login button gets enabled.
In a similar manner I want to be able to start a spinner on click of the button and dismiss when the user receives a response.
When I try to listen to the loginObservable in from view model in the controller class. it does not hit the bind block.
I am not able to figure out what the problem is.
Any help will be appreciated
Following is my SignInViewModel
class SignInViewModel {
let validatedEmail: Observable<Bool>
let validatedPassword: Observable<Bool>
let loginEnabled: Observable<Bool>
let loginObservable: Observable<Bool>
init(username: Observable<String>,
password: Observable<String>,
loginTap: Observable<Void>) {
self.validatedEmail = username
.map { $0.characters.count >= 5 }
.shareReplay(1)
self.validatedPassword = password
.map { $0.characters.count >= 2 }
.shareReplay(1)
self.loginEnabled = Observable.combineLatest(validatedEmail, validatedPassword ) { $0 && $1 }
let userAndPassword = Observable.combineLatest(username, password) {($0,$1)}
self.loginObservable = loginTap.withLatestFrom(userAndPassword).flatMapLatest{ (username, password) in
return RestService.login(username: username, password: password).observeOn(MainScheduler.instance)
}
}
}
Following is the moyaRequest class
final class MoyaRequest{
func signIn(userData: Creator) -> Observable<Response> {
return provider.request(.signIn(userData))
.filter(statusCode: 200)
}
}
Following is my RestService class
class RestService:NSObject {
static var moyaRequest = MoyaRequest()
static var disposeBag = DisposeBag()
static func login(username: String, password: String) -> Observable<Bool> {
let userData = Creator()
userData?.username = username
userData?.password = password
print("Username password", userData?.username, userData?.password)
return Observable.create { observer in moyaRequest.signIn(userData: userData!).subscribe{ event -> Void in
switch event {
case .next(let response):
print("Response",response)
case .error(let error):
let moyaError: MoyaError? = error as? MoyaError
let response: Response? = moyaError?.response
let statusCode: Int? = response?.statusCode
print("Sample Response code error" + String(describing: statusCode))
default:
break
}
}
return Disposables.create()
}
}
}
I am trying to bind the view model in the controller class.
class SignInViewController: UIViewController{
let disposeBag = DisposeBag()
#IBOutlet weak var passwordTextfield: UITextField!
#IBOutlet weak var usernameTextfield: UITextField!
private var viewModel : SignInViewModel!
#IBOutlet weak var signInButton: UIButton!
override func viewDidLoad() {
setUpRxViewModel()
}
func setUpRxViewModel(){
self.viewModel = SignInViewModel(username: self.usernameTextfield.rx.text.orEmpty.asObservable(),
password: self.passwordTextfield.rx.text.orEmpty.asObservable(),
loginTap: self.signInButton.rx.tap.asObservable())
self.viewModel.loginEnabled.bind{ valid in
self.signInButton.isEnabled = valid
}.addDisposableTo(disposeBag)
self.viewModel.loginObservable.bind{ input in
print("Login Clicked")
}.addDisposableTo(disposeBag)
}
}
In your login method you are not dispatching any events to your observer. It should be:
case .next(let response):
observer.on(.next(true))
print("Response",response)
case .error(let error):
observer.on(.error(error))
//or observer.on(.next(false)) if you intend to use Bool as indicator of operation success which is a very bad idea.
let moyaError: MoyaError? = error as? MoyaError
let response: Response? = moyaError?.response
let statusCode: Int? = response?.statusCode
furthermore I recommend you use RxMoyaProvider everywhere if you are using Moya with RxSwift. Using Observable.create usually means you are doing something wrong.
You also shouldn't filter off events based on status code at the level of network request because if something goes wrong you are not going to receive any event in your chain.

"Generic parameter 'Value' could not be inferred" for `observe`

I have a class that is generic, like:
class Row<T> {
...
}
If I have an instance of Row where T is ExampleClass, I want to be able to do:
row.bind(to: \ExampleClass.category)
Then in bind I want to start observing ExampleClass.category for my ExampleClass instance that I have in the class.
I've implemented:
func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<T, Value>) {
if let model = model as? NSObject {
model.observe(targetKeyPath, options: [.new ,.old], changeHandler: { _, change in
Log.info?.msg("Now we have some stuff: \(change)")
})
}
}
This gives me the error: Generic parameter 'Value' could not be inferred.
How is that possible? The T is solved but why can't Value be inferred? It should come from the parameter targetKeyPath.
Full code for repoducable:
class Row<T> {
let model: T
init(model: T) {
self.model = model
}
func bind<Value>(to targetKeyPath: ReferenceWritableKeyPath<T, Value>) {
if let model = model as? NSObject {
model.observe(targetKeyPath, options: [], changeHandler: { _, _ in
})
}
}
}
How I'd like to use the example class above:
class Person: NSObject {
#objc var age: NSNumber?
}
class Book: NSObject {
#objc var title: String?
}
let row1 = Row(model: Person())
let row2 = Row(model: Book())
row1.bind(to: \Person.age)
row2.bind(to: \Book.title)
You're probably over-thinking this. The problem is that model is known only to be an NSObject. You can reproduce simply like this:
class ExampleClass:NSObject {
#objc dynamic var category = ""
}
let model = NSObject()
model.observe(\ExampleClass.category) { _,_ in }
Same error. If you change model to be an ExampleClass, the problem goes away.

RxSwift - subscribe to a method

Is there a way with RxSwift to subscribe to a method which returns a completion block?
Example, let's have this object:
struct Service {
private var otherService = ...
private var initSucceeded = PublishSubject<Bool>()
var initSucceededObservale: Observable<Bool> {
return initSucceeded.asObservable()
}
func init() {
otherService.init {(success) in
self.initSucceeded.onNext( success)
}
}
}
And in a different place have a way to be notified when the service has been initialised:
service.initSucceededObservable.subscribe(onNext: {
[unowned self] (value) in
...
}).addDisposableTo(disposeBag)
service.init()
Would be there a simpler solution?
I like to use Variables for this sort of thing. Also, I'd recommend using class here because you're tracking unique states and not just concerning yourself with values.
class Service {
private let bag = DisposeBag()
public var otherService: Service?
private var isInitializedVariable = Variable<Bool>(false)
public var isInitialized: Observable<Bool> {
return isInitializedVariable.asObservable()
}
public init(andRelyOn service: Service? = nil) {
otherService = service
otherService?.isInitialized
.subscribe(onNext: { [unowned self] value in
self.isInitializedVariable.value = value
})
.addDisposableTo(bag)
}
public func initialize() {
isInitializedVariable.value = true
}
}
var otherService = Service()
var dependentService = Service(andRelyOn: otherService)
dependentService.isInitialized
.debug()
.subscribe({
print($0)
})
otherService.initialize() // "Initializes" the first service, causing the second to set it's variable to true.
You could use a lazy property:
lazy let initSucceededObservale: Observable<Bool> = {
return Observable.create { observer in
self.otherService.init {(success) in
observer.on(.next(success))
observer.on(.completed)
}
return Disposables.create()
}
}()
and then you can use:
service.init()
service.initSucceededObservable.subscribe(onNext: {
[unowned self] (value) in
...
}).addDisposableTo(disposeBag)
Let me know in the comments if you have problems before downvoting, thanks.

Generic constraint for any protocol in Swift

Is it possible to constrain generic type to accept protocol in Swift?
I have implemented wrapper to have weak list of objects, and I need to extend that to protocols.
protocol Incrementable: class {
func inc()
}
class Counter: Incrementable {
var n: Int = 0
func inc() {
n += 1
}
}
struct Weak<T: AnyObject> {
weak var value : T?
init (value: T?)
{
self.value = value
}
}
var cnt: Counter? = Counter()
let counters : [Weak<Counter>] = [Weak(value: cnt), Weak(value: Counter())]
for counter in counters
{
counter.value?.inc()
}
Now if I want to store any object that implements Incrementable I have to use AnyObject and that is not very type safe and also includes as? casting
let counters : [Weak<AnyObject>] = [Weak(value: cnt), Weak(value: Counter())]
for counter in counters
{
(counter.value as? Incrementable)?.inc()
}
And code I would like to have would be
let counters: [Weak<Incrementable>] = [Weak(value: cnt), Weak(value: Counter())]
for counter in counters
{
counter.value?.inc()
}
Of course, above code cannot be compiled and fails with:
Using 'Incrementable' as concrete type conforming to protocol
'AnyObject' is not supported
Is it possible to write Weak wrapper so it can accept and store weak references to protocol?
While root cause of my problem is same as in Using as a concrete type conforming to protocol AnyObject is not supported that question deals with hash tables and I need solution with lists that allows duplicate entries.
Following answer pointed me in right direction and I was able to come up with following solution for implementing weak list of protocol references that allows duplicates and nil (convenience) entries.
struct Weak<T>
{
weak var value: AnyObject?
init (value: T?)
{
if value != nil
{
guard value is AnyObject else { fatalError("Object (\(value)) should be subclass of AnyObject") }
self.value = value as? AnyObject
}
}
}
class WeakList<T>: SequenceType
{
var items : [Weak<T>] = []
func add(item: T?)
{
items.append(Weak(value: item))
}
func generate() -> AnyGenerator<T>
{
var nextIndex = items.count - 1
return anyGenerator
{
while nextIndex >= 0
{
let item = self.items[nextIndex--]
if item.value != nil
{
return item.value as? T
}
}
return nil
}
}
}
let incrementables = WeakList<Incrementable>()
incrementables.add(Counter())
incrementables.add(cnt)
incrementables.add(nil)
incrementables.add(Counter())
incrementables.add(cnt)
for counter in incrementables
{
counter.inc()
}

Resources