Instance method 'drive' requires the types 'NotificationItem' and '[NotificationItem]' be equivalent - swift

I have create a class called notification Item and parsing the data from model class RTVNotification
import Foundation
import RTVModel
public class NotificationItem: NSObject {
public var id: String
public var title: String
public var comment: String
public var publishStartDateString: String
init(id: String,
title: String,
comment: String,
publishStartDateString: String) {
self.id = id
self.title = title
self.comment = comment
self.publishStartDateString = publishStartDateString
super.init()
}
}
extension NotificationItem {
static func instantiate(with notification: RTVNotification) -> NotificationItem? {
return NotificationItem(
id: notification.id,
title: notification.title,
comment: notification.comment,
publishStartDateString: notification.publishStartDateString)
}
}
ViewModel
public class SettingsViewModel: ViewModel {
var item = [NotificationItem]()
public var fetchedNotifications: Driver<NotificationItem> = .empty()
public var apiErrorEvents: Driver<RTVAPIError> = .empty()
public var notificationCount: Driver<Int> = .empty()
public func bindNotificationEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVInformationListWebService> = trigger
.map { RTVInformationListParameters() }
.webService()
let result = webService.request()
apiErrorEvents = Driver.merge(apiErrorEvents, result.error())
notificationCount = result.success().map {$0.informationList.maxCount }
fetchedNotifications =
result.success()
.map {$0.informationList.notifications}
-----> .map {NotificationItem.instantiate(with: $0)}
}
}
Getting an Error saying that Cannot convert value of type '[RTVNotification]' to expected argument type 'RTVNotification'
What can i do to solve this.

The purpose of the map() function is to iterate over the elements of an input array and apply a transform function to each of those elements. The transformed elements are added to a new output array that is returned by map(). It's important to understand that the length of the output array is the same length as the input array.
For example:
let inputArray = ["red", "white", "blue"]
let outputArray = inputArray.map { $0.count } // outputArray is [3, 5, 4]
In your code, you are calling:
result.success().map { $0.informationList.notifications }
I don't know RxSwift at all, so I'm going to go into wild speculation here.
First, I don't know exactly what result.success() returns, but the fact you can call map() on it implies result.success() returns an array (which is weird, but ok we'll go with it).
Second, we know the array returned by result.success() contains elements that have an informationList property, and the informationList property has a property called notifications. My guess is that notifications, being plural, means the notifications property type is an array, probably [RTVNotification].
So this code:
result.success().map { $0.informationList.notifications }
Transforms the success() array into a new array. Based on my assumption that notifications is of type [RTVNotification], and further assuming the success() array contains only one element, I would expect the result of
result.success().map { $0.informationList.notifications }
To be an array of type [[RTVNotification]], i.e. an array with one element, where that element is an array of RTVNotifications.
You then feed that [[RTVNotification]] array into another map() function:
.map { NotificationItem.instantiate(with: $0) }
Recall from the start of this answer that map() iterates over the elements of arrays. Since the input array to this map is [[RTVNotification]], its elements will be of type [RTVNotification]. That's what the $0 is in your call - [RTVNotification]. But the instantiate(with:) function takes an RTVNotification, not an array of RTVNotification, thus you get the error:
Cannot convert value of type '[RTVNotification]' to expected argument type 'RTVNotification'
So what can you do to fix it?
I would probably do something like this (you'll have to tailor it to your use case):
guard let successResponse = webService.request().success().first else {
print("no success response received")
return nil // basically report up an error here if not successful
}
// get the list of notifications, this will be type [RTVNotification]
let notifications = successResponse.informationList.notifications
// Now you can create an array of `[NotificationItem]` like you want:
let notificationItems = notifications.map { NotificationItem.instantiate(with: $0) }
// do something with notificationItems...
The caveat to the above is if you need to iterate over each element in the success() array, then you could do that like this:
let successResponses = webService.result().success()
// successNotifications is of type [[RTVNotification]]
let successNotifications = successResponses.map { $0.informationList.notifications }
// successItems will be of type [[NotificationItem]]
let successItems = successNotifications.map { notifications in
notifications.map { NotificationItem.instantiate(with: $0) }
}
In other words, in this last case, you get back an array that contains arrays of NotificationItem.

Your problem is here:
fetchedNotifications: Driver<NotificationItem> should be fetchedNotifications: Driver<[NotificationItem]> and the line .map {NotificationItem.instantiate(with: $0)} needs another map You are dealing with an Observable<Array<RTVNotification>>. You have a container type within a container type, so you need a map within a map:
.map { $0.map { NotificationItem.instantiate(with: $0) } }
When your types don't match, you need to change the types.
Other issues with your code...
Drivers, Observables, Subjects and Relays should never be defined with var, they should always be lets. Objects that subscribe to your properties before the bind is called will connect to the .empty() observables and never get any values. This is functional reactive programming, after all.
Your NotificationItem type should either be a struct or all it's properties should be `let's.
Be sure to read and understand #par's answer to this question. He wrote a really good explanation and it would be a shame to waste that knowledge transfer.

Related

Why can't Swift infer this return type properly?

I'm trying to do something I think should be pretty simple, but I'm running into trouble with Swift's type inference. I really don't understand why it's falling down here.
I have a type Cocktail, which has other properties, but the only one important here is the name:
struct Cocktail {
// ... other stuff
let name: String
}
Then I have two protocols:
protocol ScrollIndexable {
var scrollIndexTitle: String { get }
}
protocol ScrollIndexProviding {
var scrollIndices: [any ScrollIndexable] { get }
}
along with a simple conformance on String to ScrollIndexable:
extension String: ScrollIndexable {
var scrollIndexTitle: String { self }
}
I want to make it so that I can use an array of Cocktails as a ScrollIndexProviding:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
// The return line here has two errors:
// Cannot convert return expression of type 'Array<Cocktail>' to return type '[any ScrollIndexable]'
// No exact matches in call to initializer
return Array(firstCharacters)
}
}
This extension fails to build, with two errors:
Cannot convert return expression of type 'Array' to return type '[any ScrollIndexable]'
No exact matches in call to initializer
The second error seems like noise to me, since Set conforms to Sequence, so I should be able to use that init method.
The first error is confusing to me since the firstCharacters array is of type Set<String>, so the error message just doesn't seem to make any sense. Is there something I'm misunderstanding about the any keyword here? What's going on?
The issue is that you're inside an extension of Array where the Element is Cocktail, so when you try to create an array without specifying the element type the compiler will assume you mean for the element type to be Cocktail.
extension Array where Element: Cocktail {
func someMethod() {
// This array is of type `Array<Cocktail>` since the compiler
// assumes the array's element type should be the same as
// Self's element type, which (from the extension) is `Cocktail`.
let array = Array()
}
}
So, to fix this, just explicitly tell the compiler that the array's element type is String, as in:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
return Array<String>(firstCharacters)
// ^^^^^^^^ add this
}
}

How can I map to a type in Combine?

I have numerous pages where users may input information. They may input the fields with dates, numbers, or text.
I am trying to receive all changes in Combine and get their outputs as Encodable so I can easily upload the results to the network.
A String is Encodable, so I thought this would be easy but I cannot get this to work in Combine. I get a compiler error:
Cannot convert return expression of type 'Publishers.Map<Published.Publisher, Encodable>' to return type 'Published.Publisher'
There is a workaround where I add another property in SampleTextHandler that is #Published var userTextEncodable: Encodable but that's not what I want to do.
import Combine
protocol FieldResponseModifying {
var id: String { get }
var output: Published<Encodable>.Publisher { get }
}
struct SampleTextWrapper {
var output: Published<Encodable>.Publisher {
// Cannot convert return expression of type 'Publishers.Map<Published<String>.Publisher, Encodable>' to return type 'Published<Encodable>.Publisher'
handler.$userTextOutput.map { $0 as Encodable}
}
let id = UUID().uuidString
let handler = SampleTextHandler()
}
class SampleTextHandler {
#Published var userTextOutput = ""
init () { }
}
Combine uses generics heavily. For example, the type returned by your use of map is Publishers.Map<Published<Value>.Publisher, Encodable>. So you could declare your property like this:
var output: Publishers.Map<Published<Encodable>.Publisher, Encodable> {
handler.$userTextOutput.map { $0 as Encodable}
}
But now your property's type depends closely on how it is implemented. If you change the implementation, you'll have to change the type.
Instead, you should almost certainly use the “type eraser” AnyPublisher, like this:
var output: AnyPublisher<Encodable, Never> {
handler.$userTextOutput
.map { $0 as Encodable }
.eraseToAnyPublisher()
}
You're probably going to run into another issue down the line, due to your use of the Encodable existential. When you hit that, you'll want to post another question.

How should I unwrap a member variable that's optional but most likely set?

I have a class that runs a function inside the init/constructor that loops over an array of...
struct Answer {
let correct: Bool
let text: String
}
...and searches for an object that has the correct boolean variable set to true. The loop and search is done via a function named getCorrectId() that returns a index to where the Answer with the correct: true is placed.
The function is implemented like this:
private func getCorrectId() -> Int? {
for i in 0..<self.answers.count {
if self.answers[i].correct {
return i
}
}
return nil
}
As you can see, the function returns an optional integer. In 99.9% of the cases it should not fail, and if it fails it is because I've made an logical programming error by myself somewhere in the code.
SolutionHandler class, including init() and getCorrectId() function
struct Answer {
let correct: Bool
let text: String
}
class SolutionHandler {
var correctId: Int?
var answers = [Answer]()
init(correctSolution: String, incorrectSolutions: [String]) {
// Add the correct solution to the answers array
self.answers.append(Answer(correct: true, text: correctSolution))
// Add the incorrect solutions to the answers array
for incorrectSolution in incorrectSolutions {
self.answers.append(Answer(correct: false, text: incorrectSolution))
}
// Shuffle the answers to make them random in array
self.answers.shuffle()
<-- IMPORTANT PART
// Find the correct id (with forced unwrap)
self.correctId = getCorrectId()!
-->
}
private func getCorrectId() -> Int? {
for i in 0..<self.answers.count {
if self.answers[i].correct {
return i
}
}
return nil
}
}
So, to my main question... how should I unwrap this optional? Is it okay to force unwrap it or should I make the class SolutionHandler be set to nil if getCorrectId() function fails and handle it whenever I create the class? Or should I maybe throw an custom error and catch it whenever I create this class?
Thanks in advance! :)
You already know which one is correct; it's always the first one, as guaranteed by the way you constructed the answers array.
I think a better solution to the problem would be to not mix together all answers into a single array. Something like this:
class SolutionHandler { // FIXME: this is probably a bad name for this, and it should probably be a struct.
let correctAnswer: Answer
let incorrectAnswers: [Answer]
var answers: [Answer] { incorrectAnswers + [correctAnswer] }
init(correctSolution: String, incorrectSolutions: [String]) {
self.correctAnswer = Answer(correct: true, text: correctSolution)
self.incorrectAnswers = incorrectSolutions.map { Answer(correct: false, text: $0) }
}
}
I would do something like this:
if result = getCorrectId() {
self.correctId = result
}
Only assign the result of getCorrectID() if it is not nil.

Swift: How to assign a variable by reference, not by value?

I'm trying to get a reference to an Array and make modifications to it. Because Arrays in Swift are value types, instead of reference types, if I assign my array to a variable first, I am getting a copy of the array instead of the actual array:
var odds = ["1", "3", "5"]
var evens = ["2", "4", "6"]
var source = odds
var destination = evens
var one = odds.first!
source.removeFirst() // only removes the first element of the `source` array, not the `odds` array
destination.append(one)
When we look at the odds and evens arrays, they are unaltered because we changed the source and destination arrays.
I know that I can use the inout parameter attribute on a function to pass them by reference, instead of by value:
func move(inout source: [String], inout destination: [String], value:String) {
source.removeAtIndex(source.indexOf(value)!)
destination.append(value)
}
move(&odds, destination: &evens, value:one)
Is there a way to assign these arrays to a variable by reference, instead of by value?
Array is a struct, which means it's a value type in Swift. Because of this, arrays always behave according to value and not reference semantics. The problem here is that you're attempting to use mutable, reference based logic to operate on values types.
You don't want to rely on mutations occurring inside the function to propagate back to the caller. As you've found, this is only possible with inout parameters. What you should do instead is return the mutated array from the function back to the caller. The point of value oriented programming is that it shouldn't matter which array you have, but rather that any two equivalent arrays or values types are interchangeable.
It's slightly easier to imagine with another value type. Take an Int for example, and this function that does some math.
func addFive(int: Int) -> Int {
return int + 5
}
Now consider a similar function, but written in the reference oriented style that you're attempting to use:
func addFive(inout int: Int) {
int = int + 5
}
You can see it's simply not natural to operate on value types this way. Instead just return the updated value (the modified arrays) from your function and carry on from there.
Here is your function refactored with value semantics.
func move(source: [String], destination: [String], value:String) -> ([String], [String]) {
var mutableSource = source
var mutableDestination = destination
mutableSource.removeAtIndex(source.indexOf(value)!)
mutableDestination.append(value)
return (mutableSource, mutableDestination)
}
let (updatedSource, updatedDestination) = move(odds, destination: evens, value:one)
You cannot assign an array to a variable by reference in Swift.
"In Swift, Array, String, and Dictionary are all value types..."
Source: https://developer.apple.com/swift/blog/?id=10
If you need arrays that can be manipulated by reference you can create a class that encapsulates an array and use it for your variables.
here's an example:
class ArrayRef<Element>:CustomStringConvertible
{
var array:[Element]=[]
init() {}
init(Type:Element.Type) {}
init(fromArray:[Element]) { array = fromArray }
init(_ values:Element ...) { array = values }
var count:Int { return array.count }
// allow use of subscripts to manipulate elements
subscript (index:Int) -> Element
{
get { return array[index] }
set { array[index] = newValue }
}
// allow short syntax to access array content
// example: myArrayRef[].map({ $0 + "*" })
subscript () -> [Element]
{
get { return array }
set { array = newValue }
}
// allow printing the array example: print(myArrayRef)
var description:String { return "\(array)" }
// delegate append method to internal array
func append(newElement: Element)
{ array.append(newElement) }
// add more delegation to array methods as needed ...
}
// being an object, the ArrayRef class is always passed as a reference
func modifyArray(X:ArrayRef<String>)
{
X[2] = "Modified"
}
var a = ArrayRef("A","B","C")
modifyArray(a)
print(a) // --> a is now ["A", "B", "Modified"]
// various means of declaration ...
var b = ArrayRef<String>()
b[] = ["1","2","3"]
var c = ArrayRef(fromArray:b[])
// use .array to modify array content (or implement delegation in the class)
c.array += a[] + ["X","Y","Z"]
Note that you could also define your arrays as NSMutableArrays which are Obj-C classes and are passed by reference. It does a similar thing and does present differences with a regular array for the methods that are available.
I recommend this the following only for didactic purpose only, I advise against using it in production code.
You can circulate a "reference" to something via an UnsafePointer instance.
class Ref<T> {
private var ptr: UnsafePointer<T>!
var value: T { return ptr.pointee }
init(_ value: inout T) {
withUnsafePointer(to: &value) { ptr = $0 }
}
}
var a = ["1"]
var ref = Ref(&a)
print(a, ref.value) // ["1"] ["1"]
a.append("2")
print(a, ref.value) // ["1", "2"] ["1", "2"]
ref.value.removeFirst()
print(a, ref.value) // ["2"] ["2"]
Thus, you can simulate a reference to a variable via the above class, which stores a pointer to the given variable reference.
Please note that this is a simple use case, and will behave as expected only if if the variable doesn't get destroyed before the pointer, as in that case the memory initially occupied by the variable will be replaced by something else, and the unsafe pointer will no longer be valid. Take for example the next code:
var ref: Ref<[String]>!
// adding an inner scope to simulate `a` being destroyed
do {
var a: [String] = ["a"]
ref = Ref(&a)
print(a, ref.value)
a = ["b"]
print(a, ref.value)
}
// `a` was destroyed, however it's place on the stack was taken by `b`
var b: [String:Int] = ["a": 1]
// however `b` is not an array, thus the next line will crash
print(ref.value)

'AnyObject' does not have a member named xxx

There is a method which receives an array of 'AnyObject'. Then inside the method based on some condition cast that AnyObject to specific object. There is no issue till this point. After cast the AnyObject into specific object, if i try to access it's properties then it throws error. I understand the issue for what reason the error is coming. But is there any other way to obtain the same in any other logic. Here is the code.
func downloadImage(#list: Array<AnyObject>, forControler: String) {
var xxxList: Array<AnyObject>
if forControler == "A" {
xxxList = list as! Array<A>
} else if forControler == "B" {
xxxList = list as! Array<B>
} else {
xxxList = list as! Array<C>
}
for (index, url) in enumerate(xxxList) {
url.A
}
}
Error throws in url.A
Thanks
You have multiple errors in your code.
First, when declaring a var in Swift (here xxxList), you have to initialize it just after. Otherwise, this var needs to be flagged as optional.
You get that error for the compiler is "dumb". It does not know what the real type of your objects wrapped into your array is. It only sees AnyObject objects, which do not have any A field or property. You have to cast your list into the desired type before iterating over it.
Your code should be like this instead:
func downloadImage(#list: [AnyObject], forController: String) {
if forController == "A" {
var aList = list as! [A]
// for loop iterating over A objects
} else if forController == "B" {
var bList = list as! [B]
// for loop iterating over B objects
} else {
var cList = list as! [C]
// for loop iterating over C objects
}
}
You must define a loop for each type. Well, you can also define a single loop and perform the casting into that one (moving the if statements into that loop).