1

I have a Core Data project where I'm retrieving data from multiple sources. Not all the data will be saved, so I created a struct to pass the data around before it's saved.

struct SearchResult {
    var myURL: String?
    var myHeadline: String?

    init() {}

    init(myURL: String, myHeadline: String) {
        self.myURL = myURL
        self.myHeadline = myHeadline
    }
}

Since I want to display sections in a UITableView, I created a struct for that, as well:

struct ResultsObjects {
    var sectionName: String?
    var sectionObjects = [SearchResult]()

    init() {}

    init(sectionName: String) {
        self.sectionName = sectionName
    }
}

Lastly, I created a singleton to keep hold SearchResult objects prior to saving them in the managedObjectContext.

class SearchResultsArray {
    static let sharedInstance = SearchResultsArray()
    var resultsObjectsArray = Array<ResultsObjects>()
    fileprivate init() {}
}

My objective is to have the SearchResultsArray class accessible via multiple classes and be able to dump ResultsObjects into it in one class and see ResultsObjects in another.

On MyViewController, I'm instantiating the SearchResultsArray as follows:

var resultsObjectsArray = SearchResultsArray.sharedInstance.resultsObjectsArray

I have a UITableViewController that I use resultsObjectsArray to populate.

On MyViewController, a button calls a method that uses a method in MyParserClass to parse web pages. This works, too.

MyParserClass declares a resultsObjectsArray in the same manner as MyViewController:

var resultsObjectsArray = SearchResultsArray.sharedInstance.resultsObjectsArray

In MyParserClass, a method is called that creates SearchResult objects, dumps them into ResultsObjects and appends them to SearchResultsArray.resultsObjectsArray. This works fine. I threw in the following line in the method that creates ResultsObjects that are dumped into resultsObjectsArray:

print("\(resultsObjectsArray.count) ResultsObjects in SearchResultsArray")
delegate.updateSearchResults()

I threw a print statement in MyViewController's updateSearchResults delegate method and it returns 0, regardless of what MyParserClass says was in there just prior to calling the delegate method.

The app doesn't crash, but it seems like I've got a problem with the singleton. Any suggestions re: what I'm doing wrong are greatly appreciated.

Adrian
  • 16,233
  • 18
  • 112
  • 180
  • Have you seen this post http://stackoverflow.com/questions/26742138/singleton-in-swift it suggests that we should both be declaring the class `final` to ensure we don't get multiples - might be worth checking out – Russell Jan 31 '17 at 17:56
  • 1
    Arrays are value types in swift. You can't use an instance variable to refer to the array. You must always use the `SearchResultsArray.sharedinstance.resultsObjectsArray` – Paulw11 Jan 31 '17 at 18:30
  • @Paulw11 Wow! I knew I was missing something simple, but WOW! Thank you so much for your response. I greatly appreciate it. – Adrian Jan 31 '17 at 18:56
  • 1
    since this it is also a problem to change values inside structs - i read about a pattern named `lenses`. it is interestung. just google for `lenses swift` or this starting point https://github.com/narfdotpl/lenso – muescha Feb 01 '17 at 01:37

2 Answers2

1

Arrays are value types in swift; when you assign an array to a variable and then assign that variable to another, the second variable refers to a copy of the original array, not the original array (technically, for efficiency, the array is copied on modify, not on assignment but the result is the same), so changes made to the original array will not be reflected in the second variable's array.

Consider

var someArray = [Int]()
var someOtherVariable = someArray
someArray.append(2)

someArray will contain a single value while someOtherVariable will still be empty.

One solution always use the SearchResultsArray.sharedinstance.resultsObjectsArray rather than local variable/properties.

Perhaps a better approach would be to encapsulate the array in your singleton class by providing some functions to manipulate it. For example;

class SearchResultsArray {
    static let sharedInstance = SearchResultsArray()
    var resultsObjectsArray = Array<ResultsObjects>()

    public var results: [ResultsObjects] {
       get {
           return resultsObjectsArray
       }
    }


    public func add(results: ResultsObjects) {
        resultsObjectsArray.append(results)
    }


    fileprivate init() {}
}

Since results is now a read-only property and a separate add function exists, the class more clearly defines its semantics.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
0

I use a singleton in a very similar way - but I define it quite differently.

EDIT - differently, and now superseded :-(

Within the singleton DataModelInstance in my case, I have this

class DataModelInstance : NSObject, NSCoding
{
    // define all of the variables I need within the singleton

    class var sharedInstance : DataModelInstance
    {
        struct Singleton
        {
            static let instance = DataModelInstance()
        }
        return Singleton.instance
    }

    // everything else that I need in the class...
}

and then, in every class that needs to access it, I define a variable

    var dataModel = DataModelInstance.sharedInstance
Russell
  • 5,436
  • 2
  • 19
  • 27
  • 1
    its inside the DataModelInstance? can you add a little DataModelInstance around to make this more clear how "inside singleton" it is – muescha Jan 31 '17 at 17:32
  • ok - I have updated the answer. I haven't included any of the NSCoding methods, or any of the data-level methods that I have implemented, but this should be enough to get a singleton working across all of my ViewControllers – Russell Jan 31 '17 at 17:38
  • 2
    @Russell: Your way of defining a Singleton is superseeded. Now you just define a singleton the way the OP did in the question. – Luca Angeletti Jan 31 '17 at 17:40
  • 1
    why not this way? `class DataModelInstance : NSObject, NSCoding { static let sharedInstance = DataModelInstance() }` – muescha Jan 31 '17 at 17:43
  • yes - looks like I'm the one who has learned something today. I have been using the same pattern in everything - since before it was superseded! – Russell Jan 31 '17 at 17:45
  • @Russell Thanks for your suggestion. Just for kicks, I tried it and I get the same result I got with my original code...the parser class sees the singleton, but when I try to access it via the delegate method on the view controller, it doesn't see what the parser did. I'm stumped. If it's not a "how I'm instantiating the singleton" issue, is it a "where I'm instantiating the singleton"? – Adrian Jan 31 '17 at 17:50
  • That makes sense. On the plus side - I have now started updating all of my projects to use the same, simpler, approach for instantiating the Singleton – Russell Jan 31 '17 at 17:51
  • just a side note: since you use a class it returns asreference - the original question uses strucs - they are used be value – muescha Feb 01 '17 at 01:27