ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareにおけるプロパティについて
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareにおけるプロパティについて
はじめに
以下は、Xojo Cocoaビルドについての話題です。
こちらで話題にしたプロパティについて、その後判明したこと等をまとめてみました。
なお検証には、Xojo 2021 Release 2.1を用いています。(Mac mini 2018 + macOS 12.4 Montereyr)
注)プロパティは、アウトラインビューに限定されるものではありませんが、話題の流れ上、ここに置いています。
経緯
前回のレポート後に、構造体の配列を指定する方法が分かったため、プロパティを用いることが可能になりました。
そこでまずは、改めてプロパティについて再確認しておくことにしました。
参考サイト(1):Objective-C における @property と @synthesize の簡単な説明 · GitHub
参考サイト(2):Objective-Cでの「プロパティ」「インスタンス変数」「ローカル変数」の違い - ひとりまとめ
プロパティを使う理由としては、以下が考えられそうです。
1. クラスの外部から参照する場合、インスタンス変数を直接ハンドリングすることは好ましくない。(禁止されている訳ではない?)
2. ゲッター/セッターを省略できる。
3. ドット記法が使えるようになる。
後は、以下のサイトのサンプルを元にさせて頂いて、実際に確かめてみることとしました。
参考サイト(3):objective-c — 実行時にオブジェクトにプロパティを追加するにはどうすればよいですか?
実験は、XcodeとXojoの両方で行いました。
結果から申し上げますと、実験の範囲では、プロパティを使うことの意味は見出せませんでした。
まず、class_addProperty()をコメントアウトしても、正常に動作してしまいます。
一方、ゲッター/セッターを省略することはできませんでした。
また、ドット記法も使えませんでした。(Xcodeではエラーとなってビルドできない。抑止して強制実行する方法はある??ようだが未確認。)
上掲サンプルを見ても、必要なものは全て自分で記述している(注1)ので、class_addProperty()は、やっていることをプロパティのフォーマットで登録しているだけ、のように見えなくもありません。
注1:セッターを記述することでreadwrite属性、copy()メソッドを記述することでcopy属性、class_addMethod()で(実質的に)ゲッター/セッター名、をそれぞれ担保している。となると考えられるのは、プロパティリストを取得する際、ランタイムで作ったクラスからも同等に情報が得られるよう仕込んでおく、くらいですが、どうなんでしょう。
まだよく分かってはいません。
と言う訳で、今回は機能を確認するための実装例はないのですが、更なる検証のことも考えて、実験に使ったサンプルを載せておくことにしました。
機能は、(1) セットしたプロパティをゲットする、(2) プロパティのリストを取得する、というものです。
計算型プロパティを使うことで、ドット記法を再現すると共に、コードをシンプル化しています。
Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、セットした値がゲットできること、リストの取得ができることを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、TextField2個(Name:TextField1、Name:TextField2)、PushButton3個(Name:PushButton1, Caption:setParam1、Name:PushButton2, Caption:param1、Name:PushButton3, Caption:List)を追加(配置はスクリーンショットを参照)
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action cobj.param1=TextField1.Text End Sub
- 以下をPushButton2にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action TextField2.Text=cobj.param1 End Sub
- 以下をPushButton3にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action Dim propCount, attCount As UInteger Declare Sub free Lib "Cocoa" (p As Ptr) // プロパティリストの取得 Declare Function class_copyPropertyList Lib "Cocoa" (cls As Ptr, ByRef outCount As UInteger) As Ptr Dim propAry As Ptr = class_copyPropertyList(cobj.myCustomClass, propCount) for i As Integer = 0 to propCount-1 Dim pelm As Ptr = propAry.Ptr(i*8) // 要素を順に取得(ポインターの配列なので、ポインターのバイト数でスライド) // プロパティ名を取得 Declare Function property_getName Lib "Cocoa" (prp As Ptr) As CString Dim nam As String = property_getName(pelm) MessageBox "i="+str(i)+" name="+nam // アトリビュートをカンマ区切りの文字列で取得 Declare Function property_getAttributes Lib "Cocoa" (prp As Ptr) As CString Dim att As String = property_getAttributes(pelm) MessageBox "i="+str(i)+" attr="+att // アトリビュートを構造体の配列で取得 Declare Function property_copyAttributeList Lib "Cocoa" (prp As Ptr, ByRef outCount As UInteger) As Ptr Dim attAry As Ptr = property_copyAttributeList(pelm, attCount) for j As Integer = 0 to attCount-1 Dim aelm As attr = attAry.attr(j*attr.Size) // 要素を順に取得(構造体の配列なので、構造体のSizeでスライド) MessageBox "i="+str(i)+" j="+str(j)+" name="+aelm.Name+" value="+aelm.Value next free(attAry) // 解放(しないと、メモリーリークする) next // 解放(しないと、メモリーリークする) free(propAry) End Sub
- 以下をWindow1にペースト
Sub Open() Handles Open cobj = new myCustomObject() End Sub
- 以下をWindow1にペースト(できなければPropertyに、Name:cobj、Type:myCustomObject、を追加)
Public Property cobj As myCustomObject
- 新規クラスを作成(名前は、ここでは「myCustomObject」とした。)
- 以下をmyCustomObjectにペースト
Public Sub Constructor() // カスタムクラスを作成。初回のみ makeClass() // カスタムクラスのインスタンス作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" Selector "init" (receiver As Ptr) As Ptr InstancePtr = init(alloc(myCustomClass)) End Sub
- 以下をmyCustomObjectにペースト(できなければPropertyに、Name:InstancePtr、Type:Ptr、を追加)
Private Property InstancePtr As Ptr
- 以下をmyCustomObjectにペースト(できなければmyCustomObjectにComputed Propertyを追加し、Name:param1、Type:Stringをセット。その後、Get - End Getの間をGetに、Set - End Setの間をSetに記述)
Public Property param1 As String Get Declare Function getParam1 Lib "Cocoa" Selector "param1" (receiver As Ptr) As CFStringRef Dim str As String = getParam1(instancePtr) return str End Get Set Declare Sub setParam1 Lib "Cocoa" Selector "setParam1:" (receiver As Ptr, val As CFStringRef) setParam1(instancePtr, value) End Set End Property
- 以下をmyCustomObjectにペースト
Private Shared Sub makeClass() // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // Declare宣言 Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr) Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean Declare Function class_addProperty Lib "Cocoa" (cls as Ptr, name as CString, attr as Ptr, size as UInteger) As Boolean Declare Function class_addIvar Lib "Cocoa" (cls as Ptr, name as CString, size as UInteger, alignment as UInt8, types as CString) As Boolean // 既にクラス作成済なら戻る if myCustomClass <> nil then return end if // クラス名をmyCustomObject、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myCustomObject", 0) // 変数を追加(_param1) if not class_addIvar (newClassId, "_param1", 8, 3, "@") then msgBox "error1." return end if // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // 構造体の配列作成 Dim mb As new MemoryBlock(3 *attr.Size) // 要素数は、3 Dim mbp As Ptr = mb mbp.attr(0 *attr.Size).name="T" mbp.attr(0 *attr.Size).value="@""NSString""" mbp.attr(1 *attr.Size).name="C" mbp.attr(1 *attr.Size).value="" mbp.attr(2 *attr.Size).name="V" mbp.attr(2 *attr.Size).value="_param1" // プロパティを追加(param1) if not class_addProperty (newClassId, "param1", mbp, 3) then msgBox "error2." return end if // メソッドを追加(param1をXojo側で用意したparam1Getterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("param1"), AddressOf param1Getter, "@@:") then msgBox "error3." return end if // メソッドを追加(setParam1:をXojo側で用意したparam1Setterメソッドで受け取る。) if not class_addMethod (newClassId, NSSelectorFromString("setParam1:"), AddressOf param1Setter, "v@:@") then msgBox "error4." return end if // クラスを保持 myCustomClass = newClassId End Sub
- 以下をmyCustomObjectにペースト
Private Shared Function param1Getter(id As Ptr, sel As CString) As CFStringRef // クラスから変数(_param1)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, cnt As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(myCustomClass, "_param1") // インスタンスから変数(_param1)の値を取得 Declare Function object_getIvar Lib "Cocoa" (receiver As Ptr, extraBytes As Ptr) as CFStringRef Dim value As CFStringRef = object_getIvar(id, ivar) return value End Function
- 以下をmyCustomObjectにペースト
Private Shared Sub param1Setter(id As Ptr, sel As CString, value As Ptr) // クラスから変数(_param1)のIVerを取得 Declare Function class_getInstanceVariable Lib "Cocoa" (cls As Ptr, name As CString) As Ptr Dim ivar As Ptr = class_getInstanceVariable(myCustomClass, "_param1") // インスタンスから変数(_param1)の現在の値を取得 Declare Function object_getIvar Lib "Cocoa" (receiver As Ptr, extraBytes As Ptr) as Ptr Dim oldValue As Ptr = object_getIvar(id, ivar) // 値が同じなら戻る if value=oldValue then return // インスタンスの変数(_param1)に値をセット Declare Sub object_setIvar Lib "Cocoa" (receiver As Ptr, ivar as Ptr, val as Ptr) Declare Function myCopy Lib "Cocoa" Selector "copy" (receiver As Ptr) As Ptr object_setIvar(id, ivar, myCopy(value)) // コピーをセットしないと、オリジナルが解放されてしまう? End Sub
- 以下をmyCustomObjectにペースト(できなければShared Propertyに、Name:myCustomClass、Type:Ptr、を追加)
Public Shared Property myCustomClass As Ptr
- 新規モジュールを作成(名前は、ここでは「Globals」とした。)
- 以下をGlobalsにペースト
Structure attr name As CString value As CString End Structure
おわりに
結局、値をハンドルしたい場合はプロパティではなくインスタンス変数ベース、即ち、前回プロジェクトそのままでOKということになりそうです。
ただ、何か釈然としないものが残るので、もう少し調べてみたい気もしますが、公開されている情報が少ないこともあって、難航しそうです。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):Objective-C における @property と @synthesize の簡単な説明 · GitHub
参考サイト(2):Objective-Cでの「プロパティ」「インスタンス変数」「ローカル変数」の違い - ひとりまとめ
参考サイト(3):objective-c — 実行時にオブジェクトにプロパティを追加するにはどうすればよいですか?
更新履歴
2022.06.23 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]