📲

【class】objc_util からrubicon-objc へ移行【宣言】

2024/03/29に公開

objc_util からrubicon-objc へ乗り換える

Pythonista3が、3.4になったタイミングでobjc_utilのObjcBlock の処理が落ちる。
blockを使わずに実装する。といっても限度があるし、Python側で処理。というよりも内部の問題ぽい(深くは調べて(られ)ない)ので、rubicon-objcへ移行することにした。

objc_util — Utilities for bridging Objective-C APIs — Pythonista Documentation

beeware/rubicon-objc: A bridge interface between Python and Objective-C.

よく使いながら、毎回過去のコードを見直す内容をメモ的に書いていく。

今回はClass宣言:

プロトコル・デリゲートについては、以下を参照。

書き換え

objc_util

objc_util.create_objc_class | objc_util — Utilities for bridging Objective-C APIs — Pythonista Documentation

sub_class = objc_util.create_objc_class(name, superclass=NSObject, methods=[], classmethods=[], protocols=[], debug=True)

PythonのClass継承のように書けず、宣言っぽさも希薄な印象。
独自の書き方として、Class 宣言をラップするイメージで書いていた:

雑な例.py
class SubClass:
    def __init__(self, *args, **kwargs):
        self.インスタンスにさせたいオブジェクト: '型'   # 何を呼び出したかったか忘れないため

    def override_hoge(self):
        # メソッド内に、objc で定義したいメソッドを書く
        def hoge_fuga_(_self, _cmd, _fuga):
            # (大体)ポインタで来るので、ObjCInstance でラップする
            # `self だとClass 内で衝突してしまうので、`this`
            this = ObjCInstance(_self)

        def foo_bar_baz_(_self, _cmd, _bar, baz):
            # objc のメソッドとして、存在するものもオーバーライドできる
            this = ObjCInstance(_self)

        _methods = [
            hoge_fuga_,
            foo_bar_baz,
        ]
        create_kwargs = {
            'name': 'onamae',    # 変数名と揃える
            'superclass': 使いたいClass,    # 事前に、`ObjCClass('使いたいClass')` しておく
            'methods': _methods,
        }
        onamae = create_objc_class(**create_kwargs)
        self.インスタンスにさせたいオブジェクト = onamae

    def _init(self):
        self.override_hoge()
        return self.インスタンスにさせたいオブジェクト

    @classmethod
    def new(cls, *args, **kwargs):
        _cls = cls(*args, **kwargs)
        return _cls._init()

意味ない部分も多いかも、コードの視認性として、Classのブロックとしてまとめたかった。
docsにもあるが、無理にsubClass化する必要はない。

rubicon-objc

Tutorial 2 - Writing your own class - Rubicon 0.4.7

なんと、宣言できる。

Tutorial2.py
from rubicon.objc import NSObject, objc_method


class Handler(NSObject):
    @objc_method
    def initWithValue_(self, v: int):
        self.value = v
        return self

    @objc_method
    def pokeWithValue_andName_(self, v: int, name) -> float:
        print("My name is", name)
        return v / 2.0

デコレーション@objc_method をつける。initializeのmethodはreturnでself を返す。引数には型をつける。など、rubicon上でのルール(エラーでしっかり警告が出る)はあるが、PythonのClass宣言。

サンプルコードNSObject の部分を、継承させたいClassにすれば継承される。ObjCClass('呼びたいclass') で呼び出しておくことは必要。

ObjCClass | rubicon.objc.api — The high-level Rubicon API - Rubicon 0.4.7

所感

rubiconのPythonicなClass宣言は、objc_utilでのsubClass生成よりもハードルは低い印象。
しかし、rubiconでは__init__ が隠蔽されていたり、デコレータを付け足したり。仕様の理解度合いで、細かい実装の可能性が変わっていきそうな予感がしている。
一方objc_utilは、素朴で無骨な「なんとかPythonで操作できる状態のオブジェクト」を提供している印象があり、状態の管理をどっち(モジュールか自分自身)がもつかの認識判断が必要だと感じた。

rubiconは現在もアクティブに更新され続けています。objc_utilのソースとの差異(過去のrubiconをベースにobjc_utilが作られている)を眺めつつ、過去から今までの変遷を追うことで理解が深まる。

Discussion