Swift 和@objc 方法:如何转换方法以便它可以由@objc 表示?

Swift and @objc methods: How do I transform a method so that it can be represented by @objc?

由于 Swift 是我的第一门编程语言,而且我还没有 Objective C 经验... 我很难理解@objc 与方法的关系。

如何使用@objc 语法来符合我的方法? 是否有另一种方法 select 不使用 #selector 语法的方法?

这是我遇到困难的代码(主要是 @objc 对 startGame 方法的尝试):

import UIKit

@objc class ViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()


override func viewDidLoad() {
    super.viewDidLoad()
    
navigationItem.rightBarButtonItem = 
UIBarButtonItem(barButtonSystemItem: .add, target: self, action: 
#selector(promptForAnswer))
    
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New 
Word", style: .plain, target: self, action: #selector(startGame))
   
    
    if let startWordsURL = Bundle.main.url(forResource: "start", 
withExtension: "txt") {
        if let startWords = try? String(contentsOf: startWordsURL) {
            allWords = startWords.components(separatedBy: "\n")
        }
    }
    if allWords.isEmpty {
        allWords = ["silkworm"]
    }
    
@objc func startGame() {
        title = allWords.randomElement()  
        usedWords.removeAll(keepingCapacity: true) 
        tableView.reloadData() 
    {
    startGame()
    }

我认为您的@objc 方法有语法错误。应该是:

@objc
func functionName() {
}

对你来说它将是:

@objc
func startGame() {
        title = allWords.randomElement()  
        usedWords.removeAll(keepingCapacity: true) 
        tableView.reloadData() 
   }

几点观察:

  1. 您的视图控制器声明中不需要 @objc

  2. 两个action/selector方法应该带有@objc限定符。

  3. 我建议您为这两个方法起一个描述性的名称,清楚地表明它们在用户点击特定按钮时被调用,例如:

    @objc func didTapNewWord(_ sender: UIBarButtonItem) {
        ...
    }
    
    @objc func didTapAdd(_ sender: UIBarButtonItem) {
        ...
    }
    

    注意,我还为这些方法添加了一个参数。这使得它们是按钮处理程序这一点完全明确。您不需要这样做,但现在您可以扫一眼代码并立即理解该方法的用途。

    显然,您将相应地更改添加这些目标操作的代码:

    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
                                                        target: self,
                                                        action: #selector(didTapAdd(_:)))
    
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
                                                       style: .plain,
                                                       target: self,
                                                       action: #selector(didTapNewWord(_:)))
    
  4. 小心放置牙套。 Swift 允许您在函数内部声明函数。因此,请确保这些 select 或方法是视图控制器的实例方法,而不是例如在另一个函数(即 viewDidLoad)中声明的私有函数。

    如果你开始忘记大括号,你可以select这个文件中的所有代码然后按control+i(或在 Xcode 菜单中,“编辑器”»“结构”»“Re-Indent”)。如果你在某处缺少大括号,代码的 re-indentation 会让你跳出来。


所以把它们放在一起,你会得到类似的东西:

//  ViewController.swift

import UIKit

class ViewController: UITableViewController {
    var allWords = [String]()
    var usedWords = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        configureButtons()
        fetchData()
    }
}

// MARK: - Actions

extension ViewController {
    @objc func didTapNewWord(_ sender: UIBarButtonItem) {
        startGame()
    }

    @objc func didTapAdd(_ sender: UIBarButtonItem) {
        ...
    }
}

// MARK: - UITableViewDataSource

extension ViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        ...
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...
    }
}

// MARK: - Private utility methods

private extension ViewController {
    func configureButtons() {
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
                                                            target: self,
                                                            action: #selector(didTapAdd(_:)))

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
                                                           style: .plain,
                                                           target: self,
                                                           action: #selector(didTapNewWord(_:)))
    }

    func fetchData() {
        guard
            let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt"),
            let startWords = try? String(contentsOf: startWordsURL).components(separatedBy: "\n"),
            !startWords.isEmpty
        else {
            allWords = ["silkworm"]
            return
        }

        allWords = startWords.filter { ![=12=].isEmpty }
    }

    func startGame() {
        title = allWords.randomElement()
        usedWords.removeAll(keepingCapacity: true)
        tableView.reloadData()
    }
}

对我的代码示例的一些最终观察(与您的问题没有直接关系,但只是为了解释为什么像我那样构建它):

  • 我喜欢把方法放在扩展中,这样它们就在逻辑组中。这样可以更轻松地一目了然地了解正在发生的事情。您还可以 collapse/expand 这些扩展,以便您在编辑时可以专注于相关代码。

  • MARK 注释只是将不错的部分 headers 放在 Xcode 跳转栏中,再次让您更容易在代码中跳转。

  • 除了用“业务逻辑”调用某些方法外,我个人不会在操作方法中放置任何东西。这将“视图”代码(按钮的处理)与业务逻辑分开。有一天,您可能会开始使用视图模型或演示器 objects,因此现在接受这种职责分离将使最终的过渡更容易。当您着手编写单元测试时,它也将使编写单元测试变得更容易(例如,您为“开始游戏”逻辑编写单元测试,而不是点击按钮)。