Swift Function to Return Array Reference - arrays

I'm fairly new to Swift, so be gentle...
I have a class, called ProgramOptions, as so:
class ProgramOptions {
var startDate: Date = Date()
var trackedActivities = [TrackedItem]()
var trackedFoods = [TrackedItem]()
var trackedDrugs = [TrackedItem]()
...
}
TrackedItem is another class of mine.
In a TableViewController's code I want to select one of the arrays from an instance of ProgramOptions, based on what section of the table is in question. I want to then do many possible things, like remove or add items, edit them, etc.
Being a Swift beginner, I naively wrote this function:
func trackedArrayForSection(_ section: Int) -> [TrackedItem]? {
switch(section) {
case 1: return programOptions.trackedActivities
case 2: return programOptions.trackedFoods
case 3: return programOptions.trackedDrugs
default: return nil
}
}
(Section 0 and Sections > 3 don't have associated arrays so I return nil)
But then harsh reality bit me. I guess the array is a copy. (Or my weak understanding of similar question on StackOverflow indicates that it is sometimes copied.)
So here's the question for you... How could I write my trackedArrayForSection so that I get a reference to actual array sitting in ProgramOptions that I can then add and remove items from?
I could, of course, have a switch statement every place I use this, but there are close to a zillion of them and I'd like to avoid that. I'm assuming there is an easy answer to this and I'm just too ignorant at this point to know it.
Thanks for your help!

I would extract your switch logic to an instance method of your class:
class ProgramOptions {
var startDate: Date = Date()
private var trackedActivities = [TrackedItem]()
private var trackedFoods = [TrackedItem]()
private var trackedDrugs = [TrackedItem]()
func trackedItems(forSection section: Int) -> [TrackedItem]? {
switch section {
case 1: return self.trackedActivities
case 2: return self.trackedFoods
case 3: return self.trackedDrugs
default: return nil
}
}
func add(trackedActivity activity: TrackItem){
self.trackedActivities.append(activity)
}
}
and then in you view you only have to reload the table.

One possibility is to create a wrapper class to store the array of TrackedItems. Say, for example:
class TrackedItemCollection {
var items = Array<TrackedItem>()
}
and then in your implementation:
class ProgramOptions {
var startDate: Date = Date()
var trackedActivities = TrackedItemCollection()
var trackedFoods = TrackedItemCollection()
var trackedDrugs = TrackedItemCollection()
...
}
func trackedCollectionForSection(_ section: Int) -> TrackedItemCollection? {
switch(section) {
case 1: return programOptions.trackedActivities
case 2: return programOptions.trackedFoods
case 3: return programOptions.trackedDrugs
default: return nil
}
}
When you want the array, you use the items property in TrackedItemCollection. As TrackedItemCollection is a class and thus a reference type, you won't have the arrays copyed.

Related

Swift Deep Flatten Array Property [duplicate]

I'm starting to learn about closures and want to implement them in a project I'm working on and I'd like some help.
I have a class defined as follows:
class MyObject {
var name: String?
var type: String?
var subObjects: [MyObject]?
}
And I want to use closures or higher oder functions (something like flatMap comes to mind) to flatten an [MyObject] and joining all MyObject and subOjects into one array.
I've tried using [MyObject].flatMap() but this operation doesn't return the nested subObjects.
First, I would highly recommend making the type of subObjects be non-optional. There's rarely a reason for optional arrays. Do you really need to distinguish between "no array" and "an empty array?" This is very uncommon. If you make subObjects just be an array, you can write what you're describing as a simple recursive function:
func flattenMyObjects(myObjects: [MyObject]) -> [MyObject] {
return myObjects.flatMap { (myObject) -> [MyObject] in
var result = [myObject]
result.appendContentsOf(flattenMyObjects(myObject.subObjects))
return result
}
}
If you need it to be optional, the changes are minor (you'll need to add an if-let or something similar).
One approach to flattening a recursive class structure is with a recursive function.
Here is the class that we would like flattened:
public class Nested {
public let n : Int
public let sub : [Nested]?
public init(_ n:Int, _ sub:[Nested]?) {
self.n = n
self.sub = sub
}
}
Here is the function that demonstrates how this could be done:
func test() {
let h = [
Nested(1, [Nested(2, nil), Nested(3, nil)])
, Nested(4, nil)
, Nested(5, [Nested(6, nil), Nested(7, [Nested(8, nil), Nested(9, nil)])])
]
func recursiveFlat(next:Nested) -> [Nested] {
var res = [Nested]()
res.append(next)
if let subArray = next.sub {
res.appendContentsOf(subArray.flatMap({ (item) -> [Nested] in
recursiveFlat(item)
}))
}
return res
}
for item in h.flatMap(recursiveFlat) {
print(item.n)
}
}
The heart of this approach is recursiveFlat local function. It appends the content of the nested object to the result, and then conditionally calls itself for each element to add their contents as well.

Cast to right generic from array in Swift

I have a Protocol called Composite.
This protocol has an array composites: [Composite]
I also have a generic subclass GenericSubclass<T>: Composite
When iterating over the array the best I can come up with looks like this:
for item in composites {
if let item = item as? GenericSubclass<A> {
let sc = SomeOtherClass<A>
} else if let item = item as? GenericSubclass<B> {
let sc = SomeOtherClass<B>
} //and so on...
}
Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.
Any help is much appreciated.
It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.
However, one way to solve the issue is to hoist the API into the Composite protocol:
protocol Composite {
var composites: [Composite] { get set }
func otherClass() -> OtherProtocol
}
protocol OtherProtocol { }
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
func otherClass() -> OtherProtocol {
return SomeOtherClass<T>()
}
}
class SomeOtherClass<T>: OtherProtocol {}
So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:
var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]
for item in c.composites {
let sc = item.otherClass()
print(sc)
}
Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:
protocol Composite {
var composites: [Composite] { get set }
func optionalClass() -> OtherProtocol?
}
extension Composite {
func optionalClass() -> OtherProtocol? {
return nil
}
}
I did some experiment on this in the playground and i came up with this
protocol Composite {
var composites: [Composite] { get set }
}
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
}
let subclass = GenericSubclass<String>()
for item in subclass.composites {
let className = String(describing: type(of: item))
let aClassType = NSClassFromString(className) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(instance) //Output: GenericSubclass<String>
}
Hope this will help someone.
I think it's not possible to do that in array.
While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].
// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
You want donging something like this func below, the param should be GenericSubclass<T> to compile success
func genericFunc<T>(param:GenericSubclass<T>) {
let sc = SomeOtherClass<T>()
print(sc)
}
Anyway you can implement it with member var for the instance like the code below:
class Subclass {
var type : Any
init(type : Any) {
self.type = type
}
}
class SomeOtherClass : CustomDebugStringConvertible{
var type : Any
init(type : Any) {
self.type = type
}
var debugDescription: String{
return String(describing: type.self)
}
}
let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]
let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]
You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:
import UIKit
//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type
func createNewObject()->MyAwesomeProtocol
}
//Just create empty string
extension String: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return String()
}
}
//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return UIControlState.normal
}
}
//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return type(of:self).init()
}
}
//This is test function
//it creates array of newly created items and prints them out
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){
var newItems = [MyAwesomeProtocol]()
for anItem in items {
let newOne = anItem.createNewObject()
newItems.append(newOne)
}
print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}
doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])
doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])
doSomeCoolStuffWith(items: ["I","love","swift"])
This will produce the following result:
created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]
created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]
created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]

How to check if an object is in array

Edit: The problem is already solved by #vacawama. But if you are looking for an answer for NSObject classes, you should implement isEqual function which is NSObjectProtocol. Otherwise you gonna get an error says: " Redundant conformance of 'classname' to protocol 'Equatable' "
You can check this for details: Swift 2.2, Contains Method not working
In swift, how can i check if an object is in array?
I have a simple class like this;
class Test: {
private var _number: Int!
private var _type: String!
var number: Int {
return _number
}
var type: String {
return _type
}
init (number: Int, type: String) {
self._number = number
self._type = type
}
}
Also i have this class;
class TestRandom {
private let _numberArr: [Int] = [1,2,3,4,5,6,7,8,9,10]
private let _typeArr: [String] = ["x","y","z"]
public private(set) var _testArr: [Test] = []
private var _randomTest: Test!
func randomTestPicker () {
repeat {
let randomNumber = Int(arc4random_uniform(UInt32(self._numberArr.count)))
let randomType = Int(arc4random_uniform(UInt32(self._typeArr.count)))
self._randomTest = Test(number: self._numberArr[randomNumber], type: self._typeArr[randomType])
} while self._testArr.contains(_randomTest)
}
}
All i want to do is to pick different objects. Lets say i have x2,y4,x6,z3,z8,y2 in _testArr. When i call randomTestPicker, it should not pick x2 or z8. Because they are already in array.
I have tried contains as you see. However it did not work for me. Is there any solution that i can use for this purpose? Or what is the best way to do this?
Edit: I tried self._testArr.contains{$0 === _randomTest} but not working neither.
You can't use contains that way since your class doesn't conform to the Equatable protocol.
Add :Equatable to your class definition and implement the == function which compares two of your objects:
class Test: Equatable {
private var _number: Int!
private var _type: String!
var number: Int {
return _number
}
var type: String {
return _type
}
init (number: Int, type: String) {
self._number = number
self._type = type
}
}
func ==(lhs: Test, rhs: Test) -> Bool {
return lhs.number == rhs.number && lhs.type == rhs.type
}
The other way this could have been done is to use the predicate form of contains. The predicate takes two objects and returns a Bool indicating if they match. In that case, you would write:
self._testArr.contains { $0.number == _randomTest.number && $0.type == _randomTest.type }
As you can see, in this case the closure is essentially the == function from above, so implementing the Equatable protocol is the cleaner way to do it.
The closure { $0 === _randomTest } doesn't work because that only tests if the objects are the same instance. In your case, you need to check if the two objects have the same properties, and you are not interested if they are same instance. The way you are creating the objects, you never would create an instance that is already in the array, so this check would always return false.

Mapping swift enum with associated values

Let say we have an enum with associated value types. In the example below the two value types are simple object that hold an image and a url to share.
enum Content {
case Image(ShareableImage)
case Video(ShareableVideo)
}
Now let's have an array of video and image cases.
let media: [Content] = [*a lot of enum cases inside here*]
All the code above so far cannot be changed in any way in the codebase, I need to work with it.
Here starts my problem:
Let's filter the array with media to only image cases
let imageOnlyCases: [Content] = media.filter { item -> Bool in
switch item {
case .Image: return true
default: return false
}
}
Next step, I want to get from array of enum to an array of their associated values
[Content] -> [ShareableImage] by using map.
so I do this
let shareablemages = imageOnlyCases.map { imageCase -> ShareableImage in
switch imageCase {
case .Image(let image): return image
default: return WHAT TO DO HERE?
}
}
You see, I have a problem with return type..I know that the enum cases are all .Image..and I want a simple map. But the swift syntax is not helping me.
Any ideas?
You could return image for case .Image, and nil otherwise, within a .flatMap operation (to "filter" out nil entries):
/* Example */
enum Foo {
case Bar(Int)
case Baz(Int)
}
let foo: [Foo] = [.Bar(1), .Bar(9),. Baz(3), .Bar(39), .Baz(5)]
/* 1. using 'switch' */
let barOnlyValues: [Int] = foo.flatMap {
switch $0 {
case .Bar(let val): return val
case _: return nil
}}
/* 2. alternatively, as pointed out in MartinR:s answer;
as you're only looking for a single case, the alternative
'if case let' clause could be preferred over 'switch': */
let barOnlyValuesAlt: [Int] = foo.flatMap {
if case let .Bar(val) = $0 { return val }
else { return nil }}
print(barOnlyValues) // [1, 9, 39]
Applied to your use case: note that you needn't perform the filtering to create the imageOnlyCases array, as you can apply the above directly on the media array:
/* 1. using switch */
let shareableImages : [ShareableImage] = media.flatMap {
switch $0 {
case .Image(let image): return image
case _: return nil
}}
/* 2. 'if case let' alternative, as per MartinR:s suggestion */
let shareableImagesAlt : [ShareableImage] = media.flatMap {
if case let .Image(image) = $0 { return image }
else { return nil }}
Disclaimer: I cannot verify your specific use case in practice as I don't have access to the ShareableImage class/struct.
(Thanks #MartinR for advice that .map{ ... }.flatMap{ ... } can be simplified to just .flatMap{ ... }).
If it is guaranteed that only the .Image case can occur then
you can call fatalError() in all other cases:
let shareableImages = imageOnlyCases.map { imageCase -> ShareableImage in
if case let .Image(image) = imageCase {
return image
} else {
fatalError("Unexpected content")
}
}
fatalError() causes the program to terminate immediately. It is
only meant for situations that "cannot occur", i.e. to find programming
errors.
It satisfies the compiler because the function is marked as #noreturn.
If you cannot make that guarantee then use flatMap() as suggested
in the other answer.
Note also that you can use if case here with a pattern instead
of switch/case.

Enum with array of string

I'm a newbie in Swift and I found on the internet a utiliy class to handle errors when I use Objective C classes from swift. Here the utility class, which is a enum:
enum Result<A> {
case Success(Box<A>), Error(NSError)
static func success(v: A) -> Result<A> {
return .Success(Box(v))
}
static func error(e: NSError) -> Result<A> {
return .Error(e)
}
}
final class Box<A> {
let value: A
init(_ value: A) {
self.value = value
}
}
I have a function that returns an Array of strings of type result
func getFiles(account:DBAccount, curFolder:String) ->Result<[String]>{ ...}
Now how can I access the results in an easy way. A println on the console gives me (Enum Value).
I tried to get the results using the following:
let dicList = getFiles(account, currFolder)
switch dicList {
case let .Success(aBox): results = aBox.value
case let .Error(err): results = []
}
now the array results contains the data, but is there no easier way to access the results.
thanks
arnold

Resources