mutableArrayValueForKey: countOf<Key> not being called, countOfSongs - swift

My exact question is here:
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058
I'm trying to understand how the proxy object which is returned from
mutableArrayValueForKey: works and I've hit a bit of a wall. I get
what the proxy is and why it exists. I have a test app which allows
me to do things to/with the collection it represents and it all works
fine. The problem is that when I try to implement some of the methods
mentioned in the developer docs under the "Key-Value Coding Accessor
Methods" section. There are some methods there which if implemented
in the hosting object (i.e. the original recipient of the
mutableArrayValueForKey: call) are to be called by the proxy when the
proxy is asked to do various things. The hitch is that in my test app
I can't get the -countOf<key> or -objectIn<key>AtIndex methods to be
invoked.
In reading through the docs, it seems that a number of methods need
to be implemented before any of these (dare I call them 'proxy
methods'?) are called. I implemented a whole slew of them - including
a number that shouldn't need to be - and ended up with this set: (The
test app is based on a Playlist->Songs->Song model where "songs" is
the NSMutableArray which lives in the Playlist class in which I'm
interested in getting a count of its member songs.)
- (unsigned int)countOfSongs;
- (Song *)objectInSongsAtIndex:(unsigned int)index;
- (NSArray *)songsAtIndexes:(NSIndexSet *)indexes;
- (void)getSongs:(Song **)buffer range:(NSRange)inRange;
- (void)insertObject:(Song *)newSong inSongsAtIndex:(unsigned int)idx;
- (void)removeObjectFromSongsAtIndex:(unsigned int)idx;
Even simple test code like this fails:
Playlist *myPlaylist = [[[Playlist alloc] init] autorelease];
id arrayProxy = [myPlaylist mutableArrayValueForKey:#"songs"];
[arrayProxy insertObject:[[[Song alloc] initWithName:#"test" andLength:
10] autorelease] atIndex:0];
unsigned int theCount = [arrayProxy count];
I've got all the properties defined, all the methods written, etc.
Yet when I call [arrayProxy count] my countOfSongs method in the
Playlist class isn't touched. The right answer is returned, but it's
apparently coming from the runtime going to the array directly and
getting the answer via NSArray's count method.
Oddly enough, when I do the insertObject call in line #3 the
insertObject:inSongsAtIndex: method IS called... so some of this stuff
works as I believe it is supposed to. Unfortunately it's the other
stuff that's driving me nuts. I've been working on this one for a
couple of days now and have tried everything I could come up with -
including some really silly, paranoid stuff - and have made no progress.
Can anybody help me with a suggestion as to what I might be doing
wrong or what I'm missing?
Thanks!
And the answer there--although it seemed to help the op--does nothing to shed any light on the problem for me.
Here is my playground code:
import Cocoa
"hello"
class Song {
dynamic var title = "Hello"
}
"hello"
class PlayList: NSObject {
/*dynamic*/ var songs = NSMutableArray()
//private var theSongs = NSMutableArray()
//var countOfSongs: Int = 100
func countOfSongs() -> Int {
println("count of songs")
return 100
//return theSongs.count + 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return songs[i]
//return theSongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
songs[index] = song as! Song
//theSongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
songs.removeObjectAtIndex(index)
//theSongs.removeObjectAtIndex(index)
}
}
"hello"
var playlist = PlayList()
"hello"
let arrayProxy = playlist.mutableArrayValueForKey("songs")
"hello"
arrayProxy.addObject(Song()) //successfully calls proxy method => outputs "insert"
arrayProxy.removeObjectAtIndex(0) //successfully calls proxy method => outputs "remove"
arrayProxy.count //=> 0 ???
What changes do I need to make to my code so that the countOfSongs property or method is called when I write:
arrayProxy.count

From the referenced article
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058:
The documentation about accessor search order shows that it will
prefer a method named -<key> over the corresponding -countOf<Key> and
- objectIn<Key>AtIndex: methods.
which means that
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
accesses the "songs" property of Playlist directly, if there is such
a property.
If you rename the property then it works as you expected:
class Song {
dynamic var title = "Hello"
}
class PlayList: NSObject {
var thesongs = NSMutableArray()
func countOfSongs() -> Int {
println("count of songs")
return 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return thesongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
thesongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
thesongs.removeObjectAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
println(cnt)
Output:
count of songs
100

For future searchers, here is a full example exercising all the methods in an OSX>Application>Command Line Tool (I checked Swift for the language):
import Foundation
class Song {
var title = "Hello"
}
class PlayList: NSObject {
private var array: [Song] = []
func countOfSongs() -> Int {
println("countOfSongs() called")
return array.count
}
/*
//This also works:
var countOfSongs: Int {
println("countOfSongs property was accessed")
return array.count
}
*/
func objectInSongsAtIndex(index: Int) -> AnyObject? {
println("getter called")
return array[index]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("inserting at index \(index)")
array.insert(song as! Song, atIndex: index)
}
func removeObjectFromSongsAtIndex(index:Int) {
println("removing at index \(index)")
array.removeAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.objectAtIndex(0)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
--output:--
countOfSongs() called
inserting at index 0
countOfSongs() called
1
getter called
removing at index 0
countOfSongs() called
0
No more playgrounds for me!

Related

MacOS: reading shared preferences in Swift and ObjectiveC

I have a project that mixes Swift and objective c. The controller for my preference panes is written in Swift. I can’t seem to read those preferences in Objective C.
This is the code that writes the parameter into the preferences…
let userDefaults = Preferences.shared.getUserDefaults()
#IBAction func DefType(_ sender: NSPopUpButton) {
var parameter:NSNumber
parameter = DefType.indexOfSelectedItem + 1 as NSNumber
userDefaults.set(parameter, forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
}
func getUserDefaults() -> UserDefaults {
return UserDefaults.standard
}
The above code is working as the following code is in the function that sets up the preference pane And it initiates the popup correctly.
func setupUI() {
var temp1:Int
temp1 = userDefaults.parameter(forKey: ConstantUtility.StorageKeys.SelectedType.rawValue) ?? 0
temp1 -= 1
DefType.selectItem(at: temp1)
}
On the other side of the program is the following code intended to read the preferences. This code always produces an answer of zero.
- (void) tableViewDoubleTapAction {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
long selectedType = [userDefaults integerForKey:#"SelectedType"];
I'm assuming the problem is in the objectiveC code but I'm open to changing either or both. Been spinning my wheels too long on this one.
Thanks in advance!
I set up a Preferences class as follows...
class Preferences : NSObject {
private override init() {}
#objc static let shared = Preferences()
#objc var selectedType: ConstantUtility.SelectedType {
get {
let type = UserDefaults.standard.integer(forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
guard type != 0, let selectedType = ConstantUtility.SelectedType(rawValue: type) else {
return ConstantUtility.SelectedType.One
}
return type
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: ConstantUtility.StorageKeys.SelectedType.rawValue)
}
}
the swift code to read the preference was changed to
temp1 = userDefaults.integer(forKey: ConstantUtility.StorageKeys.SelectedChart.rawValue)
And the Objective C code became
- (void) tableViewDoubleTapAction {
long selectedChartType = Preferences.shared.selectedType;
It is all working!

trouble acsessing values from class

i am learning swift atm and the use of classes kept me busy the last two weeks.
thank you for any help
for a project in xcode i created a swift file containing a class that initializes empty strings/arrays of strings.
then a function in that class is retrieving data from google firebase to fill those strings.
than in a viewcontroller class is want to retrieve those two strings. how do it do it right?
so far i tried many different ideas but i either get empty strings (as initialized in the beginning of the class) or i get errors
ideally everytime the viewdidload() (in this case) of the viewcontroller class is called i want it to create an instance of the class with uptodate data.
class RetrieveDatabase {
//static let sharedInstance = RetrieveDatabase()
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
//this function works, the arrays contain type [string] and string
return (self.allMessages, self.categorieNames)
}
/*
-> i tried initializers in so many variations...
init(allMessages: [[String]], categorieNames: [String]) {
self.allMessages = loadGoogleValue().allMessages
self.categorieNames = loadGoogleValue().categorieNames
messages = []
}
*/
}
here the code from the viewcontroller class:
class SettingsView: UIViewController {
let retrieveDatabase = RetrieveDatabase.//tried everything here, only errors
override func viewDidLoad() {
super.viewDidLoad()
label1.text = retrieveDatabase.categorieNames[0]
}
}
I've simplified your code to something that you can easily try out in a playground. If you start with a working version, perhaps you can see the difference between it and the one that's giving you trouble.
class RetrieveDatabase {
var allMessages = [[String]]()
var messages = [String]()
var categorieNames = [String]()
init() {
messages.append("Hi")
messages.append("Hello")
allMessages.append(messages)
categorieNames.append("SALUTATIONS")
}
func loadGoogleValue() -> (allMessages: [[String]], categorieNames: [String]) {
return (self.allMessages, self.categorieNames)
}
}
class SettingsView {
let retrieveDatabase = RetrieveDatabase()
func caller() {
print(retrieveDatabase.loadGoogleValue().allMessages)
print(retrieveDatabase.loadGoogleValue().categorieNames)
}
}
let test = SettingsView().caller()
Since there's very little difference between what I've done and the code you added in your comment above, I suspect it's something you've left out concerning how strings are added to the arrays.

Calling a function/passing data from outside a TableViewController (and others) in Swift

In my app I have one screen divided between two ViewControllers - LadderViewController and GameHistoryTableViewController, which lies in a container. I want user to be able to filter the data in the table by tapping on something in the LadderView. I tried to solve this using delegates:
LadderViewController:
delegate = GameHistoryTableViewController()
func imageTapped(imageIndex: Int) {
delegate?.selectedHeroNumber(imageIndex)
}
GameHistoryTableViewController: (conforms to the delegate protocol and implemets a function from it)
func selectedHeroNumber(heroNumber: Int) {
let filteredGames = filterGamesFromHeroNumber(heroNumber)
tableDataSource = filteredGames
self.tableView.reloadData()
}
That doesn't work, though, because the delegate I declare in LadderViewConroller is another instance of GameHistoryTableViewController, not the (to the user) shown one. I don't know how to access the "visible" instance (table) of GameHistoryTableViewController though... So, how should be delegating used here? Or should I use another approach (and if so, what kind)? I basically need to change the table's data source according to on what the user taps, one can say "from outside" (dataSource is a property in my GameHistoryTableViewController class).
Here is an example with delegation like you want to do. It's a better solution than singleton in this case ;)
declare a new protocol call HeroInfo:
protocol HeroInfo: class {
func selectedHeroNumber(heroNumber: Int);
}
LadderViewController:
//create the delegation
weak var delegate:HeroInfo?
func imageTapped(imageIndex: Int) {
//call the delegate method
delegate?.selectedHeroNumber(imageIndex)
}
GameHistoryTableViewController:
// Here get the protocol HeroInfo inheritance
class userTableViewController: UITableViewController, HeroInfo {
override func viewDidLoad() {
super.viewDidLoad()
//Here get your Ladder view in a splitView
if let split = self.splitViewController {
let controllers = split.viewControllers
self.ladderViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? ladderViewController
//register it to delegate
self.ladderViewController?.delegate = self
}
}
...
// Here is your method of your protocol that you must conform to
func selectedHeroNumber(heroNumber: Int) {
let filteredGames = filterGamesFromHeroNumber(heroNumber)
tableDataSource = filteredGames
self.tableView.reloadData()
}
...
}
There are a few ways to achieve this, I have a similar setup for which I use a model class with a singleton to store the relevant data.
For instance you could have the following
class dataModel {
static let sharedInstance = dataModel()
private var _heroNumber = Int()
private init() {}
var heroNumber: Int = {
return _heroNumber
}
func setHero(hero: Int) -> Int {
return _heroNumber
}
}
}
You can then can access this model from each of your controllers using dataModel.sharedInstance.heroNumber etc...

Swift.... Class method vs. Instance method

Thanks in advance for help!!
I'm trying to call a func from within my Class and I keep getting an error saying that:
Missing parameter for argument #1.............Read a few posts saying it's an instance vs class problem? I don't get it..I'm calling the method from within the Class??? There has to be an instance of the class if the method is being called????? right? Here is my code...Thanks
import Foundation
import Parse
class TestViewController {
let photos = getWallImages() //-----This is the line requesting an argument
func getWallImages() -> [WallPost] {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
return objects
println("We have \(objects.count)")
}
} else if let error = error {
println(error)
}
}
}
}
So the "crime" you are committing is the fact that the method is applied in an instance of the class and not as a class method. The function is expecting a self parameter (a reference to the instance). That explains the error message.
Now to fix that you have two quick options:
1. Make it a class function and call it that way too:
class TestViewController {
let photos = TestViewController.getWallImages()
class func getWallImages() -> [WallPost] {
// mumbo jumbo
}
}
This approach is problematic in case you would want to do some instance specific operations, because class func is static method and doesn't provide you with some of the object benefits.
2. Instantiate the object you are calling the method on:
class TestViewController {
let photos = TestViewController().getWallImages()
func getWallImages() -> [WallPost] {
// mumbo jumbo
}
}
This approach isn't correct with your given structure - it doesn't make sense to instantiate another view controller, but if you take the method and put it in a separate class, maybe it would then make sense.
Then of course you have multiple other ways of changing your code to make it work. Maybe you could initialize it with lazy parameter, maybe you could initialize it in the init method. Whatever suits you best. My answer is simply explaining where you've gone wrong.
There are a few ways you can set your property appropriately. You can make getWallImages() a type method:
class TestViewController {
let photos = TestViewController.getWallImages()
class func getWallImages() -> [WallPost] {
....
}
}
Or, you can keep your method an instance method and set your property upon initialization:
class TestViewController {
let photos: [WallPost]!
init() {
super.init()
photos = getWallImages()
}
func getWallImages() -> [WallPost] {
....
}
}
If you're asking a question you should reduce your code to a minimum, discarding unnecessary details.
You probably want something like this:
class MyClass {
let x = MyClass.getStuff()
static func getStuff() -> Int {
return 0
}
}
However your method getWallImages() can't do something like this, because it's returning the result asynchronous, which means you get the result much later after the function has returned.
You could do something like this though (this is how I'd be doing it):
class MyClass {
var x : Int? {
didSet {
if let x = x {
// Do something with x, here it's assigned
} else {
// x was set to nil, something failed
}
}
}
init() {
getStuffAsynchronous()
}
func getStuffAsynchronous() {
// Do your query stuff here, assign x to your objects to invoke the didSet
x = 0
// If it fails somehow, assign set x to nil
// if fail {
// x = nil
// }
}
}

How do I share variables inside a singleton class

I am trying to create a common class for storing and retrieving data in Parse. I made the ParseProcessing class a singleton class. From my main View Controller I load the data and store it into a dictionary in the ParseProcessing. I do this by creating a shared instance of the ParseProcessing class. From another view controller I try to access the data from the dictionary. I assumed that because ParseProcessing is a singleton class that I have a single copy of the dictionary. This does not appear to be correct. How should I declare the variables inside the ParseProcessing so that they are shared? The code is shown below:
import UIKit
var gSep = ","
class QwikFileViewController: UIViewController {
var loadData = ParseProcessing.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// load data from Parse
loadData.loadCategorySubcategoryData()
loadData.loadRecordsFromParse()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
ParseProcessing Singleton Class
import UIKit
import Parse
class ParseProcessing: Parse {
var dictMenuList = [String:String]()
var noteTitle = [String]()
var notes = [String]()
var thumbnailFiles = [PFFile]()
var objectIds = [String]()
var noteImage = UIImage()
class var sharedInstance:ParseProcessing {
struct singleton {
static let instance:ParseProcessing = ParseProcessing()
}
return singleton.instance
}
// Load Category/Subcategory data from Parse Data Base
func loadRecordsFromParse () -> Bool{
var tmpFile = [PFFile]()
var loadComplete = false
var query = PFQuery(className:"Record")
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) items.")
for object in objects! {
self.noteTitle.append(object["title"] as! String)
self.notes.append(object["notes"] as! String)
self.thumbnailFiles.append(object["thumbnail"] as! PFFile)
self.objectIds.append(String(stringInterpolationSegment: object.objectId))
}
} else {
println("\(error)")
}
loadComplete = true
}
return loadComplete
}
// Load Category/Subcategory data from Parse Data Base
func loadCategorySubcategoryData () // -> Dictionary <String,String>
{
var success : Bool = false
var d : Dictionary <String,String> = ["":""]
var menu = PFQuery(className: "Classification")
println("ParseProcessing: loadCategory...")
menu.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
var category = ""
var subcategory = ""
for object in objects! {
category = object["category"] as! String
println("ParseProcessing: category = \(category)")
subcategory = object["subcategory"] as! String
println("ParseProcessing: subcategory = \(subcategory)")
d[category] = subcategory
}
success = true
self.dictMenuList = d
return
} else {
println("ParseProcessing: error = \(error)")
success = false
}
}
return
}
}
Another View Controller to examine the data
import UIKit
class TestViewController: UIViewController {
var dictMenuList = [String:String]()
var loadData = ParseProcessing.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
dictMenuList = loadData.dictMenuList
println("dictMenuList: \(dictMenuList)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The problem is that findObjectsInBackgroundWithBlock is asynchronous method (i.e. it returns immediately but the closure is called later when the query is done). So you cannot return loadComplete in loadRecordsFromParse, for example. This background request will almost certainly never be done by the time loadRecordsFromParse returns.
Instead, you probably want to adopt the completionHandler pattern. For example, this sample loadRecords doesn't try to return anything immediately, but rather will call the completionHandler when the request is done.
func loadRecords(completionHandler:([SomeObject]?, NSError?) -> ()) {
let query = PFQuery(className: "SomeClass")
query.findObjectsInBackgroundWithBlock { objects, error in
// build some model object
completionHandler(objectArray, error)
}
}
And you'd call it like so:
loadData.loadRecords() { objects, error in
// use `objects` (and make sure `error` is `nil`) here
}
// but do not use those variables here, as the above closure probably has not run yet!
Frankly, I'd be inclined to get rid of those properties in your singleton altogether. When you're dealing with asynchronous code, to have public properties that are updated asynchronously is going to be a source of heartache. You can do it, but it wouldn't be my first choice.
For example, when TestViewController is presented, you cannot assume that the asynchronous fetch associated with dictMenuList is done yet. I look at this and wonder if it makes sense for TestViewController to initiate the fetch itself and then use dictMenuList in the completion handler. That's going to be easiest.
If you must initiate the asynchronous request from one view controller and then have another view controller be informed when that asynchronous request is done, then you might have to use some other pattern, such as notifications (e.g. use NSNotificationCenter, and have the singleton post notifications when the various requests are done, and then any view controller that needs to be informed of this fact can add themselves as observers for that notification).