ホームページ開発ツール>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等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、TextField2個(Name:TextField1、Name:TextField2)、PushButton3個(Name:PushButton1, Caption:setParam1、Name:PushButton2, Caption:param1、Name:PushButton3, Caption:List)を追加(配置はスクリーンショットを参照)
  3. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      cobj.param1=TextField1.Text
    End Sub
    
  4. 以下をPushButton2にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      TextField2.Text=cobj.param1
    End Sub
    
  5. 以下を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
    
  6. 以下をWindow1にペースト
    Sub Open() Handles Open
      cobj = new myCustomObject()
    End Sub
    
  7. 以下をWindow1にペースト(できなければPropertyに、Name:cobj、Type:myCustomObject、を追加)
    Public Property cobj As myCustomObject
    
  8. 新規クラスを作成(名前は、ここでは「myCustomObject」とした。)
  9. 以下を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
    
  10. 以下をmyCustomObjectにペースト(できなければPropertyに、Name:InstancePtr、Type:Ptr、を追加)
    Private Property InstancePtr As Ptr
    
  11. 以下を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
    
  12. 以下を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
    
  13. 以下を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
    
  14. 以下を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
    
  15. 以下をmyCustomObjectにペースト(できなければShared Propertyに、Name:myCustomClass、Type:Ptr、を追加)
    Public Shared Property myCustomClass As Ptr
    
  16. 新規モジュールを作成(名前は、ここでは「Globals」とした。)
  17. 以下をGlobalsにペースト
    Structure attr
      name As CString
      value As CString
    End Structure
    
 実行してみたところ、セットした値がゲットできること、リストの取得ができることを確認しました。
S Shot1


 おわりに

 結局、値をハンドルしたい場合はプロパティではなくインスタンス変数ベース、即ち、前回プロジェクトそのままでOKということになりそうです。
 ただ、何か釈然としないものが残るので、もう少し調べてみたい気もしますが、公開されている情報が少ないこともあって、難航しそうです。


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):Objective-C における @property と @synthesize の簡単な説明 · GitHub
 参考サイト(2):Objective-Cでの「プロパティ」「インスタンス変数」「ローカル変数」の違い - ひとりまとめ
 参考サイト(3):objective-c — 実行時にオブジェクトにプロパティを追加するにはどうすればよいですか?


 更新履歴

 2022.06.23 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]