37386


いつもお世話になっております。

クロージャーにて引数を渡せなく悪戦苦闘しております。  
（勉強中でして、この記述方法をクロージャーと言うのか心配です）

以下のサンプルソースは正常で、AAをクリックするとログ"abc"が表示されます。  
やりたいことは、インスタンスAを生成する時に、表示したい文字列を一緒に渡したいです。

ご教授ご鞭撻のほどよろしくお願い致します。

[code]

    class GameScene: SKScene {
    
        override func didMove(to view: SKView) {
    
            if let node = A(closure: hoge) {　←hoge(text: "abc")をやってみたい
                self.addChild(node)
            }
            /*
            if let node = A(closure: hoge) {　←hoge(text: "def")をやってみたい
                node.position = CGPoint(x: 50, y: 50)
                self.addChild(node)
            }
            */
        }
    
        /*
        func hoge(text: String) {　←こっちを使ってみたい
            print(text)
        }
        */
        func hoge() {
            print("abc")
        }
    }
    
    
    class A: SKLabelNode {
    
        //var handler: (String) -> ()
        var handler: () -> ()
    
        //init?(closure: @escaping (String) -> ()) {
        init?(closure: @escaping () -> ()) {
    
            self.handler = closure
    
            super.init()
    
            self.isUserInteractionEnabled = true
    
            let node = SKLabelNode(text: "AA")
            self.addChild(node)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            self.handler()
        }
    }
    
[/code]


CC BY-SA 3.0
2017-08-21T12:23:09.067
37378
2017-08-21T18:24:25.010
2017-08-21T13:09:57.403
13972
20142
question
swift swift3 spritekit
クロージャーで引数を渡したい


あなたのコード、`A(closure:
hoge)`で、メソッド`hoge`は`()->Void`型のクロージャーとして使われているので、「クロージャー」と呼ぶのは構わないでしょう。(戻り値の型に`()`を使うのは誤解を招きやすいので、私は極力使わないようにしていますが、`()->Void`型と`()->()`型は全く同じです。)

クラス`A`(お試しコードなんでしょうが、それにしてももうちょっと意味のある名前を付けた方が良いように思われます)に`String`を引数とするクロージャーとそれに渡される`String`型の値を両方パラメータに取るイニシャライザ(`init?(closure:
@escaping (String)->Void, arg:
String)`)を定義してしまうと言うのも一つの手ですが、なんとなくクロージャーのパラメータとしてしか使われない値をイニシャライザの引数にするのも、話を無理に面倒臭くしてしまっているように思われます。

「クロージャー」と言うくらいですから、Swiftのクロージャー式を使われてはいかがでしょうか。

[code]

    override func didMove(to view: SKView) {
    
        if let node = A(closure: hoge) { //<-後で注意があります。
            self.addChild(node)
        }
        if let node = A(closure: {[weak self] in self?.hoge(text: "def")}) { //<-クロージャー式を使う
            node.position = CGPoint(x: 50, y: 50)
            self.addChild(node)
        }
    }
    
[/code]

(当然`hoge(text:)`はコメントインしておいてください。)

`{[weak self] in self?.hoge(text:
"def")}`の部分がクロージャー式です。このクロージャー式自身はパラメーターを取らないので、`()->Void`型のクロージャーということになります。(厳密に言うと話がややこしくなるんで、少々簡略化しています。)

`[weak self]
in`と言うのは循環参照を防ぐために必要になります。あなたの使い方では、子ノードを通じて強参照の連鎖が存在するため、どこかに弱参照を挟み込まないと循環参照になり、メモリリークが発生することになります。

その意味で言うと、実は前半(`A(closure: hoge)`)も`self`が弱参照になるクロージャー式に変えておかないといけません。

[code]

    override func didMove(to view: SKView) {
    
        if let node = A(closure: {[weak self] in self?.hoge()}) {
            self.addChild(node)
        }
        if let node = A(closure: {[weak self] in self?.hoge(text: "def")}) {
            node.position = CGPoint(x: 50, y: 50)
            self.addChild(node)
        }
    }
    
[/code]

よくわからない部分があるかもしれませんが、その際にはコメント等でお知らせください。


CC BY-SA 3.0
2017-08-21T13:06:44.193
37379
2017-08-21T13:06:44.193
13972
37378
answer


いつも回答頂きありがとうございます。

無事に解決することができました。  
回答頂いた内容と以下サイトを見比べ熟読し納得することもできました。  
ありがとうございます。  
<https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html>

閲覧者の為にもソースをもう少し分かりやすい名称に致しました。

名称記載と合わせことの経緯について、  
ボタンクラス内のインスタンス変数にキャラクター画像やスキル画像を渡すと、保管の為にボタンごとにリソースを消費してしまうんじゃないだろうか、ボタンインスタンスを生成した親(GameSceneクラス)にてキャラクターやスキルを管理しているなら、クロージャーで渡した方が便利そうかなと思った次第です。

[code]

    class GameScene: SKScene {
    
        // var characterList: [SKSpriteNode]?
    
        override func didMove(to view: SKView) {
    
            if let buttonA = Button(closure: {[weak self] in self?.buttonPress(text: "abc")}) {
                self.addChild(buttonA)
                //buttonA.handler = {...}  // ボタンの機能を記載。各ボタンと冗長してしまうのでクロージャーで改善
            }
            if let buttonB = Button(closure: {[weak self] in self?.buttonPress(text: "def")}) {
                buttonB.position = CGPoint(x: 50, y: 0)
                self.addChild(buttonB)
            }
            if let buttonC = Button(closure: {[weak self] in self?.buttonPress2(text: "ghi")}) {
                buttonC.position = CGPoint(x: 100, y: 0)
                self.addChild(buttonC)
            }
        }
    
        func buttonPress(text: String) {
            print(text)
            //print(characterList?[1])
        }
    
        func buttonPress2(text: String) {
            print("hoge" + text)
        }
    }
    
    class Button: SKLabelNode {
    
        var handler: () -> Void
    
        init?(closure: @escaping () -> Void) {
    
            self.handler = closure
    
            super.init()
    
            self.isUserInteractionEnabled = true
    
            let node = SKLabelNode(text: "AA")
            self.addChild(node)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            self.handler()
        }
    }
    
[/code]


CC BY-SA 3.0
2017-08-21T18:24:25.010
37386
2017-08-21T18:24:25.010
20142
37378
answer