Player Object Model
In the Player Model, I want to save the JSON response so that I will get any new computed properties in the future without changing the schema.
But here, I'm getting the error to save the json of type [String: Any].
Any alternative or recommendations...?
Any is not a supported value type of Map. Looking a the documentation for Map, which shows the definition
public final class Map<Key, Value>
value is a RealmCollectionValue can be one of the following types
This can be either an Object subclass or one of the following types:
Bool, Int, Int8, Int16, Int32, Int64, Float, Double, String, Data,
Date, Decimal128, and ObjectId (and their optional versions)
One option is to to use AnyRealmValue so it would look like this
class Player: Object {
let json = Map<String, AnyRealmValue>()
}
here's how to populate the json with a string and an int
let s: AnyRealmValue = .string("Hello")
let i: AnyRealmValue = .int(100)
let p = Player()
p.json["key 0"] = s
p.json["key 1"] = i
then to get back the values stored in the map:
for key in p.json {
let v = key.value
if let s = v.stringValue {
print("it's a string: \(s)")
} else if let i = v.intValue {
print("it's an int: \(i)")
}
}
and the output
it's a string: Hello
it's an int: 100
I'm very new to F# and trying to figure out the best way to convert between similar but different data objects while minimising duplication of code. I'm writing an app using the Fabulous F# MVU framework for Xamarin Forms.
I'm using sqlite-net-pcl for data persistence, which calls for an object when creating a database table and interacting with that table, and I have created an AuthorObject:
type AuthorObject() =
[<PrimaryKey>] // sqlite-net-pcl attribute
member val AuthorId = 0 with get, set
member val Username = "" with get, set
member val Token = "" with get, set
Fabulous makes use of record types for models and I have created an Author record type to pass around inside Fabulous:
type Author =
{ AuthorId: int
Username: string
Token: string }
But I also have to interact with a web service API library (written in C#) that returns an AuthorDetailDto object. AuthorDetailDto has the same named properties on it as above, plus more that I don't need to worry about. I cannot change the definition of AuthorDetailDto.
So it seems the constraints are currently that sqlite-net-pcl needs an object with members, Fabulous needs a record type, and I have this AuthorDetailDto object coming across from the service API client library that I can't change. I need to convert between these types and I initially was hoping that I could get away with just:
let convertToObject author = // Was hoping author param here could be generic in some way
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
But with the added complication of AuthorDetailDto, I'm finding I now need to do something like this to keep the compiler happy:
let convertAuthorDetailDtoToObject (dto: AuthorDetailDto) =
let obj = AuthorObject()
obj.AuthorId <- dto.AuthorId
obj.Username <- dto.Username
obj.Token <- dto.Token
obj
let convertAuthorToObject (author: Author) =
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
Here are some source files:
DomainModels.fs:
namespace MyApp.Mobile
module DomainModels =
type Author =
{ AuthorId: int
Username: string
Token: string }
Author.fs:
namespace MyApp.Mobile.Repository
open SQLite
open Client // Provides AuthorDetailDto.
open MyApp.Mobile.DomainModels
module Author =
type AuthorObject() =
[<PrimaryKey>]
member val AuthorId = 0 with get, set
member val Username = "" with get, set
member val Token = "" with get, set
let convertToObject author =
let obj = AuthorObject()
obj.AuthorId <- author.AuthorId
obj.Username <- author.Username
obj.Token <- author.Token
obj
let convertToModel (obj: AuthorObject) : Author =
{ AuthorId = obj.AuthorId
Username = obj.Username
Token = obj.Token }
let connect dbPath = async {
let db = SQLiteAsyncConnection(SQLiteConnectionString dbPath)
do! db.CreateTableAsync<AuthorObject>() |> Async.AwaitTask |> Async.Ignore
return db
}
let purgeLoggedInAuthor dbPath = async {
let! db = connect dbPath
do! db.ExecuteAsync "DELETE FROM AuthorObject" |> Async.AwaitTask |> Async.Ignore
}
let insertLoggedInAuthor dbPath author = async {
let! db = connect dbPath
do! db.InsertAsync (convertToObject author) |> Async.AwaitTask |> Async.Ignore
}
The initial call looks like this:
insertLoggedInAuthor dbPath loggedInAuthor // loggedInAuthor is an AuthorDetailDto.
The error is FS0001 This expression was expected to have type 'DomainModels.Author' but here has type 'AuthorDetailDto' due to both types being referenced in Author.fs.
I've tried messing around with inline and static member constraints, and attempting to define an interface, but due to Author needing to be a record type and not being able to change AuthorDetailDto, it doesn't seem like this approach is viable. Or perhaps I'm just not well versed enough in F#.
To those more familiar with F# than me, what is the best way to address this kind of situation to minimise code duplication?
I have a userInfo dictionary from a UILocalNotification. Is there an easy way to get the String value when using implicit unwrapping?
if let s = userInfo?["ID"]
Gives me a AnyObject that I have to cast to a string.
if let s = userInfo?["ID"] as String
Gives me an error about StringLiteralConvertable
Just didn't want to have to declare two variables to get a string - one literal for the unwrap and another var for the casted string.
Edit
Here is my method. This doesn't work either - I get (NSObject, AnyObject) is not convertible to String on the if statement.
for notification in scheduledNotifications
{
// optional chainging
let userInfo = notification.userInfo
if let id = userInfo?[ "ID" ] as? String
{
println( "Id found: " + id )
}
else
{
println( "ID not found" )
}
}
I don't have it in my question, but besides getting this way to work, I'd like to actually have
if let s = notification.userInfo?["ID"] as String
You want to use a condition cast using as?:
(Note: This works for Xcode 6.1. For Xcode 6.0, see below)
if let s = userInfo?["ID"] as? String {
// When we get here, we know "ID" is a valid key
// and that the value is a String.
}
This construct safely extracts a string from userInfo:
If userInfo is nil, userInfo?["ID"] returns nil due to optional chaining and the conditional cast returns a variable of type String? that has a value of nil. The optional binding then fails and the block is not entered.
If "ID" is not a valid key in the dictionary, userInfo?["ID"] returns nil and it proceeds like the previous case.
If the value is another type (like Int), then the conditional cast as? will return nil and it proceeds like the above cases.
Finally, if userInfo is not nil, and "ID" is a valid key in the dictionary, and the type of the value is a String, then the conditional cast returns an optional string String? containing the string. The optional binding if let then unwraps the String and assigns it to s which will have type String.
For Xcode 6.0, there is one additional thing you must do. You need to conditionally cast to NSString instead of to String because NSString is an object type and String is not. They apparently improved that handling in Xcode 6.1, but for Xcode 6.0 do the following:
if let s:String = userInfo?["ID"] as? NSString {
// When we get here, we know "ID" is a valid key
// and that the value is a String.
}
Finally, addressing your last point:
for notification in scheduledNotifications
{
if let id:String = notification.userInfo?["ID"] as? NSString
{
println( "Id found: " + id )
}
else
{
println( "ID not found" )
}
}
I've got a Dictionary that I've created where the key is a String and the value is a custom object called SectorCoordinate.
I just want to store the whole darn thing in NSUserDefaults, but when I do, Xcode says:
The type [String, SectorCoordinate] does not conform to protocol AnyObject
I'm really confused. I thought AnyObject was Swift's version of Objective-C's id and should be able to hold any object.
I've seen and attempted to implement a bunch of solutions that were targeted towards [String: String] dictionaries (e.g. JSON manipulations), but those didn't work, with similar errors. I even tried to break up the key-value-pairs, and I get the same error when trying to store even a single SectorCoordinate (which itself is just a struct with a bunch of Strings, Ints and dates in it) as an AnyObject.
Does anyone have any suggestions on how to store a semi-complex object and/or a dictionary thereof as an AnyObject? It seems like this should be a lot simpler.
The Apple documentation states about NSUserdefaults setObject:forKey: method:
The value parameter can be only property list objects: NSData,
NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and
NSDictionary objects, their contents must be property list objects.
See “What is a Property List?” in Property List Programming Guide.
Thus, for example, you can cast a Swift Dictionary [String : NSNumber] to a NSDictionary and save/retrieve it with NSUserDefaults just like this:
let dictionary = ["myKey" : NSNumber(int: 12)] as NSDictionary
NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey: "myDict") //[myKey : 12]
NSUserDefaults.standardUserDefaults().dictionaryForKey("myDict") //{[myKey : 12]}
But this is not possible for a Swift Dictionary of type [String : SectorCoordinate] where SectorCoordinate is a Swift Struct.
You can store any object in NSUserDefs, see NSUserDefaults Apple docs:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData. For more details, see Preferences and Settings
Programming Guide.
To have an object that can seamlessly convert to NSData, make sure your class conforms to NSCoding protocol (i.e. so it can be archived and unarchived). Swift also sometimes will want you class to conform to NSSecureCoding as well.
This saves a dictionary containing a home made structure to NSUserDefaults. however if you want to save more than just a little data you should use the file example I have also posted here.
import Cocoa
//The key to the dictionary is in the struct here as permanentTimeDateCode
struct CheckBookEntry{
var permanentTimeDateCode = String() //value here is also the key for the dictorary it must be a string
var amountOfTransaction = Double()
var category = String()
var payee = String()
var memo = String()
var checkNumber = String()
}
var checkBookEntryOne = CheckBookEntry(permanentTimeDateCode: "2015-02--06", amountOfTransaction: 20.00, category: "Properietor", payee: "Balance Forward", memo: "No memo", checkNumber: "00000")
var checkBookEntryTwo = CheckBookEntry(permanentTimeDateCode: "2015-02--05", amountOfTransaction: -15.00, category: "Reference", payee: "Bookstore", memo: "No memo", checkNumber: "00001")
//A dictionary with the date as the key and a CheckBookEntry struct as the value.
var myCheckBookEntry:Dictionary = [String :CheckBookEntry ]()
myCheckBookEntry["2015-02--06"] = checkBookEntryOne
myCheckBookEntry["2015-02--07"] = checkBookEntryTwo
print(myCheckBookEntry)
//To save these set up an array of dictionaries
var checkEntryArrayOfDictionaries:[[String:AnyObject]] = []
//your struct is no an object that can be saved so it needs to be converted.
//use the variable names from our struct CheckBookEntry as the keys
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryOne.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryOne.amountOfTransaction, "catergory" : checkBookEntryOne.category, "payee" : checkBookEntryOne.payee, "memo" : checkBookEntryOne.memo, "checkNumber": checkBookEntryOne.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryTwo.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryTwo.amountOfTransaction, "catergory" : checkBookEntryTwo.category, "payee" : checkBookEntryTwo.payee, "memo" : checkBookEntryTwo.memo, "checkNumber": checkBookEntryTwo.checkNumber])
print("//______________printing checkEntryArrayOfDictionaries----//")
print(checkEntryArrayOfDictionaries)
//Save The values
NSUserDefaults().setObject(checkEntryArrayOfDictionaries, forKey: "aCheckbook")
//recovering the struct
//The dictionary to recover to PLAYGROUND
var myCheckBookEntry2:Dictionary = [String :CheckBookEntry ]()
//A SINGLE INSTANCE OF THE STRUCT TO SAVE EACH TO.
var anIndividualCheckBookEntry = CheckBookEntry()
//RECOVER THE SAVED ENTRY
if let checkEntry2 = NSUserDefaults().arrayForKey("aCheckbook") as? [[String:AnyObject]] {
for key in checkEntry2{
anIndividualCheckBookEntry.permanentTimeDateCode = key["permanentTimeDateCode"]! as! String
anIndividualCheckBookEntry.amountOfTransaction = key["amountOfTransaction"]! as! Double
anIndividualCheckBookEntry.category = key["catergory"]! as! String
anIndividualCheckBookEntry.payee = key["payee"]! as! String
anIndividualCheckBookEntry.memo = key["memo"]! as! String
anIndividualCheckBookEntry.checkNumber = key["checkNumber"]! as! String
//LOAD THIS SINGLE ENTRY INTO OUR NEW DICTIONARY
myCheckBookEntry2[ anIndividualCheckBookEntry.permanentTimeDateCode] = anIndividualCheckBookEntry
}
print("//---------------------------------//")
print(myCheckBookEntry2)
}
I have answered how to use NSUserDefaults elsewhere on this page however if you have any data at all don't use NSUserDefaults. This version saves a array of dictionaries including your own homemade struct to FILE and recovers them. This can be pasted into a playground for testing. In fact, in the NSUserDefaults version a two more check book entries would crash the save in user defaults.
import Cocoa
//The key to the dictionary is in the struct here as permanentTimeDateCode
struct CheckBookEntry{
var permanentTimeDateCode = String() //value here is also the key for the dictorary it must be a string
var amountOfTransaction = Double()
var category = String()
var payee = String()
var memo = String()
var checkNumber = String()
}
var checkBookEntryOne = CheckBookEntry(permanentTimeDateCode: "2015-02--06", amountOfTransaction: 20.00, category: "Properietor", payee: "Balance Forward", memo: "No memo", checkNumber: "00000")
var checkBookEntryTwo = CheckBookEntry(permanentTimeDateCode: "2015-02--05", amountOfTransaction: -15.00, category: "Reference", payee: "Bookstore", memo: "No memo", checkNumber: "00001")
var checkBookEntryThree = CheckBookEntry(permanentTimeDateCode: "2015-02--08", amountOfTransaction: -5.00, category: "Dinning", payee: "Moe's", memo: "Good Eats", checkNumber: "00003")
//A dictionary with the date as the key and a CheckBookEntry struct as the value.
var myCheckBookEntry:Dictionary = [String :CheckBookEntry ]()
myCheckBookEntry["2015-02--06"] = checkBookEntryOne
myCheckBookEntry["2015-02--07"] = checkBookEntryTwo
myCheckBookEntry["2015-02--08"] = checkBookEntryThree
print(myCheckBookEntry)
//To save these set up an array of dictionaries
var checkEntryArrayOfDictionaries:[[String:AnyObject]] = []
//your struct is no an object that can be saved so it needs to be converted.
//use the variable names from our struct CheckBookEntry as the keys
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryOne.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryOne.amountOfTransaction, "catergory" : checkBookEntryOne.category, "payee" : checkBookEntryOne.payee, "memo" : checkBookEntryOne.memo, "checkNumber": checkBookEntryOne.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryTwo.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryTwo.amountOfTransaction, "catergory" : checkBookEntryTwo.category, "payee" : checkBookEntryTwo.payee, "memo" : checkBookEntryTwo.memo, "checkNumber": checkBookEntryTwo.checkNumber])
checkEntryArrayOfDictionaries.append( ["permanentTimeDateCode" : checkBookEntryThree.permanentTimeDateCode, "amountOfTransaction" : checkBookEntryThree.amountOfTransaction, "catergory" : checkBookEntryThree.category, "payee" : checkBookEntryThree.payee, "memo" : checkBookEntryThree.memo, "checkNumber": checkBookEntryThree.checkNumber])
print("//______________printing checkEntryArrayOfDictionaries----//")
print(checkEntryArrayOfDictionaries)
//Save The values
NSUserDefaults().setObject(checkEntryArrayOfDictionaries, forKey: "aCheckbook")
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let pathLocationString:String = paths[0] as String
let checkbookFile:String = pathLocationString.stringByAppendingString("/aCheckbook")
print(checkbookFile)
if !NSFileManager.defaultManager().fileExistsAtPath(checkbookFile) {
print("files exists or will exist")
NSFileManager.defaultManager().createFileAtPath(checkbookFile, contents: nil, attributes: nil)
}
NSKeyedArchiver.archiveRootObject(checkEntryArrayOfDictionaries,
toFile: checkbookFile)
//The dictionary to recover to PLAYGROUND
var myCheckBookEntry2:Dictionary = [String :CheckBookEntry ]()
//A SINGLE INSTANCE OF THE STRUCT TO SAVE EACH TO.
var anIndividualCheckBookEntry = CheckBookEntry()
let path2 = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let myStringDictionaryArray:String = path2[0] as String
let arrayDictionaryFilePath:String = myStringDictionaryArray.stringByAppendingString("/aCheckbook")
print(arrayDictionaryFilePath)
if NSFileManager.defaultManager().fileExistsAtPath(arrayDictionaryFilePath) {
let dictionaryFileArray =
NSKeyedUnarchiver.unarchiveObjectWithFile(arrayDictionaryFilePath)
as! [Dictionary <String,AnyObject> ]
var x = dictionaryFileArray[0]
var y = dictionaryFileArray[1]
var z = dictionaryFileArray[2]
print("\(x) \(y) \(z)")
var myDictionaryX = x as! [String : AnyObject]
var myDictionaryY = y as! [String : AnyObject]
var myDictionaryZ = z as! [String : AnyObject]
}
print("//---------------------------------//")
Is it possible to store closures in dictionaries (how we could store ObjC blocks in dictionaries)? Example:
data = [String:AnyObject]()
data!["so:c0.onSelection"] = {() in
Debug.log(.Debug, message: "Hello, World!")
}
You can, but with some restrictions. First of all, function types don't inherit from AnyObject and don't share a common base class. You can have a dictionary [String: () -> Void] and [String: (String) -> Int], but they can't be stored in the same dictionary.
I also had to use a typealias to define the dictionary so that swift would parse correctly. Here's an example based off of your snippet.
typealias myClosure = () -> Void
var data: [String: myClosure]? = [String: myClosure]()
data!["so:c0.onSelection"] = {() -> Void in
Debug.log(.Debug, message: "Hello, World!")
}
I have a different approach
I create a "holder" class to hold your closures something like this:
typealias SocialDownloadImageClosure = (image : UIImage?, error: NSError?) -> ()
typealias SocialDownloadInformationClosure = (userInfo : NSDictionary?, error: NSError?) -> ()
private class ClosureHolder
{
let imageClosure:SocialDownloadImageClosure?
let infoClosure:SocialDownloadInformationClosure?
init(infoClosure:SocialDownloadInformationClosure)
{
self.infoClosure = infoClosure
}
init(imageClosure:SocialDownloadImageClosure)
{
self.imageClosure = imageClosure
}
}
then i make the dictionary like this:
var requests = Dictionary<String,ClosureHolder>()
Now to add a closure to the dictionary just do this:
self.requests["so:c0.onSelection"] = ClosureHolder(completionHandler)
Connor is correct, I did try many ways to store variables and closures in the same dictionary, but I failed and couldn't parse it out, the swift decompiler will throw the error:
"Command failed due to signal: Segmentation fault: 11" (the hell is it?!)
For example:
//This won't work
var params:[String: Any] = ["x":100, "onFoundX": {println("I found X!")}]
if var onFoundX: (()->Void) = params["onFoundX"] as? (()->Void) {
onFoundX()
}
//This should work by separate into 2 dictionaries and declare the "typealias" obviously
var params:[String: Any] = ["x":"100"}]
var events:[String: (()->Void)] = ["onFoundX": {println("I found X!")]
if var onFoundX: (()->Void) = events["onFoundX"] as? (()->Void) {
onFoundX() // "I found X!"
}
if var x = events["x"] as? String {
println(x) // 100
}
I hope Swift will allow this to happen in the future..
Cheers!
This simple example helped me understand a bit more:
//Init dictionary with types (i.e. String type for key, Closure type for value):
var myDictionary: [String: ()->(String)] = [:]
//Make a closure that matches the closure signature above and assign to variable (i.e. no parameter and returns a String):
let sayHello: () -> (String) = {
return "Hello!"
}
//Add closure to dictionary with key:
myDictionary["myFunc"] = sayHello
//Access closure by known key and call it:
myDictionary["myFunc"]!() //"Hello!"