Learning to work with Declares

Hello,

(sorry, long post ahead)

The last few days, I’ve been busy setting my first steps into using declares.
I needed a class for saving and loading Preferences so I thought that would be a good opportunity to learn using declares.
So far, I managed to get things working (using method overloading + operator_lookups) for Strings, Integers, Doubles, Booleans, Colors and String Arrays.

What I would like to add are both Dictionaries and Pictures (and maybe even Variants) but I can’t seem to figure it out.

To give 2 examples of the how I did things, here is the loading and saving of a String:

Sub SaveKey(Key As String, Value As String)
  // Save Key - String
  Key = Lowercase(Key)
  If Key = "" Then Return
  Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults"(NSUserDefaultsClass As Ptr) As Ptr
  Declare Function NSClassFromString Lib FoundationLib(ClassName As CFStringRef) As Ptr
  Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:"(NSUserDefaults As Ptr, value As CFStringRef, Key As CFStringRef)
  Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults"))
  setObject(standardUserDefaultsPtr, Value, Key)
End Sub

Function LoadKey(Key As String, DefaultValue As String) As String
  // Load Key - String
  Key = Lowercase(Key)
  If Me.HasKey(Key) Then
    Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults"(NSUserDefaultsClass As Ptr) As Ptr
    Declare Function NSClassFromString Lib FoundationLib(ClassName As CFStringRef) As Ptr
    Declare Function stringForKey Lib FoundationLib Selector "stringForKey:"(NSUserDefaults As Ptr, Key As CFStringRef) As CFStringRef
    Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults"))
    Return stringForKey(standardUserDefaultsPtr, Key)
  Else
    Return DefaultValue
  End If
End Function

…and here is the loading and saving of a String Array:

Sub SaveKey(Key As String, Value() As String)
  // Save Key - String() Array
  Key = Lowercase(Key)
  If Key = "" Then Return
  Declare Function alloc Lib FoundationLib Selector "alloc"(NSClass As Ptr) As Ptr
  Declare Function init Lib FoundationLib Selector "init"(NSClass As Ptr) As Ptr
  Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults"(NSUserDefaultsClass As Ptr) As Ptr
  Declare Function NSClassFromString Lib FoundationLib(ClassName As CFStringRef) As Ptr
  Declare Sub addObject Lib FoundationLib Selector "addObject:"(NSMutableArrayClass As Ptr, anObject As CFStringRef)
  Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:"(NSUserDefaults As Ptr, NSArray As Ptr, Key As CFStringRef)
  Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults"))
  Static mutableArrayClassPtr As Ptr = NSClassFromString("NSMutableArray")
  Dim mutableArrayPtr As Ptr = init(alloc(mutableArrayClassPtr))
  For i As Integer = 0 To Value.Ubound
    addObject(mutableArrayPtr, Value(i))
  Next
  setObject(standardUserDefaultsPtr, mutableArrayPtr, Key)
End Sub

Function LoadKey(Key As String, DefaultValue() As String) As String()
  // Load Key - String() Array
  Key = Lowercase(Key)
  If Me.HasKey(Key) Then
    Declare Function count Lib FoundationLib Selector "count" (NSArray As Ptr) As UInt32
    Declare Function objectAtIndex Lib FoundationLib Selector "objectAtIndex:" (NSArray As Ptr, index As UInt32) As CFStringRef
    Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr
    Declare Function NSClassFromString Lib FoundationLib(ClassName As CFStringRef) As Ptr
    Declare Function stringArrayForKey Lib FoundationLib Selector "stringArrayForKey:" (NSUserDefaults As Ptr, Key As CFStringRef) As Ptr
    Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults"))
    Dim stringArrayPtr As Ptr = stringArrayForKey(standardUserDefaultsPtr, Key) 
    Dim Count As Integer = Count(stringArrayPtr)
    Dim ReturnArray() As String
    For i As Integer = 0 To Count - 1
      ReturnArray.Append(objectAtIndex(stringArrayPtr, i))
    Next
    Return ReturnArray
  Else
    Return DefaultValue
  End If
End Function

Both work fine and I think I did those correctly.

I’m currently stuck on saving a Dictionary.
I can save an empty Dictionary but I have no idea on how to get keys and values in there.
This is what I have so far:

Sub SaveKey(Key As String, Value As Dictionary)
  // Save Key - Dictionary
  Key = Lowercase(Key)
  If Key = "" Then Return
  Declare Function alloc Lib FoundationLib Selector "alloc"(NSClass As Ptr) As Ptr
  Declare Function init Lib FoundationLib Selector "init"(NSClass As Ptr) As Ptr
  Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults"(NSUserDefaultsClass As Ptr) As Ptr
  Declare Function NSClassFromString Lib FoundationLib(ClassName As CFStringRef) As Ptr
  Declare Sub addObject Lib FoundationLib Selector "addObject:"(NSMutableArrayClass As Ptr, anObject As CFStringRef)
  Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:"(NSUserDefaults As Ptr, NSDictionary As Ptr, Key As CFStringRef)
  Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults"))
  Static mutableDictionaryClassPtr As Ptr = NSClassFromString("NSMutableDictionary")
  Dim mutableDictionaryPtr As Ptr = init(alloc(mutableDictionaryClassPtr))
   
  // For i As Integer = ..etc 
  // addObject(mutableDictionaryPtr, ...) ?
  // Next
  
  setObject(standardUserDefaultsPtr, mutableDictionaryPtr, Key)
End Sub

Can I somehow hand over the passed Dictionary or do I need to for-next the key-values in?

And as for saving a picture, does anyone have any pointers in the right direction for that?

You will need to use an NSMutableDictionary and “setObject:forKey:” to set all of the values in the dictionary. You might want to limit the dictionary to only containing strings or something to make this easier, otherwise the serialization/deserialization becomes more challenging. You can save it to NSUserDefaults with the same “setObject:forKey:” selector.

You could go one of two ways with this. You can convert the picture to a string with GetData and then do EncodeBase64 on it and save as a string to NSUserDefaults. Or you could convert the picture to an NSImage as shown in MacOSLib, get the NSData object from the NSImage, and then save the NSData object with “setObject:forKey:”. Both should do the trick.

okay… So here’s what I would suggest.

#1 Create a method that takes a Xojo object and converts it to NSObjects, numbers to NSNumber, string to NSString, and so on.
#2 Create a method for converting backwards to Xojo objects.

Now Xojo’s variant will help figure out what object you’re converting to a instanceID and with Obj-C you can get the classtype of a instanceID and convert that back to a Xojo object.

Once you can covert most data types, it’s easy to then convert a whole dictionary both ways. Arrays are a lot harder, but can still be done.

I’m sure it’s a stupid question but is a Variant itself an object that somehow can be stored and retrieved?

No. You need to handle each type separately, like this:

Select Case myVariant.Type Case Variant.TypeInt32 Dim value As Int32 = myVariant.Int32Value // do something with it Case Variant.TypeInt32 + Variant.TypeArray Dim values() As Int32 = myVariant // do something with it Case Variant.TypeDouble ...