ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでもう一つの通知センターを利用する
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでもう一つの通知センターを利用する
はじめに
以下は、Xojo Cocoaビルドについての話題です。
通知センターは、既にCocoaのDeclareで通知センターを利用する他で取り上げましたが、ここでは、もう一つの通知センター、について調べてみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + macOS 10.13.6 High Sierra)
方針
Xojoでは、2013 Release 1以降、スレッドからコントロールにアクセスできなくなってしまったので、代替方法としてタイマーの使用が推奨?されています。
(言語リファレンスのThreadの項に、サンプルが載っています。)
それでもいいのですが、他にも方法がないのか、調べてみることにしました。
思い当たったのは、以前にもやった通知センターですが、処理内容がちょっと違うので検索してみたところ、以下がヒットしました。
参考サイト(1):見えない通知を捕まえて通知駆動アプリケーションを作る - ザリガニが見ていた...。
ここで、通知センターには二種類あることを知りました。
今回使えそうなのは、(以前使った)NSUserNotificationCenterではなく、NSNotificationCenterのようです。
当初は、同じアプリ内なので、NSNotificationCenterでいいかと思ったのですが、ThreadAccessingUIExceptionエラーで止まってしまいました。
デバッガの情報を見ると、直接メソッドを呼んでいるようなスタックになっています。
なので、別アプリに通知を送る、NSDistributedNotificationCenterを使ってみたところ、…、今度はうまくいくいきました。
一方、せっかくなので(笑)、NSNotificationCenterの方も試してみることにしました。
事例としては、(スレッドには使えないので)参考サイト(1)で取り上げられていた、ウィンドウの背景色にしてみました。
このケースでは、通知は各ウィンドウ(インスタンス)が受け取ることになりますが、通知を最初に受け取るのはクラスメソッドのため、これを各ウィンドウ(インスタンス)に振り分ける処理が必要になります。
直接インスタンスメソッドで受けられないか、いくつか試してはみたのですが、試した範囲ではうまくいきませんでした。
注)これまでも、このクラス - インスタンス問題はあったが、受け手が一箇所しかなかったため、インスタンス生成時に一回指定すれば事足りた。そこで、各ウィンドウ(インスタンス)生成ごとに、イベントをレイズするメソッドのアドレスを配列に追加し、通知受け取り時は、通知名(実際にはランタイムAPIのインスタンスに継承された、ランタイムAPIのクラス名)に埋め込まれたウィンドウ番号を判定して、振り分けるようにしました。
ここに、「イベントをレイズ」とあるのは、受け手が一つの場合のようにインスタンス生成時にメソッドのアドレスを渡すことができないため、クラス側のみで処理しようとすると、イベントの方が都合がいいからです。(ただし、生成後は可能なので、別メソッド化すれば、従来(例1含む)方式で処理することも可能。)
以上を踏まえ、それぞれの仕様は以下の通りとしました。
例1(NSDistributedNotificationCenter)
例2(NSNotificationCenter)
- Xojo Threadの項のサンプルをベースに、タイマー部分を通知センターに置き換える
- 通知の名前には、Bundle identifierを付加する
- メインウィンドウから、サブウィンドウを生成する
- メインウィンドウから、カラーのRGB値を指定する
- メインウィンドウからサブウィンドウに対し、背景色変更の通知を発行する
- 通知はメソッドではなく、イベントで受ける
Xojoでの実装(例1)
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、スレッドからプログレスバーを更新できることを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、PushButton(Name:PushButton1)、ProgressBar(Name:ProgressBar1、Value:0)を追加
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // 通知センターに通知名と受け取り手を登録 Dim tmp As NotificationCenter = new NotificationCenter("myNotif", kIdentifier, AddressOf PostGet) // プログレスバー初期化 ProgressBar1.Value=0 // スレッドを起動 Dim th As CustomThread th=new CustomThread th.Run End Sub
- 以下をWindow1にペースト
Private Sub PostGet(notify As Ptr) Dim progressValue As Integer if ProgressBar1.Value >= ProgressBar1.Maximum then // 最大値を超えたら ProgressBar1.Value = 0 // クリア msgBox "Done" else // 最大値以下なら // notifyが有効なら if notify<>nil then // notifyからuserInfoを取得 Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim info As Ptr = userInfo(notify) if info<>nil then // userInfoが有効なら // userInfoから値を取得 Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr Dim vPtr As Ptr = objectForKey(info, "Value") if vPtr<>nil then // 値が有効なら // 値を取得 Declare Function integerValue Lib "Cocoa" Selector "integerValue" (receiver As Ptr) As Integer progressValue = integerValue(vPtr) end if end if end if // プログレスバーに値をセット ProgressBar1.Value = progressValue end if End Sub
- 新規クラスを作成(名前は、ここでは「NotificationCenter」とした。)
- 以下をNotificationCenterにペースト(できなければ移譲に、名前:ActionDelegate、を追加)
Private Sub ActionDelegate(notify As Ptr)
- 以下をNotificationCenterにペースト
Public Sub Constructor(clsName As String, notName As String, action As ActionDelegate) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // ObserverとSelectorを生成 MakeObserver(clsName) // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSDistributedNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // defaultCenterにObserver/Selector/名前/オブジェクトをセット Declare Sub addObserver Lib "Cocoa" Selector "addObserver:selector:name:object:" (receiver As Ptr, obs As Ptr,sel As Ptr, nam As CFStringRef, obj As Ptr) addObserver(nCenter, NotifyObserver, NotifySelector, notName, nil) // クラス生成元でActionを受け取るメソッドを登録 ActionHandler = action End Sub
- 以下をNotificationCenterにペースト
Private Sub MakeObserver(clsName As String) // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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 // 既にインスタンス作成済なら戻る if NotifyObserver <> nil then return end if // クラス名をmyNotif、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), clsName, 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // 通知の受け口となるメソッドを追加(getPost:をXojo側で用意したgetPostメソッドで受け取る。) NotifySelector = NSSelectorFromString("getPost:") if not class_addMethod (newClassId, NotifySelector, AddressOf getPost, "@@:@") then msgBox "error." return end if // 上記で生成したクラスのインスタンスを作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr Dim targetId As Ptr = init(alloc(newClassId)) // インスタンスを保持 NotifyObserver = targetId End Sub
- 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifyObserver、データ型:Ptr、を追加)
Private Property NotifyObserver as Ptr
- 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifySelector、データ型:Ptr、を追加)
Private Property NotifySelector as Ptr
- 以下をNotificationCenterにペースト
Private Shared Sub getPost(id As Ptr, SEL As CString, notify As Ptr) ActionHandler.Invoke(notify) // クラス生成元でActionを受け取るメソッドを呼び出す End Sub
- 以下をNotificationCenterにペースト(できなければ共有プロパティに、名前:ActionHandler、データ型:ActionDelegate、を追加)
Private Shared Property ActionHandler as ActionDelegate
- Xojoプロジェクトウィンドウ>ナビゲーターに、Threadをドロップ(名前は、ここではデフォルトの「CustomThread」とした。)
(Xojoプロジェクトウィンドウ>ナビゲーター>Window1、ではない)- 以下をCustomThreadにペースト(できなければ、Sub - Endの間をRunイベントに記述)
Sub Run() Handles Run // 初期化 Dim progressValue As Integer = 0 // 100になるまで繰り返す while progressValue <= 100 // インクリメント progressValue = progressValue + 1 // 遅延 Dim waitUntil As Integer = Ticks + 1 while ticks < waitUntil wend // 通知を発行 PutPost(progressValue) wend End Sub
- 以下をCustomThreadにペースト
Public Sub PutPost(progressValue As Integer) // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 通知と一緒に渡すパラメータを生成 Dim numb As Ptr = NSClassFromString("NSNumber") // 整数をNSNumber形式に変換 Declare Function numberWithInteger Lib "Cocoa" Selector "numberWithInteger:" (receiver As Ptr, path As Integer) As Ptr numb = numberWithInteger(numb, progressValue) Dim dict As Ptr = NSClassFromString("NSDictionary") // NSDictionaryにセット Declare Function dictionaryWithObject Lib "Cocoa" Selector "dictionaryWithObject:forKey:" (receiver As Ptr, objt As Ptr, key As CFStringRef) As Ptr dict = dictionaryWithObject(dict, numb, "Value") // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSDistributedNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // 通知を発行 Declare Sub postNotification Lib "Cocoa" Selector "postNotificationName:object:userInfo:" (receiver As Ptr, nam As CFStringRef, obj As CFStringRef, info As Ptr) postNotification(nCenter, kIdentifier, nil, dict) End Sub
- 新規モジュールを作成(名前は、ここではデフォルトの「Module1」とした。)
- 以下をModule1にペースト(できなければ定数に、名前:kIdentifier、デフォルト値:com.mycompany.notifybgcolor.identifier、データ型:String、を追加)
注)デフォルト値は任意です。Bundle identifierに相当する部分はじめ、適宜変更して下さい。Public Const kIdentifier as String = com.mycompany.notifybgcolor.identifier
Xojoでの実装(例2)
実行してみたところ、ウィンドウの背景色を変更できることを確認しました。
- Xojoで新規プロジェクトを作成
- 以下をWindow1にペースト(できなければ定数に、名前:kIdentifier、デフォルト値:identifier、データ型:String、を追加)
注)例1と同様、モジュールに置いても構いません。また、こちらもデフォルト値は任意です。Public Const kIdentifier as String = identifier
- Window1に、PushButton2個(Name:PushButton1, Name:PushButton2)、Slider3個(Name:Slider1, Name:Slider2, Name:Slider3)を追加
- Slider1, Slider2, Slider3のMaximumを255にセット
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // ウィンドウ生成 Dim w2 As Window2 = new Window2("myNotif_"+Str(cnt), kIdentifier) // 初期化 w2.InitApp(Str(cnt)) // 表示 w2.Show // カウントアップ cnt=cnt+1 End Sub
- 以下をPushButton2にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 通知と一緒に渡すパラメータを生成 Dim dict As Ptr = NSClassFromString("NSDictionary") // NSDictionary生成 Declare Function dictionaryWithObject Lib "Cocoa" Selector "dictionaryWithObject:forKey:" (receiver As Ptr, objt As Ptr, key As CFStringRef) As Ptr dict = dictionaryWithObject(dict, RGBtoNSColor(RGB(Slider1.Value,Slider2.Value,Slider3.Value)), "Value") // カラーをセット // 通知の発行 PutPost(kIdentifier,dict) End Sub
- 以下をWindow1にペースト
Private Sub PutPost(name As String, uinfo As Ptr) // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // NSNotificationを作成する Dim notification As Ptr = NSClassFromString("NSNotification") Declare Function notificationWithName Lib "Cocoa" Selector "notificationWithName:object:userInfo:" (receiver As Ptr, nam As CFStringRef, obj As Ptr, info As Ptr) As Ptr notification = notificationWithName(notification, name, nil, uinfo) // 通知を発行 Declare Sub postNotification Lib "Cocoa" Selector "postNotification:" (receiver As Ptr, notify As Ptr) postNotification(nCenter, notification) End Sub
- 以下をWindow1にペースト
Private Function RGBtoNSColor(rgb As Color) as Ptr Dim r, g, b As CGFloat // 0〜255 を 0.0〜1.0 にマッピング r=rgb.Red/255 g=rgb.Green/255 b=rgb.Blue/255 // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // カラーの取得 Dim clr As Ptr = NSClassFromString("NSColor") // RGB値からカラーを生成 Declare Function colorWithCalibrate Lib "Cocoa" Selector "colorWithCalibratedRed:green:blue:alpha:" _ (receiver As Ptr, red As CGFloat, green As CGFloat, blue As CGFloat, alpha As CGFloat) As Ptr clr = colorWithCalibrate(clr, r, g, b, 1.0) // alpha値は常に1.0 // カラーを返す return clr End Function
- 以下をWindow1にペースト(できなければプロパティに、名前:cnt、データ型:Integer、を追加)
Private Property cnt as Integer
- 新規クラスを作成(名前は、ここでは「NotificationCenterWin」とした。)
- 以下をNotificationCenterWinにペースト(できなければ移譲に、名前:ActionDelegate、引数:notify As Ptr、を追加)
Private Sub ActionDelegate(notify As Ptr)
- 以下をNotificationCenterWinにペースト(できなければイベント定義に、イベント名:GetPost、引数:notify As Ptr、を追加)
Event GetPost(notify As Ptr)
- 以下をNotificationCenterWinにペースト
Public Sub Constructor(clsName As String, notName As String) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // ObserverとSelectorを生成 MakeObserver(clsName) // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // defaultCenterにObserver/Selector/名前/オブジェクトをセット Declare Sub addObserver Lib "Cocoa" Selector "addObserver:selector:name:object:" (receiver As Ptr, obs As Ptr,sel As Ptr, nam As CFStringRef, obj As Ptr) addObserver(nCenter, NotifyObserver, NotifySelector, notName, nil) ActionHandler.Append AddressOf RaiseEventGetPost // クラス生成元のイベントをレイズするため、インスタンスメソッドを登録(配列に追加) End Sub
- 以下をNotificationCenterWinにペースト
Private Sub MakeObserver(clsName As String) // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 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 // 既にインスタンス作成済なら戻る if NotifyObserver <> nil then return end if // クラス名をclsNameで与えられる名前、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), clsName, 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // 通知の受け口となるメソッドを追加(getPost:をXojo側で用意したgetPostメソッドで受け取る。) NotifySelector = NSSelectorFromString("getPost:") if not class_addMethod (newClassId, NotifySelector, AddressOf getPost, "@@:@") then msgBox "error." return end if // 上記で生成したクラスのインスタンスを作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr Dim targetId As Ptr = init(alloc(newClassId)) // インスタンスを保持 NotifyObserver = targetId End Sub
- 以下をNotificationCenterWinにペースト
Private Sub RaiseEventGetPost(notify As Ptr) // イベントをレイズ RaiseEvent GetPost(notify) End Sub
- 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifyObserver、データ型:Ptr、を追加)
Public Property NotifyObserver as Ptr
- 以下をNotificationCenterにペースト(できなければプロパティに、名前:NotifySelector、データ型:Ptr、を追加)
Private Property NotifySelector as Ptr
- 以下をNotificationCenterにペースト
Private Shared Sub getPost(id As Ptr, SEL As CString, notify As Ptr) // 呼び出し元のクラスを取得 Declare Function myClass Lib "Cocoa" selector "class" (class_id As Ptr) As Ptr Dim pnt1 As Ptr = myClass(id) // クラス名を取得 Declare Function description Lib "Cocoa" selector "description" (class_id As Ptr) As CFStringRef Dim st As String = description(pnt1) // クラス名からウィンドウ番号を取得 Dim cnt As Integer = Val(NthFieldB(st,"_",2)) // イベントをレイズするため、ウィンドウ番号に対応したインスタンスメソッドを起動 ActionHandler(cnt).Invoke(notify) End Sub
- 以下をNotificationCenterにペースト(できなければ共有プロパティに、名前:ActionHandler(-1)、データ型:ActionDelegate、を追加)
Private Shared Property ActionHandler(-1) as ActionDelegate
- Xojoプロジェクトウィンドウ>ナビゲーターに、Windowをドロップ(名前は、ここではデフォルトの「Window2」とした。)
- Window2のSuperを「NotificationCenterWin」にする。
- 以下をWindow2にペースト(できなければ、Sub - Endの間をCloseイベントに記述)
Sub Close() Handles Close // Observerが未設定なら戻る if NotifyObserver=nil then return end if // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // defaultCenterからObserverを削除 Declare Sub removeObserver Lib "Cocoa" Selector "removeObserver:" (receiver As Ptr, obs As Ptr) removeObserver(nCenter, NotifyObserver) End Sub
- 以下をWindow2にペースト(できなければ、Sub - Endの間をGetPostイベントに記述)
Sub GetPost(notify As Ptr) Handles GetPost // notifyが無効なら戻る if notify=nil then return end if // notifyからuserInfoを取得 Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim uinfo As Ptr = userInfo(notify) // userInfoが無効なら戻る if uinfo=nil then return end if // userInfoから値を取得 Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr Dim vPtr As Ptr = objectForKey(uinfo, "Value") // 値が無効なら戻る if vPtr=nil then return end if // 背景色のセット me.BackColor=NSColortoRGB(vPtr) End Sub
- 以下をWindow2にペースト
Public Sub InitApp(cntString As String) // タイトルに固有番号を追加 me.Title=me.Title+" "+cntString // 背景のカラーを白で初期化 Dim clr As Color = RGB(255,255,255) // 背景色のセット me.BackColor=clr End Sub
- 以下をWindow2にペースト
Private Function NSColortoRGB(pnt As Ptr) as Color #if Target32Bit Dim rA As New MemoryBlock(4) Dim gA As New MemoryBlock(4) Dim bA As New MemoryBlock(4) Dim aA As New MemoryBlock(4) #elseif Target64Bit Dim rA As New MemoryBlock(8) Dim gA As New MemoryBlock(8) Dim bA As New MemoryBlock(8) Dim aA As New MemoryBlock(8) #endif Dim rF, gF, bF As CGFloat Declare Sub getRGB Lib "Cocoa" Selector "getRed:green:blue:alpha:" (receiver As Ptr, red As Ptr, green As Ptr, blue As Ptr, alpha As Ptr) getRGB(pnt, rA, gA, bA, aA) // rgb値を取得 #if Target32Bit rF = rA.SingleValue(0) gF = gA.SingleValue(0) bF = bA.SingleValue(0) #elseif Target64Bit rF = rA.DoubleValue(0) gF = gA.DoubleValue(0) bF = bA.DoubleValue(0) #endif return RGB(rF*255,gF*255,bF*255) // Color形式に変換して返す End Function
おわりに
例1のケースでは、仕掛けが大掛かりな割には得られるものが少ないので、実用性は薄いかもしれません。
例2のケースでは、ウィンドウを閉じると、通知センターへの登録は解除していますが、レイズイベント用の配列要素は削除していない(すればしたで、別の問題が発生)ので、一方的に増えてしまいます。Dictionary等を使えば任意の要素の削除が可能になりますが、オーバーヘッドを考えると、どちらがいいかは不明です。別の方法を考えたほうがいいかもしれません。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):見えない通知を捕まえて通知駆動アプリケーションを作る - ザリガニが見ていた...。
更新履歴
2018.08.10 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]