読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

CocoaのC++ラップが地味に面倒

この間レポジトリを作った某プロジェクト、密かに合間を見つけて進めてはいるのですが、なかなか問題点も多くてですね。UIViewのラップをどうするかが考え物なわけですよ。

class view : boost::noncopyable {
 public:
  explicit view(const CGRect &frame)
      : view_([[UIView alloc] initWithFrame:frame], false) {}
  
  virtual ~view() = default;

  void add_subview(const view &v) {
    [view_.get() addSubview:v.view_.get()];
  }
  
 private:
  boost::intrusive_ptr<UIView> view_;
};

まぁ取り敢えず、UIViewの寿命管理がし易いように、いわゆるretain/releaseを管理したスマポを一つ持っておいて、コピー禁止、継承前提の仮想デフォルトデストラクタやUIViewのそれっぽいラップメンバ函数まではいいとして、ここからどうするかがなかなかややこしいです。
継承が前提ってことはこんな用途が考えられるわけで、

class my_view : view {
 public:
  my_view()
      : button_(UIButtonTypeRoundedRect) {
    button_.add_action(target_and_action(std::bind(&my_view::action, this)),
                       UIControlEventTouchUpInside);
    add_subview(button_);
  } 

 private:
  void action() {}
  
  button button_;
};

このmy_viewの持ってるUIViewをUIViewControllerのviewかなんかに突っ込むとすると、UIViewはUIViewControllerが管理してくれるのでいいですが、恐らく突っ込んだ後そのうちスコープを抜けて寿命が尽きるであろうmy_viewとそのメンバ達は力尽きてしまうのです。これの何がまずいかというと、UIButtonのaddTargetとかで問題になるわけです。targetとactionがobj-CっぽくてあれだからC++っぽくラップしようとすると例えばこんなんになるかと思うのですが、

@interface CPPTargetAndAction : NSObject {
 @private
  std::function<void ()> function_;
}

- (id)initWithFunction:(std::function<void ()>)function;
- (void)call;

@end

@implementation CPPTargetAndAction

- (id)initWithFunction:(std::function<void ()>)function {
  if (self = [super init]) {
    function_ = function;
  }
  return self;
}

- (void)call {
  function_();
}

@end
class target_and_action {
 public:
  explicit target_and_action(std::function<void ()> function)
      : target_and_action([[CPPTargetAndAction alloc] initWithFunction:function], false) {}
 
  id get_target() const {
    return target_and_action_.get();
  }

  SEL get_action() const {
    return @selector(call);
  }

 private:
  boost::intrusive_ptr<CPPTargetAndAction> target_and_action_;
};

で、buttonクラスはメンバでtarget_and_actionを保持してくれるとそんな感じです。しかしbuttonクラスがtarget_and_actionを保持していても、buttonを持ってるmy_viewの寿命が尽きればお亡くなりになるので結果ボタンを押すと存在しないtargetのcallを呼んで残当するわけです。これを避けるためにはviewはUIViewの代わりにviewの寿命を管理するメンバを持つUIViewを継承したCPPView的なクラスを作ってやる必要がありそうなのですが、これを単純にviewはshared_ptrでしか作れない感じにして寿命はshared_ptrで管理するような感じにしてしまうと循環参照が発生するのが目に見えるわけです。困った困ったって話でした。
他にはUIViewControllerもどうせC++ラップするのでこれはviewを持って、viewはsubviewのコンテナを持って管理してやろうって説もあるのですが、これでも一つ問題があったりします。これだとUITableViewのセルの管理が面倒になって、というのもUITableViewCellってのはUITableViewがいい感じにセルの再利用をしてくれてたりするのです。これを利用しようと思ったら、一旦生のUITableViewCellを取り出さなきゃいけないわけで、この瞬間一機に上記の作戦は潰えます。subviewのコンテナを持ったtable_view_cellクラスを作ったとして、これとUITableViewCellの再利用とかをいい感じに同期してやらなきゃいけなくなるわけでもの凄く面倒そうです。
なんというか、頭が半分眠い状態で書いたので意味不明な文章になっている可能性が高いですが、取り敢えず何が言いたいかというと、色々と面倒そうってことです。はい。