📲

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

に公開

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 が作られている)を眺めつつ、過去から今までの変遷を追うことで理解が深まる。

GitHubで編集を提案

Discussion