The book say: ‘the proxy object will ask the Playlist object if it has countOfSongs. If Playlist does, the proxy object will access the Playlist’s property and return the value’ - Figure 11.3
I try this in Xcode/project/ not in playground but the code is not working. The return value is always from .count. Perhaps is due to the bridging from swift Array to NSmMutableArray ?
Thank for help
Here my simple code:
[code]
import Foundation
class Song: NSObject {
var titolo = String()
var autore = String()
}
//create two Song objects
let canzoneA = Song()
canzoneA.titolo = "Piove"
canzoneA.autore = "Jovannotti"
let canzoneB = Song()
canzoneB.titolo = "la va in campagna"
canzoneB.autore = “Pippo”
class PlayList: NSObject{
var songs = Song
var countOfSongs = 9 //this is the property’s value that I expect from proxy !!
}
//create a playlist and add a song
var playlist = PlayList()
playlist.songs.append(canzoneA)
//verify
println(playlist.songs.count) // = 1 ok
println(playlist.countOfSongs) // = 9 ok
let arrayProxy = playlist.mutableArrayValueForKey(“songs”)
Unfortunately, the authors occasionally use fake examples, which lack the supporting code that is needed to get them to run. And because they are fake examples, they can’t easily be checked for correctness. The authors do say:
…and that is exactly backwards. And, because a lot of the book is copied from the 4th edition, that is probably wrong in the 4th edition, too. Congratulations, you discovered a major mistake in the book that has gone unnoticed for years.
It turns out that if the Playlist class has a property named “songs”, then countOfSongs() will NOT be called–it’s only when the proxy object fails to find a songs property in Playlist that countOfSongs() will be called. Whaaa?? What that means is that your code should pretend that there is a songs property in Playlist but you should not actually have one: you should name the songs array something like array or data or theSongs.
However, they are a bit convoluted. The following is what I have been able to surmise:
When you write:
let arrayProxy = playlist.mutableArrayValueForKey("songs")
the mutableArrayValueForKey(_ method searches the Playlist class and if specially named insert and remove methods are found, then a proxy object is returned. Subsequently, if you call normal array methods on the proxy object where the methods insert or remove elements:
the proxy object will turn around and call the special insert or remove methods in Playlist. However, those are the ONLY specialized traits that the proxy object possesses. All other method calls or attempted property accesses, e.g. arrayProxy.count, are forwarded to the original array. As far as I can tell, the forwarding is accomplished by:
First using the KVC method playlist.valueForKey(“songs”) to get the original array.
Then calling the method or accessing the property on the original array.
As a result, when a songs property exists in the Playlist class, that process will always return songs.count and never call playlist.countOfSongs().
To get countOfSongs() to execute, you have to remove any property named songs in the Playlist class. Then when valueForKey(“songs”) is unable to find the songs property, the valueForKey( method next searches the Playlist class for the methods countOfSongs() and objectInSongsAtIndex(:), and if they are present, the docs say that valueForKey(_ returns a proxy object. Whaaa? Another proxy object??! Yes. The second proxy object will respond to all NSArray methods (i.e. array methods that are non-mutating), and the second proxy object has the special trait that if you call count on it, it will turn around and call playlist.countOfSongs().
As far as I can tell, the first proxy object must store the second proxy object somewhere, and when methods that do not insert or remove objects are called on the first proxy object, the first proxy object forwards the method calls to the second proxy object.
In any case, here is a full working example:
[code]import Foundation
class Song {
var title = “Hello”
}
class PlayList: NSObject {
private var array: [Song] = []
//Apparently, countOfSongs can be a method or a property:
func countOfSongs() -> Int {
println("countOfSongs() called")
return array.count
}
/*
//Or this works too:
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.removeObjectAtIndex(0)
println(arrayProxy.count)
//Some extra stuff:
let secondArrayProxy: AnyObject? = playlist.valueForKey(“songs”)
println(secondArrayProxy!.count) //The second proxy object works as described and calls playlist.countOfSongs()
//Does the secondArrayProxy have the same traits as the first proxy object?
//secondArrayProxy?.addObject(Song()) //=>Error unrecognized selector, i.e. no such method[/code]
I had problems running the code in a playground:
If countOfSongs() returns a number larger than the actual array size, I got errors in my playground. That tripped me up for hours. Somewhere some code loops over the array to get the elements in order to output them in the playground, and if the countOfSongs() method returns a number that is greater than the size of the array, the playground will no longer execute because of an out of bounds error. I’ve decided I’m not using playgrounds anymore. Instead, I created a Command Line Project, and checked Swift for the language, and now I have essentially the same thing as a playground with better error reporting.