24045


iOS(Swift)でアプリを開発しています。  
カスタムViewを作成し、あるViewControllerにaddSubviewし、そのViewControllerにカスタムViewから呼び出されるデリゲートメソッドを実装しています。  
このとき、以下のようにCustomView内のUIButtonから直接メソッドの呼び出しを行うと、"unrecognized selector sent
to instance xxxx"と、メモリに関するエラーでアプリが落ちてしまいます。

SampleViewController

[code]

    class SampleViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let customView = CustomView(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height))
            self.view.addSubview(customView) 
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        func sampleFunc() {
            print("sampleFunc")
        }
    }
    
[/code]

CustomView

[code]

    class CustomView: UIView {
        let button = UIButton(frame: CGRectZero)
    
        func setup() {
            button.addTarget(self, action: #selector(SampleViewController.sampleFunc(_:)), forControlEvents: .TouchUpInside)
            self.addSubview(button)
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            // 略
        }
    }
    
[/code]

一方で、CustomView内に別のメソッドを用意し、そのメソッドから間接的にViewController内のデリゲートメソッドを呼び出した場合は正常に動作しました。

CustomView(修正後)

[code]

    class CustomView: UIView {
        let button = UIButton(frame: CGRectZero)
    
        func setup() {
            button.addTarget(self, action: #selector(CustomView.buttonAction(_:)), forControlEvents: .TouchUpInside)
            self.addSubview(button)
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            // 略
        }
    
        @objc private func buttonAction(sender: AnyObject) {
            UIApplication.sharedApplication().sendAction(#selector(SampleViewController.sampleFunc(_:)), to: nil, from: self, forEvent: nil)
        }
    
    }
    
[/code]

これがなぜ間接的に呼び出した場合は成功するのかがわからず、お分かりになる方がいれば教えていただきたいです。宜しくお願いします。


CC BY-SA 3.0
2016-04-15T08:49:25.077
24041
2016-04-15T09:56:10.000
15172
question
ios swift メモリ管理
Cocoa touchのデリゲートメソッドの呼び出しとメモリ管理について


メモリ管理は関係ないですね。解放済みのオブジェクトにアクセスした場合も同様の例外が起きることがありますが、今回のはオブジェクトが解放されてるのではなく、文字通り間違ったオブジェクトに対してメソッドを呼んでいます。

`addTarget()`メソッドの最初の引数`target`はどのオブジェクトが`selector`で指定したメソッドを呼ぶかという意味ですので、書かれたコードですとCustomView
のselfを指定しているので、CustomViewの`sampleFunc()`を呼ぶ、ということが起こります。

後者の場合に、なぜ同じ例外が起きないかというと、後者もほぼ同様のことをしていますが、`sendAction()`メソッドの同じ意味の引数である`to`引数に`nil`を指定しているので、メソッドを呼び出すオブジェクトを指定しないことになります。  
指定しなかった場合、ボタンからビューとビューコントローラの親子関係を遡って、同じ名前のメソッド(someFunc)を持つオブジェクトを探して、見つかった時点でそのオブジェクトに対して呼びだす、という動きをします。  
（レスポンダチェーンといいます。厳密には遡るのはレスポンダチェーンを逆に辿る、ということになります。）

先の`addTarget()`メソッドでも、target引数を指定しなければ同じ挙動になります。ですので

[code]

    `button.addTarget(nil, action: #selector(CustomView.buttonAction(_:)), forControlEvents: .TouchUpInside)
    
[/code]

上記のように、最初の引数`target`を`nil`にするとクラッシュせず、同じ結果になります。

本来はレスポンダチェーンに任せるのではなく、正しいオブジェクトを指定するべきですので、きちんと修正するならCustomViewにビューコントローラのオブジェクトを渡し、それをボタンのアクション`addTarget()`メソッドの引数に指定する、などになります。


CC BY-SA 3.0
2016-04-15T09:56:10.000
24045
2016-04-15T09:56:10.000
5519
24041
answer