【问题标题】:Swift and @objc methods: How do I transform a method so that it can be represented by @objc?Swift 和@objc 方法:如何转换方法以便它可以由@objc 表示?
【发布时间】:2021-06-06 20:25:20
【问题描述】:

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

如何使用@objc 语法来符合我的方法? 有没有另一种不使用#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()
    }

【问题讨论】:

  • 你在这里省略了很多重要信息(或者我假设你是)。 startGame 需要与您的其他函数在同一级别定义 - 您当前的代码使其看起来好像嵌套在 inside 另一个函数中。如果是这样,那是你的问题。如果不是这种情况,请将代码显示为真实代码中的结构。
  • 是的,我确实省略了很多信息。我更新了代码。我不认为我的方法嵌套在这里。让我知道我是否应该包含更多代码。谢谢!!
  • 在我看来,func startGame() 仍然是 inside viewDidLoad。它必须是与viewDidLoad 处于同一级别的自己的函数——不在其内部。
  • ["silkworm"]@objc 之间应该是两个}} 而不是一个}

标签: objective-c swift function methods


【解决方案1】:

一些观察:

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

  2. 这两个动作/选择器方法应该带有@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 允许你在函数内部声明函数。因此,请确保这些选择器方法是视图控制器的实例方法,而不是例如在另一个函数中声明的私有函数(即viewDidLoad)。

    如果您开始忘记大括号,您可以选择此文件中的所有代码并按 control+i(或在 Xcode 菜单中,“Editor” » “结构” » “重新缩进”)。如果您在某处缺少大括号,代码的重新缩进将使您跳出来。


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

//  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 { !$0.isEmpty }
    }

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

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

  • 我喜欢将方法放入扩展中,以便它们在逻辑组中。这使得一目了然地了解正在发生的事情变得更加容易。您还可以折叠/展开这些扩展,以便在编辑时可以专注于相关代码。

  • MARK cmets 只是在 Xcode 跳转栏中放置了漂亮的节标题,再次,使在代码中跳转更容易。

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

【讨论】:

  • 非常感谢您的详细回复。
  • 我肯定需要更好地组织我的代码。谢谢你的建议!
【解决方案2】:

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

@objc
func functionName() {
}

对你来说是:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多