12
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

がっつりiOSをやった人がAndroidに移るとListenerの実装でハマるかもしれない話

Posted at

初めに

しばらくiOSの実装ばかりした後で久々にAndroidの実装にとりかかったら、
何故か変なタイミングでListenerのインスタンスが解放されてしまうのにちょっと悩まされた。

やったこと

多分誰でも一度は書いたことがある、iOSで言うDelegate、 Androidで言うListenerを↓こんな感じで実装した。
メソッド名とかの流儀がおかしい、とかの話は今回は気にしない。

iOS


import UIKit

// UI
class ViewController: UIViewController, ModelDelegate {
    
    let model = Model()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        model.delegate = self
    }
    
    func notify(model: Model) {
    }
}

// Modelと通知用のprotocol
protocol ModelDelegate: class {
    func notify(model: Model)
}
class Model {
    weak var delegate: ModelDelegate?
    
    func doSomething() {
        // 何かする
        delegate?.notify(model: self)
    }
}

Android


import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.lang.ref.WeakReference;

// UI
public class MainFragment extends Fragment {

    private Model model;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        this.model = new Model();
        model.setListener(new ModelListener() {
            @Override
            public void notify(Model model) {

            }
        });

        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

// Modelと通知用のinterface
interface ModelListener {
    void notify(Model model);
}

class Model {

    private WeakReference<ModelListener> mListener;

    void setListener(ModelListener listener) {
        mListener = new WeakReference<>(listener);
    }

    void doSomething() {
        // 何かする
        // 本当はnullチェックする

        mListener.get().notify(this);
    }
}

で、これだとiOSでは問題なく動く。
Androidでも問題なく動くように思わせておいて、あるとき突然 mListener.get() がnullを返すようになる。

原因

無名クラスで実装するとListenerを強参照しているインスタンスが無いのでGCの対象になる、という話だった。
GCが走ったタイミングでmListener.get() がnullを返すようになる。

無名クラスでない、通常のクラスで実装すると強参照されているので大丈夫だった。

考えたこと

(じゃあWeakReferenceではなくすべき?
でも強参照したら無名クラスでない通常のクラスで実装したときに循環するよな…。
え、javaってinterfaceの実装の仕方を強制するようなクソ言語なの?)

とか考えたがもちろんそんなことは無く、クソなのは自分の方だった。

結局

お気づきかとは思うが、javaはGCがあるので循環しても問題ない、という話だった。
ということでWeakReferenceじゃない普通の参照に直した。

iOSしかやったことが無い人がGCがある言語を書くときは気をつけよう。

終わりに

無名クラスはGCがあるからこそ可能な概念なのだ、ということに気付けたのは良い経験だった。

12
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?