Swift append String var to an List by reference - swift

Hi i want to append string by reference to a List.
for example :
var str = "old"
func foo(){
list.append(str)
}
func changeStr(){
str = "new"
}
my problem is that the value of str ("old") is being appended and not the reference, so when i change str to "new" it doesn't take any effect on the list.
my question is how i can append reference to a string var?
thanks.

You have to wrap the string in a class. Have the array hold members of your class, and update the class object with a new string value.
Just like if you had an array of ints in whatever language you are used to.
Best solution is to get used to standard Swift/functional idioms.
-- EXAMPLE --
class StringObject {
init(str: String) {
self.str = str
}
var str: String = ""
}
let so = StringObject(str: "old")
func foo() {
list.append(so)
}
func changeStr() {
so.str = "new"
}

Related

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

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.

In Swift, how do I make a class's array of objects only accessible as an entire array?

I have a pattern that I want developers to be able to access my classes, due to using property observers. In the examples below, I have property observer on B.a and A.str, below, so if developers try modify B.a.str directly, only the str property observer is fired. So I want to limit access to B.a to only setting and getting the entire array, making modifications have to happen outside of access to B directly (i.e., you'll see below). I have been reading a lot of Apple docs on Swift 4+, and I can't figure it out. Example below:
class A
{
let str: String? = "Hello!"
...
}
class B
{
var a: [A]()
...
}
I don't want users to use this set of classes like this:
let b: B = B()
B.a.str? = "NewValue"
How do I make "a" accessible only by this pattern:
let a_ref = b.a
a_ref.str? = "NewValue"
b.a = a_ref
I am not sure why you want to have this pattern and I am not sure if it's a good idea to use it. Anyway, you can achieve what you want by simply returning a copy of a when you use the getter that B provides.
class A {
var str: String?
init(str: String? = "Hello") {
self.str = str
}
func copy() -> A {
return A(str: str)
}
}
class B {
private var _a = A()
var a: A {
get { return _a.copy() }
set { _a = newValue }
}
init() { }
}
Now you can test it with the following code:
// First Attempt (Direct)
let b: B = B()
b.a.str = "NewValue"
print(b.a.str) // Prints "Hello"
// Second Attempt (via Reference)
let a_ref = b.a
a_ref.str = "NewValue"
b.a = a_ref
print(b.a.str) // Prints "NewValue

Variable 'xxx' was never mutated; in derived class

I'm posting my first message here, I've a logical question about swift language. For your information, I'm quite new in swift language, I'm use to code in c++ and it's a bit hard for me to have an objective point of view on how to do things right (in an elegant way), if you have some advices, pls feel free to do your suggestions.
I'm doing a homemade encapsulation using the following superclass :
class MultiLevel_encapsulation {
var separator = "";
var datas:[String:String] = [:]
func wrap() -> String{
var out:String = ""
var i = 0
for (key, data) in datas{
if i==0{
out += key + separator + data
}
else{
out += separator + key + separator + data
}
i+=1
}
return out
}
func unwrap(content:String){
let split = content.components(separatedBy: separator)
var i = 1
while(i < split.count){
datas[split[i-1]] = split[i]
i += 2
}
}
func getAttributesNames() -> [String]{
var out:[String] = []
for (key, _) in datas{
out.append(key)
}
return out
}
func getValue(name:String) -> String? {
return datas[name];
}
func setValue(name:String, value:String){
datas[name] = value;
}
}
and I want to create some subclasses including the superclass, I just change the separator depending of the subclass name :
class Level5_encapsulation: MultiLevel_encapsulation{
init(message:String) {
super.init()
separator = "&&LEVEL5&&"
unwrap(content:message)
}
override init() {
super.init()
separator = "&&LEVEL5&&"
}
}
So after it I just need to create the subclass as a var in my program, add values and wrap it to have an encapsulated string :
var l5message = Level5_encapsulation()
l5message.setValue(name: #anyTitle#, value: #anyValue#)
var output = l5message.wrap() // String with encapsulated message
Do you think it 's the right way to do it or is there a better way for that ?
My main question is about this compiler warning :
Variable 'l5message' was never mutated; consider changing to 'let' constant
I changed it for a let and it works.
So there is something I don't understand : Why can I change proprieties in the superclass as if the inherited subclass is declared as constant ? Where is the storage of the superclass and how does it works ?
In Swift classes and structs behave a bit differently than in C++. var and let prevent changes to the actual value, and since the variable type that you're using is a class the variable holds a reference, and not the actual data (Like Level5_encapsulation *l5message).
Since you're not mutating the value of the variable (A reference), the compiler raises a warning.

Using a Type Variable in a Generic

I have this question except for Swift. How do I use a Type variable in a generic?
I tried this:
func intType() -> Int.Type {
return Int.self
}
func test() {
var t = self.intType()
var arr = Array<t>() // Error: "'t' is not a type". Uh... yeah, it is.
}
This didn't work either:
var arr = Array<t.Type>() // Error: "'t' is not a type"
var arr = Array<t.self>() // Swift doesn't seem to even understand this syntax at all.
Is there a way to do this? I get the feeling that Swift just doesn't support it and is giving me somewhat ambiguous error messages.
Edit: Here's a more complex example where the problem can't be circumvented using a generic function header. Of course it doesn't make sense, but I have a sensible use for this kind of functionality somewhere in my code and would rather post a clean example instead of my actual code:
func someTypes() -> [Any.Type] {
var ret = [Any.Type]()
for (var i = 0; i<rand()%10; i++) {
if (rand()%2 == 0){ ret.append(Int.self) }
else {ret.append(String.self) }
}
return ret
}
func test() {
var ts = self.someTypes()
for t in ts {
var arr = Array<t>()
}
}
Swift's static typing means the type of a variable must be known at compile time.
In the context of a generic function func foo<T>() { ... }, T looks like a variable, but its type is actually known at compile time based on where the function is called from. The behavior of Array<T>() depends on T, but this information is known at compile time.
When using protocols, Swift employs dynamic dispatch, so you can write Array<MyProtocol>(), and the array simply stores references to things which implement MyProtocol — so when you get something out of the array, you have access to all functions/variables/typealiases required by MyProtocol.
But if t is actually a variable of kind Any.Type, Array<t>() is meaningless since its type is actually not known at compile time. (Since Array is a generic struct, the compiler needs know which type to use as the generic parameter, but this is not possible.)
I would recommend watching some videos from WWDC this year:
Protocol-Oriented Programming in Swift
Building Better Apps with Value Types in Swift
I found this slide particularly helpful for understanding protocols and dynamic dispatch:
There is a way and it's called generics. You could do something like that.
class func foo() {
test(Int.self)
}
class func test<T>(t: T.Type) {
var arr = Array<T>()
}
You will need to hint the compiler at the type you want to specialize the function with, one way or another. Another way is with return param (discarded in that case):
class func foo() {
let _:Int = test()
}
class func test<T>() -> T {
var arr = Array<T>()
}
And using generics on a class (or struct) you don't need the extra param:
class Whatever<T> {
var array = [T]() // another way to init the array.
}
let we = Whatever<Int>()
jtbandes' answer - that you can't use your current approach because Swift is statically typed - is correct.
However, if you're willing to create a whitelist of allowable types in your array, for example in an enum, you can dynamically initialize different types at runtime.
First, create an enum of allowable types:
enum Types {
case Int
case String
}
Create an Example class. Implement your someTypes() function to use these enum values. (You could easily transform a JSON array of strings into an array of this enum.)
class Example {
func someTypes() -> [Types] {
var ret = [Types]()
for _ in 1...rand()%10 {
if (rand()%2 == 0){ ret.append(.Int) }
else {ret.append(.String) }
}
return ret
}
Now implement your test function, using switch to scope arr for each allowable type:
func test() {
let types = self.someTypes()
for type in types {
switch type {
case .Int:
var arr = [Int]()
arr += [4]
case .String:
var arr = [String]()
arr += ["hi"]
}
}
}
}
As you may know, you could alternatively declare arr as [Any] to mix types (the "heterogenous" case in jtbandes' answer):
var arr = [Any]()
for type in types {
switch type {
case .Int:
arr += [4]
case .String:
arr += ["hi"]
}
}
print(arr)
I would break it down with the things you already learned from the first answer. I took the liberty to refactor some code. Here it is:
func someTypes<T>(t: T.Type) -> [Any.Type] {
var ret = [Any.Type]()
for _ in 0..<rand()%10 {
if (rand()%2 == 0){ ret.append(T.self) }
else {
ret.append(String.self)
}
}
return ret
}
func makeArray<T>(t: T) -> [T] {
return [T]()
}
func test() {
let ts = someTypes(Int.self)
for t in ts {
print(t)
}
}
This is somewhat working but I believe the way of doing this is very unorthodox. Could you use reflection (mirroring) instead?
Its possible so long as you can provide "a hint" to the compiler about the type of... T. So in the example below one must use : String?.
func cast<T>(_ value: Any) -> T? {
return value as? T
}
let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue)
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
Why Swift doesn't just allow us to let casted = cast<String>(inputValue) I'll never know.
One annoying scenerio is when your func has no return value. Then its not always straightford to provide the necessary "hint". Lets look at this example...
func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
completion(value as? T)
}
The following client code DOES NOT COMPILE. It gives a "Generic parameter 'T' could not be inferred" error.
let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
print(casted)
print(type(of: casted))
}
But you can solve this by providing a "hint" to compiler as follows:
asyncCast(inputValue) { (casted: String?) in
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
}

Swift computed property to return copy of underlying array

I have a model class written in Objective-C that I'm converting to Swift. It contains an NSMutableArray internally, but the method signature for the getter, as well as the actual return value, are NSArray. When called, it creates an immutable copy to return.
Essentially, I want callers to be able to iterate/inspect the container, but not modify it. I have this test snippet:
class Container {
internal var myItems = [String]()
func sayHello() {
"I have: \(myItems)"
}
}
let cont = Container()
cont.myItems.append("Neat") // ["Neat"]
cont.sayHello() // This causes sayHello() to print: "I have: [Neat]"
var isThisACopy = cont.myItems
isThisACopy.append("Huh") // ["Neat", "Huh"]
cont.sayHello() // This ALSO causes sayHello() to print: "I have: [Neat]"
I've been trying to find a way to override the getter for myItems so that it returns an immutable copy, but can't seem to determine how.
Attempt #1
This produces a compiler error: Function produces expected type '_ArrayBuffer<(String)>'; did you mean to call it with '()'?
internal var myItems = [String]() {
var copy = [String]()
for item in ... { // What to use in the ...?
copy.append(item)
}
return copy
}
Attempt #2
This also produces a compiler error, because I'm (understandably) redefining the generated getter Invalid redeclaration of 'myItems()':
internal func myItems() -> [String] {
var copy = [String]()
for item in myItems {
copy.append(item)
}
return copy
}
Try this:
class Container {
private var _myItems: [String] = ["hello"]
internal var myItems: [String] {
return _myItems
}
}
let cont = Container()
cont.myItems.append("Neat") //not allowed
It uses a private stored property and a computed property that returns an immutable copy. It's not possible for a stored property to use custom getters.
A better way to expose mutable properties as immutable:
class Container {
private (set) internal var myItems: [String]
}