在Swift 4中,如何使用#selector()处理@objc推理弃用?


131

我正在尝试将项目的源代码从Swift 3转换为Swift4。Xcode给我的一个警告是关于选择器的信息。

例如,我使用常规选择器将目标添加到按钮,如下所示:

button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)

这是显示的警告:

“ #selector”的参数引用“ ViewController”中的实例方法“ myAction()”,该方法依赖于Swift 4中弃用的“ @objc”属性推断

添加“ @objc”以将该实例方法公开给Objective-C

现在,点击Fix错误消息会对我的功能执行此操作:

// before
func myAction() { /* ... */ }

// after
@objc func myAction() { /* ... */ }

我真的不想重命名所有功能以包括该@objc标记,并且我认为这不是必需的。

如何重写选择器以应对弃用?


相关问题:


3
不,它们标记为@objc 现在有必要为了揭露他们的OBJ-C,并因此与选择使用。
Hamish

3
因此,不赞成使用的部分将公共访问功能推断为@objc?有点烦人,但是我通常将这些功能设为私有,@objc无论如何都需要将其标记为。
康纳

2
有关更改的更多信息,请参见SE-0160。另一种选择是将给定的类标记为@objcMembers,以便向Obj-C公开所有与Obj-C兼容的成员,但是我不建议您这样做,除非您确实需要公开整个类。
Hamish

3
如提案中所述,@ LinusGeffarth可能会不必要地增加二进制文件的大小,而动态链接将花费更长的时间。对于您要明确指出的要用于Obj-C的特定事物的额外清晰度,我真的不觉得太麻烦。
Hamish

1
尝试过,不起作用。
LinusGeffarth

Answers:


156

修复程序是正确的-选择器没有任何改变,您可以更改以使其所引用的方法公开给Objective-C。

首先发出此警告的全部原因是SE-0160的结果。在Swift 4之前,可以推断继承类的internal目标NSObject类具有更高的Objective-C兼容成员@objc,因此可以将它们暴露给Objective-C,因此允许使用选择器调用它们(因为需要Obj-C运行时才能查找该方法给定选择器的实现)。

但是在Swift 4中,情况不再如此。现在,仅推断出非常具体的声明@objc,例如,@objc方法的覆盖,@objc协议要求的实现以及带有暗示属性的声明@objc,例如@IBOutlet

上面链接的提案中所述,其背后的动机首先是为了防止NSObject继承类中的方法重载由于具有相同的选择器而彼此冲突。其次,它不必为不需要暴露于Obj-C的成员生成thunk,从而有助于减小二进制文件的大小,其三,提高了动态链接的速度。

如果要将成员公开给Obj-C,则需要将其标记为@objc,例如:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        button.addTarget(self, action: #selector(foo), for: .touchUpInside)
    }

    @objc func foo() {
       // ... 
    }
}

(在选择了“最小推断”选项的情况下,迁移程序应使用选择器自动为您执行此操作)

要将一组成员暴露给Obj-C,可以使用@objc extension

@objc extension ViewController {

    // both exposed to Obj-C
    func foo() {}
    func bar() {}
}

这会将其中定义的所有成员公开给Obj-C,并为所有无法公开给Obj-C的成员提供错误(除非明确标记为@nonobjc)。

如果您有一个班级,需要让所有 Obj-C兼容成员都可以使用Obj-C,则可以将该班级标记为@objcMembers

@objcMembers
class ViewController: UIViewController {
   // ...
}

现在,可以推断为所有成员@objc。但是,我不建议这样做,除非您确实需要让所有成员都接触Obj-C,因为上面提到的不必要地暴露成员的弊端。


2
为什么将代码转换为最新语法时Xcode不会自动执行此操作?
LinusGeffarth'6

1
我想知道,@IBAction还自动@objc吗?到目前为止,情况并非如此,但这是合乎逻辑的。编辑:nvm,该建议书明确指出这@IBAction足以满足需要的方法@objc
Sulthan

8
我什么都不懂。我从来没有Objective-C代码。是否没有将目标添加到按钮的纯Swift方法?还是使用选择器?我不想让我的代码充满这个属性。是因为UIButton源自某些NSObject / Obj-c-thing?
性病

11
@Sti因此直接回答“ 是否没有将目标添加到按钮的纯Swift方法?还是使用选择器? ” –不,没有使用选择器分配给方法的“纯Swift”方法。他们依靠Obj-C运行时来查找方法实现以调用特定的选择器-Swift运行时不具备该功能。
Hamish

12
男人选择器是一团糟。似乎所有其他快速更新都使他们感到困惑。为什么我们不能只为方法提供一个纯粹的快速自动完成选择器。使代码看起来很难看。
约翰·里塞尔瓦托

16

作为Apple的官方文档。您需要使用@objc来调用选择器方法。

在Objective-C中,选择器是一种引用Objective-C方法名称的类型。在Swift中,Objective-C选择器由Selector结构表示,可以使用#selector 表达式构造。要为可以从Objective-C调用的方法创建选择器,请传递该方法的名称,例如 #selector(MyViewController.tappedButton(sender:))。要为属性的Objective-C getter或setter方法构造选择器,请传递以getter:setter:标签作为前缀的属性名称,例如 #selector(getter: MyViewController.myButton)


我从中了解到的是,在某些情况下,无论我是否要从Objective-C调用它,都需要在函数的开头添加@objc。我正在学习Swift,已经使用Obj-C多年了
SundialSoft '18

10

从我认为Swift 4.2开始,您要做的就是将@IBAction分配给您的方法,您可以避免使用这种愚蠢的@objc注释

```

let tap  =  UITapGestureRecognizer(target: self, action: #selector(self.cancel))


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}

1
这还将告诉接口构建器可以连接此功能,而这可能不是您想要的。它也和@objc做同样的事情。
Josh Paradroid

2

正如其他答案中已经提到的那样,没有办法避免@objc为选择器添加注释。

但是可以通过执行以下步骤来消除OP中提到的警告:

  1. 转到构建设置
  2. 搜索关键字@objc
  3. Swift 3 @objc接口的值设置为Off

以下是说明上述步骤的屏幕截图:

静默警告“ Swift 3 @objc接口”

希望这可以帮助


从总体上看,这如何影响您的项目?
Zonily Jame '18

1
* .ipa或* .xarchive大小以及构建时间如何?
Zonily Jame

@ZonilyJame我没有注意到构建时间,但是对IPA大小没有影响
S1LENT WARRIOR

如果您按照Apple准则使用Swift 4.0+版本,则不能使用此建议值。检查此帮助。apple.com/xcode
mac/

1
您可以通过在Xcode 11中将值设置为Default来更新此答案。在项目和目标上都设置该值以完全消除警告很重要。
Tommie C.

2

如果在视图控制器中需要目标c成员,只需在视图控制器顶部添加 @objcMembers。您可以通过在代码中添加IBAction来避免这种情况。

@IBAction func buttonAction() {

}

确保将此插座连接到情节提要中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.