ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでシリアライズする
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでシリアライズする
はじめに
以下は、Xojo Cocoaビルドについての話題です。
Xojoは(当然ながら)標準でファイルIOの仕組みを持っていますが、変数の書き出しと読み込みは、TextOutputStream/TextInputStreamやBinaryStreamを使って自前で実装する必要があります。
一方、Cocoaは標準でシリアライズの仕組みを持っていますので、使えるか、調べてみました。
なお検証には、Xojo 2015 Release 4.1を用いています。(Mac mini mid 2010 + OS X 10.11.3 El Capitan)
方針
Cocoaのシリアライゼーションには、いくつかのAPIがありますが、いずれもプロパティリスト方式をとっています。
プロパティリストにはバイナリとテキストがあり、テキストはXML形式となっています。
ざっと調べたところ、以下が見つかりました。(他にもあるかも。)
これらを踏まえて今回は、NSPropertyListSerializationを使ったシンプルなものと、NSKeyedArchiverを使った、より実用的なものを試してみることにしました。
- NSKeyedArchiver
・対象は、NSCodingを実装するオブジェクト(NSString, NSDictionary, NSArray等は対応済。自作オブジェクトも自分で実装すれば利用可)
・ファイル形式:バイナリ
- NSPropertyListSerialization
・対象は、NSData, NSString, NSArray, NSDictionary, NSDate, NSNumber
・ファイル形式:テキストまたはバイナリ
- NSDictionaryやNSArrayのwriteToFile
・対象は、自分自身
・ファイル形式:テキスト
それぞれの仕様は以下の通りとしました。
例1(NSPropertyListSerialization)
例2(NSKeyedArchiver)
- シリアライズ対象は、NSDictionaryにセットした変数
- ファイルの形式はテキスト(パラメータを変更すれば、バイナリも可)
- 書き出しの手順は、
(1) NSDictionaryに変数をセット
(2) NSPropertyListSerializationのpropertyListWithData:options:format:error:でシリアライズ(戻り値はNSData型)
(3) NSDataのwriteToFile:atomically:でファイルに書き出し
- 読み込みの手順は、
(1) NSDataのdataWithContentsOfFile:でファイルから読み込み(戻り値はNSData型)
(2) NSPropertyListSerializationのpropertyListWithData:options:format:error:でNSDataからNSDictionaryに変換
(3) NSDictionaryから変数を取り出す
- シリアライズ対象は、複数の配列変数(ここでは整数型1個と文字列型2個)とDictionary
- ファイルの形式はバイナリとテキストの選択式
- 書き出しの手順は、
(1) 整数配列と文字列配列をNSArray、DictionaryをNSDictionaryに変換し、それらを要素とするNSArrayを作成
(2a) NSKeyedArchiverのarchiveRootObject:toFile:でファイルに書き出し(バイナリ時)
(2b) NSArrayのwriteToFile:atomically:でファイルに書き出し(テキスト時)- 読み込みの手順は、
(1a) NSKeyedUnarchiverのunarchiveObjectWithFile:でファイルから読み込み(バイナリ時。戻り値はNSArray型)
(1b) NSArrayのinitWithContentsOfFile:でファイルから読み込み(テキスト時。戻り値はNSArray型)
(2) NSArrayのobjectAtIndex:でNSArrayとNSDictionaryを取り出し、配列(整数型と文字列型)またはDictionaryに変換- 本題とは直接関係ないが、バージョン名もシリアライズ対象として、保存する変数が追加/変更になった場合に備える
- 本題とは直接関係ないが、シリアライズ処理をクラス化して再利用性を高める
Xojoでの実装(例1)
実行してみたところ、シリアライズが機能することを確認しました。
- Xojoで新規プロジェクトを作成
- Window1にPushButtonを二つ置き、一方のActionイベントにLoadPlist()、他方にSavePlist()と記述
- 以下をWindow1のメソッドに追加
メソッド名: LoadPlist 引数: なし 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim data As Ptr = NSClassFromString("NSData") // クラスメソッドなので、まずNSDataクラスを取得 Declare Function dataWithContentsOfFile Lib "Cocoa" Selector "dataWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As ptr data = dataWithContentsOfFile(data, "/Users/hoge/Desktop/Test.plist") // ファイルパスの取得/指定方法は必要に応じて変更のこと Dim option As Integer = 0 Dim format As New MemoryBlock(4) Dim err As New MemoryBlock(4) Dim pls As Ptr = NSClassFromString("NSPropertyListSerialization") // クラスメソッドなので、まずNSPropertyListSerializationクラスを取得 Declare Function propertyListWithData Lib "Cocoa" Selector "propertyListWithData:options:format:error:" (receiver As Ptr, data As Ptr, option As Integer, format As Ptr, err As Ptr) As Ptr Dim plist As Ptr = propertyListWithData(pls, data, option, format, err) // if err.UInt32Value(0) <> 0 then // エラー時 Declare Function domain Lib "Cocoa" Selector "domain" (receiver As Ptr) As CFStringRef Dim errDomain As String = domain(err.Ptr(0)) Declare Function code Lib "Cocoa" Selector "code" (receiver As Ptr) As Integer Dim errCode As Integer = code(err.Ptr(0)) Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim errUserInfo As Ptr = userInfo(err.Ptr(0)) Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef Dim errDesc As CFStringRef = objectForKey(errUserInfo, "NSDebugDescription") MessageDlg(errDomain+" : "+str(errCode), errDesc) else Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim cnt As Integer = count(plist) Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef Dim nam As CFStringRef = objectForKey(plist, "key1") // key1は保存時に指定したキー msgBox nam end if
- 以下をWindow1のメソッドに追加
注)formatの値を、メソッド名: SavePlist 引数: なし 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim dic As Ptr = NSClassFromString("NSMutableDictionary") // クラスメソッドなので、まずNSMutableDictionaryクラスを取得 Declare Function getDictionary Lib "Cocoa" Selector "dictionary" (receiver As Ptr) As Ptr // Return NSMutableDictionary* dic=getDictionary(dic) Declare Sub addObjectForKey Lib "Cocoa" Selector "setObject:forKey:" (receiver As Ptr, obj As CFStringRef, key As CFStringRef) addObjectForKey(dic, "sample2","key1") // テストデータ Dim format As Integer = 100 // 注:保存形式にテキストを選択しているので、kCFPropertyListXMLFormat_v1_0を指定するべきだが、ここではその値である100を直接指定している。 Dim err As New MemoryBlock(4) // 注:エラー処理は省略している。 Dim plist As Ptr = NSClassFromString("NSPropertyListSerialization") // クラスメソッドなので、まずNSPropertyListSerializationクラスを取得 Declare Function dataFromPropertyList Lib "Cocoa" Selector "dataFromPropertyList:format:errorDescription:" (receiver As Ptr, obj As Ptr, format As Integer, error As Ptr) As Ptr Dim data As Ptr = dataFromPropertyList(plist, dic, format, err) Declare Sub writeToFile Lib "Cocoa" Selector "writeToFile:atomically:" (receiver As Ptr, path As CFStringRef, flag As Boolean) writeToFile(data, "Users/hoge/Desktop/Test.plist",true) // ファイルパスの取得/指定方法は必要に応じて変更のこと
kCFPropertyListBinaryFormat_v1_0
( = 200)にすればバイナリで保存可。
Xojoでの実装(例2)
- Xojoで新規プロジェクトを作成
- 新規クラスを作成(名前は、ここではデフォルトの「Class1」とした。)
- 以下をClass1のメソッドに追加
メソッド名: AddArrayInteger 引数: aryXojo() As Integer 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim ary As Ptr = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return NSMutableArray* ary = getArray(ary) Dim num As Ptr = NSClassFromString("NSNumber") // クラスメソッドなので、まずNSNumberクラスを取得 Declare Function numberWithInteger Lib "Cocoa" Selector "numberWithInteger:" (receiver As Ptr, val As Integer) As Ptr // Return NSNumber* Declare Sub addObjectPtr Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr) Dim i As Integer for i=0 to Ubound(aryXojo) addObjectPtr(ary, numberWithInteger(num, aryXojo(i))) // 配列の値は整数だが、NSArrayにセットする際にはオブジェクト化する next addObjectPtr(OutAry, ary) // 出力用Arrayに追加
- 以下をClass1のメソッドに追加
メソッド名: AddArrayString 引数: aryXojo() As String 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim ary As Ptr = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return NSMutableArray* ary = getArray(ary) Declare Sub addObjectString Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef) Dim i As Integer for i=0 to Ubound(aryXojo) addObjectString(ary, aryXojo(i)) // 文字列はそのままセットできる next Declare Sub addObjectPtr Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr) addObjectPtr(OutAry, ary) // 出力用Arrayに追加
- 以下をClass1のメソッドに追加
メソッド名: AddDictionary 引数: dicXojo() As Dictionary 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim ary As Ptr = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return NSMutableArray* ary=getArray(ary) Dim dic As Ptr = NSClassFromString("NSMutableDictionary") // クラスメソッドなので、まずNSMutableDictionaryクラスを取得 Declare Function getDictionary Lib "Cocoa" Selector "dictionary" (receiver As Ptr) As Ptr // Return NSMutableDictionary* Declare Sub setObjectForKey Lib "Cocoa" Selector "setObject:forKey:" (receiver As Ptr, obj As CFStringRef, key As CFStringRef) Declare Sub addObjectPtr Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As Ptr) Dim i, j As Integer Dim val As String Dim dic2 As Ptr for i=0 to Ubound(dicXojo) dic2 = getDictionary(dic) for j=0 to 31// 最後が31なのは、キーが31までのため。 if dicXojo(i).HasKey(j) then val = dicXojo(i).Value(j) setObjectForKey(dic2, val, Str(j)) // Dictionaryのキーは整数だが、NSDictionaryにセットする際には文字列化する end if next addObjectPtr(ary, dic2) next addObjectPtr(OutAry, ary) // 出力用Arrayに追加
- 以下をClass1のメソッドに追加
メソッド名: InitArray 引数: なし 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr OutAry = NSClassFromString("NSMutableArray") // クラスメソッドなので、まずNSMutableArrayクラスを取得 Declare Function getArray Lib "Cocoa" Selector "array" (receiver As Ptr) As Ptr // Return NSMutableArray* OutAry = getArray(OutAry) Declare Sub addObjectString Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef) addObjectString(OutAry, "ver1") // バージョン番号を出力用Arrayに追加(ここでは文字列としたが、区別できるならなんでも良い)
- 以下をClass1のメソッドに追加
注)上記ではコメントアウトによる選択式となっていますが、必要ならスイッチで切り換える等して下さい。メソッド名: LoadData 引数: path As String 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Dim keyUarc As Ptr = NSClassFromString("NSKeyedUnarchiver") // クラスメソッドなので、まずNSKeyedUnarchiverクラスを取得 Declare Function unarchiveObjectWithFile Lib "Cocoa" Selector "unarchiveObjectWithFile:" (receiver As Ptr, path As CFStringRef) As Ptr Dim idx As Ptr = unarchiveObjectWithFile(keyUarc, path) // ファイルから読み込み 'Dim ary As Ptr = NSClassFromString("NSArray") // クラスメソッドなので、まずNSArrayクラスを取得 'Declare Function alloc Lib "Cocoa" selector "alloc" (receiver As Ptr) As Ptr 'Declare Function initWithContentsOfFile Lib "Cocoa" selector "initWithContentsOfFile:" (receiver As Ptr, path As CFStringRef) As Ptr 'Dim idx As Ptr = initWithContentsOfFile(alloc(ary), path) return idx
- 以下をClass1のメソッドに追加
メソッド名: RestoreArrayInteger 引数: dat As Ptr, idx As Integer, aryXojo() As Integer 戻り値型: Integer() Declare Function objectAtIndexPtr Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr Dim ary As Ptr = objectAtIndexPtr(dat, idx) // Return NSArray* Dim num As Ptr Declare Function intValue Lib "Cocoa" Selector "intValue" (receiver As Ptr) As Integer Dim i As Integer for i=0 to Ubound(aryXojo) num = objectAtIndexPtr(ary, i) // この時点ではオブジェクト aryXojo(i) = intValue(num) // 整数に戻す next return aryXojo
- 以下をClass1のメソッドに追加
メソッド名: RestoreArrayString 引数: dat As Ptr, idx As Integer, aryXojo() As String 戻り値型: String() Declare Function objectAtIndexPtr Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr Dim ary As Ptr = objectAtIndexPtr(dat, idx) // Return NSArray* Declare Function objectAtIndexString Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As CFStringRef Dim i As Integer for i=0 to Ubound(aryXojo) aryXojo(i) = objectAtIndexString(ary, i) // 文字列はそのままセット next return aryXojo
- 以下をClass1のメソッドに追加
メソッド名: RestoreDictionary 引数: dat As Ptr, idx As Integer, dicXojo() As Dictionary 戻り値型: Dictionary() Declare Function objectAtIndexPtr Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As Ptr Dim ary As Ptr = objectAtIndexPtr(dat, idx) // Return NSDictionary* Declare Function count Lib "Cocoa" Selector "count" (receiver As Ptr) As Integer Dim imax As Integer = count(ary) Declare Function allKeys Lib "Cocoa" Selector "allKeys" (receiver As Ptr) As Ptr Declare Function objectAtIndexString Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As CFStringRef Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As CFStringRef Dim i, j, jmax, keyV As Integer Dim key As String Dim keys, dic As Ptr for i=0 to imax-1 ReDim dicXojo(i) dicXojo(i) = new Dictionary dic = objectAtIndexPtr(ary, i) jmax = count(dic) keys = allKeys(dic) for j=0 to jmax-1 key = objectAtIndexString(keys, j) // キーの取得 keyV = Val(key) // キーを整数に戻す dicXojo(i).Value(keyV) = objectForKey(dic, key) // キーを指定して、取得した値をセット next next return dicXojo
- 以下をClass1のメソッドに追加
メソッド名: RestoreVersion 引数: idx As Ptr 戻り値型: String Declare Function objectAtIndexString Lib "Cocoa" Selector "objectAtIndex:" (receiver As Ptr, idx As Integer) As CFStringRef Dim str As String = objectAtIndexString(idx, 0) return str
- 以下をClass1のメソッドに追加
注)上記ではコメントアウトによる選択式となっていますが、必要ならスイッチで切り換える等して下さい。メソッド名: SaveData 引数: path As String 戻り値型: なし // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // バイナリ形式の場合 Dim keyArc As Ptr = NSClassFromString("NSKeyedArchiver") // クラスメソッドなので、まずNSKeyedArchiverクラスを取得 Declare Sub archiveRootObjectToFile Lib "Cocoa" Selector "archiveRootObject:toFile:" (receiver As Ptr, obj As Ptr, path As CFStringRef) archiveRootObjectToFile(keyArc, OutAry, path) // ファイルに保存 // テキスト形式の場合 'Declare Sub writeToFile Lib "Cocoa" Selector "writeToFile:atomically:" (receiver As Ptr, path As CFStringRef, flag As Boolean) 'writeToFile(OutAry, path,true) // ファイルに保存
- 以下をClass1のプロパティに追加
プロパティ名: OutAry データ型: Ptr 標準値: なし
- Window1にPushButtonを置き、以下をActionイベントに追加
InitData() Dim cls As Class1 = new Class1 cls.InitArray() // 初期化 cls.AddArrayInteger(PrefInteger1) // 整数型配列変数 cls.AddArrayString(PrefString1) // 文字列型配列変数1 cls.AddArrayString(PrefString2) // 文字列型配列変数2 cls.AddDictionary(DataDict1) // Dictionary cls.SaveData("Users/hoge/Desktop/Test.dat") // ファイルパスの取得/指定方法は必要に応じて変更のこと
- Window1にPushButtonを置き、以下をActionイベントに追加
Dim cls As Class1 = new Class1 Dim dat As Ptr = cls.LoadData("Users/hoge/Desktop/Test.dat") // ファイルパスの取得/指定方法は必要に応じて変更のこと Dim verStr As String = cls.RestoreVersion(dat) if verStr="ver1" then // バージョンが上がったら、別途対応する処理を追加する PrefInteger1=cls.RestoreArrayInteger(dat,1,PrefInteger1) msgBox str(PrefInteger1(0))+" "+str(PrefInteger1(9)) PrefString1=cls.RestoreArrayString(dat,2,PrefString1) msgBox PrefString1(0)+" "+PrefString1(9) PrefString2=cls.RestoreArrayString(dat,3,PrefString2) msgBox PrefString2(0)+" "+PrefString2(9) DataDict1=cls.RestoreDictionary(dat,4,DataDict1) msgBox DataDict1(0).Value(11)+" "+DataDict1(1).Value(11) end if
- 以下をWindow1のメソッドに追加
メソッド名: InitData 引数: なし 戻り値型: なし // 整数型配列変数 PrefInteger1(0)=10 PrefInteger1(1)=11 PrefInteger1(2)=12 PrefInteger1(3)=13 PrefInteger1(4)=14 PrefInteger1(5)=15 PrefInteger1(6)=16 PrefInteger1(7)=17 PrefInteger1(8)=18 PrefInteger1(9)=19 // 文字列型配列変数1 PrefString1(0)="a" PrefString1(1)="b" PrefString1(2)="c" PrefString1(3)="d" PrefString1(4)="e" PrefString1(5)="f" PrefString1(6)="g" PrefString1(7)="h" PrefString1(8)="i" PrefString1(9)="j" // 文字列型配列変数2 PrefString2(0)="0" PrefString2(1)="1" PrefString2(2)="2" PrefString2(3)="3" PrefString2(4)="4" PrefString2(5)="5" PrefString2(6)="6" PrefString2(7)="7" PrefString2(8)="8" PrefString2(9)="9" // Dictionary ReDim DataDict1(0) DataDict1(0)=new Dictionary DataDict1(0).Value(1)=0 // "next" DataDict1(0).Value(11)=2016 // "year" DataDict1(0).Value(12)=3 // "month" DataDict1(0).Value(13)=2 // "day" DataDict1(0).Value(14)=0 // "color" DataDict1(0).Value(15)=&hFFFFFF // "bkclr" DataDict1(0).Value(21)="comment" // "comment" DataDict1(0).Value(22)="" // "style" DataDict1(0).Value(23)=1 // "paragraph align" DataDict1(0).Value(31)=2 // "btag" ReDim DataDict1(1) DataDict1(1)=new Dictionary DataDict1(1).Value(1)=0 // "next" DataDict1(1).Value(11)=2015 // "year" DataDict1(1).Value(12)=4 // "month" DataDict1(1).Value(13)=3 // "day" DataDict1(1).Value(14)=0 // "color" DataDict1(1).Value(15)=&hFFFFFF // "bkclr" DataDict1(1).Value(21)="comment2" // "comment" DataDict1(1).Value(22)="" // "style" DataDict1(1).Value(23)=2 // "paragraph align" DataDict1(1).Value(31)=3 // "btag"
- 以下をWindow1のプロパティに追加
プロパティ名: DataDict1(-1) データ型: Dictionary 標準値: なし
- 以下をWindow1のプロパティに追加
プロパティ名: PrefInteger1(10) データ型: Integer 標準値: なし
- 以下をWindow1のプロパティに追加
プロパティ名: PrefString1(10) データ型: String 標準値: なし
- 以下をWindow1のプロパティに追加
プロパティ名: PrefString2(10) データ型: String 標準値: なし
実行してみたところ、シリアライズが機能することを確認しました。
おわりに
視認性はテキストの方が上ですが、データ量が多くなると、やはりバイナリを選択せざるを得ない気はします。
なお、今回も仕組みを理解するために、極力シンプルな書き方を心懸けています。
(例えば、allocした変数の一部は然るべきタイミングでReleaseするべきと思われますが、そのためにはコードを工夫する必要があります。)
実用目的であれば、macoslibを利用させて頂くことを考えた方がいいでしょう。
(NSKeyedArchiver/NSCoding/NSPropertyListSerializationは、見つけられませんでしたが、NSDictionaryやNSArrayのwriteToFileはあります。)
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):シリアライズ - Wikipedia
参考サイト(2):プロパティリスト - Wikipedia
更新履歴
2016.03.09 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]