-2

OK this is rather odd to me, can someone explain to me why handleDismiss can only be called one way?

consider the following:

import UIKit
class MenuLanucher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
{

    //[...] stuff

    let menuItems: [MenuCellSetting] = {
       return [
           MenuCellSetting(name: "Exit Application", imageName: "hamburger", ontap: {
               print("it is exit")
               MenuLanucher.handleDismiss() //<-- 2. this is illegal: 'instance member 'handleDismiss' cannot be used on type 'MenuLanucher'; did you mean to use a value of this type instead?'
           }),
            MenuCellSetting(name: "Create", imageName: "gear", ontap: {
               print("it is job")
               self?.HandleDismiss() //<-- 2. illegal : 'Cannot use optional chaining on non-optional value of type '(MenuLanucher) -> () -> (MenuLanucher)''
           }),
           MenuCellSetting(name: "Cancel", imageName: "gear", ontap: {
               print("it is nothing")
               perform(#selector(MenuLanucher.handleDismiss)) //<-- 3. crashes on run time 'unrecognized selector sent to class'
           })
        ]
      }()

     //[...] yet

     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
         menuItems[indexPath.item].ontap()
         handleDismiss() //<--1. works
     }

      @objc func handleDismiss(){
         print("dismiss works")
     }
}

class MenuCellSetting: NSObject {
     let name: String
    let imageName: String
    let ontap: ()->Void
    init(name: String, imageName: String, ontap: @escaping ()->Void){
         self.name = name
         self.imageName = imageName
         self.ontap = ontap
     }
 }

in this example

  1. fails: at runtime saying 'unrecognized selector sent to class'
  2. fails: at compile saying 'instance member 'handleDismiss' cannot be used on type 'MenuLanucher'; did you mean to use a value of this type instead?'
  3. works

my question is: why is the difference? what is going on?

EDIT: self.?handleDismiss() also fails (see image)

enter image description here

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
Mr Heelis
  • 2,370
  • 4
  • 24
  • 34
  • **Issue #1:** Are you sure that the `handleDismiss` is not nested inside any other function ? Seems like you have added that function inside some other function. Check your closing parenthesis. **Issue 2:** `handleDismiss` is an instance method which can't be called in that way, you need to make that a class function to make it work. – Midhun MP Apr 01 '19 at 10:11

3 Answers3

2

Replace

let menuItems: [MenuCellSetting] = {
  ...
}()

with

lazy var menuItems: [MenuCellSetting] = {
  ...
}()

The problem is that an instance constant is initialized before self even becomes available, therefore self inside your closures actually means a different thing than you expect. lazy var are assigned when they are called for the first time, that is, after self initialization and they can safely access self.

Actually, to prevent a memory leak you also have to use [weak self]:

MenuCellSetting(name: "Create", imageName: "gear", ontap: { [weak self] in
    self?.HandleDismiss()
})
Sulthan
  • 128,090
  • 22
  • 218
  • 270
-1

As "handleDismiss" is not a class method, we can not access it via class name. We need to first create an object and then can access it via dot operator. Please add parenthesis in front of class name to access the "handleDismiss" method.

Also, as "handleDismiss" method is in the same class, you don't have to give the class name. You can directly call the method name. You may have to use self, as the call is in a closure.

let menuItems: [MenuCellSetting] = {
    return [
        MenuCellSetting(name: "Exit Application", imageName: "hamburger", ontap: {
            print("it is exit")
            self.handleDismiss() //<-- 2. this is illegal: 'instance member 'handleDismiss' cannot be used on type 'MenuLanucher'; did you mean to use a value of this type instead?'
        }),
         MenuCellSetting(name: "Create", imageName: "gear", ontap: {
            print("it is job")
        }),
        MenuCellSetting(name: "Cancel", imageName: "gear", ontap: {
            print("it is nothing")
            perform(#selector(self.handleDismiss)) //<-- 3. crashes on run time 'unrecognized selector sent to class'
        })
    ]
}()
Abhisheks
  • 104
  • 1
  • 5
  • You should not just create a new object, otherwise, the method will be called on a new object, not the one you expect. – Aks Apr 01 '19 at 10:29
  • Should not copy the answer from others, after they post the answer. Anyway, you are capturing strong reference which will create memory leak. – Aks Apr 01 '19 at 10:33
-1

First of all handleDismiss is not a class function, So you need to have a class object to call handleDismiss().

Second, you should not just create a new object in menuItems, otherwise this method will be called on this new object and you won't see any effect on your current object.

Example code

struct MenuCellSetting {
    var name: String
    var imageName: String
    var ontap: () -> ()

    init(name: String, imageName: String, ontap: @escaping (() -> ())) {
        self.name = name
        self.imageName = imageName
        self.ontap = ontap
    }
}
class MenuLauncher {
    let menuItems: [MenuCellSetting] = {
        return [
            MenuCellSetting(name: "Exit Application", imageName: "hamburger", ontap: { [weak self] in // Want to make sure that you only capture weak reference, otherwise it will create a memory-leak due to cyclic reference
                print("it is exit")
                self?.handleDismiss()
            }),
            MenuCellSetting(name: "Create", imageName: "gear", ontap: {
                print("it is job")
            }),
            MenuCellSetting(name: "Cancel", imageName: "gear", ontap: { [weak self] in
                print("it is nothing")
                self?.handleDismiss()
            })
        ]
    }()

    func handleDismiss() {
        print("Dismissing")
    }
}
Aks
  • 1,567
  • 13
  • 23
  • @MrHeelis `MenuCellSetting` class definition has no awareness of it being a property of `MenuLauncher` that's why you need to somehow provide the reference to `MenuLauncher` instance, in this case^ a weak `self?` one – Kamil.S Apr 01 '19 at 10:58
  • @MrHeelis, yes you have to use `lazy` also. As I didn't compile the code itself, didn't get the error. Great that it's worked for you. – Aks Apr 01 '19 at 11:30