我认为我的istringstream istr(str);
char token;
istr >> token;
string t;
t.push_back(token);
istr >> token;
while (token != ' ' && token != '+' && token != '-') {
t.push_back(token);
istr >> token;
}
提供了通过轻击手势解除键盘功能的非常简单的协议扩展。这是我的代码:
UIViewController
问题是上面的代码在这一行引发编译错误:
@objc protocol KeyboardDismissing {
func on(tap: UITapGestureRecognizer)
}
extension KeyboardDismissing where Self: UIViewController {
func addDismissalGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
view.addGestureRecognizer(tap)
}
func on(tap: UITapGestureRecognizer) {
dismissKeyboard()
}
func dismissKeyboard() {
view.endEditing(true)
}
}
显示错误消息:
'#selector'的参数是指没有暴露给Objective-C的实例方法'on(tap :)'
建议在let tap = UITapGestureRecognizer(target: self, action: #selector(Self.on(tap:)))
@objc
来“修复”
好的,我添加标签:
func on(tap: UITapGestureRecognizer)
但是,它会在这个新添加的@objc func on(tap: UITapGestureRecognizer) {
dismissKeyboard()
}
标记上抛出不同的编译错误,并显示错误消息:
@objc只能用于类的成员,@ objc协议和类的具体扩展
建议通过删除完全相同的标记“修复它”,我刚刚被告知添加。
我原本以为在我的协议定义解决任何@objc
问题之前添加@objc
但显然情况并非如此,这些周期性错误消息/建议没有丝毫帮助。我已经在各地添加/删除#selector
标记,将方法标记为@objc
,将方法放在协议的定义中等等。
我在协议定义中添加的内容也无关紧要使扩展名保持不变,以下示例不起作用,也不对协议定义中声明的方法进行任何组合:
optional
这让我觉得它可以通过编译作为一个独立的协议来工作,但第二个我尝试将它添加到一个视图控制器:
@objc protocol KeyboardDismissing {
func on(tap: UITapGestureRecognizer)
}
它吐出原来的错误。
有人可以解释我做错了什么以及如何编译它?
注意:
我看过this question,但它是针对Swift 2.2而不是Swift 3,一旦创建了一个继承自示例中定义的协议的视图控制器类,答案就会编译。
我也看了this question,但答案使用的是class ViewController: UIViewController, KeyboardDismissing {}
,这不是我所追求的。
如果还有其他看似重复的问题,请告知我们。
答案 0 :(得分:35)
马特的回答是正确的。但是,我只想补充一点,如果您要处理 #selector 以使用NotificationCenter通知,您可以尝试使用闭包版本来避免 #selector 。< / p>
示例:
而不是写作:
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShow(_:)),
// !!!!!
// compile error: cannot be included in a Swift protocol
name: .UIKeyboardWillShow,
object: nil
)
}
func keyboardWillShow(_ notification: Notification) {
// do stuff
}
}
你可以写:
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
// NotificationCenter observers
NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
}
func keyboardWillShow(_ notification: Notification) {
// do stuff
}
}
答案 1 :(得分:10)
这是一个Swift协议扩展。 Swift协议扩展对于Objective-C是不可见的,无论如何;它对它们一无所知。但是#selector
是关于Objective-C看到和调用你的函数。这不会发生,因为您的on(tap:)
函数在协议扩展中仅定义 。因此,编译器正确地阻止了你。
这个问题是一大类问题之一,人们认为通过尝试将Objective-C可调用功能(选择器,委托方法,等等)注入类中来处理Cocoa时,他们会变得很聪明。通过协议扩展。这是一个吸引人的想法,但它不会起作用。
答案 2 :(得分:3)
从另一个角度来看,我又做了一次尝试。我在我的许多开发中使用了一个协议,以全局的方式处理UINavigationBar
的样式,包含在其中的每个UIViewController
。
执行此操作的最大问题之一是返回到上一个UIViewController
(pop)的标准行为,并以模态方式关闭UIViewController
。让我们看看一些代码:
public protocol NavigationControllerCustomizable {
}
extension NavigationControllerCustomizable where Self: UIViewController {
public func setCustomBackButton(on navigationItem: UINavigationItem) {
let backButton = UIButton()
backButton.setImage(UIImage(named: "navigationBackIcon"), for: .normal)
backButton.tintColor = navigationController?.navigationBar.tintColor
backButton.addTarget(self, action: #selector(defaultPop), for: .touchUpInside)
let barButton = UIBarButtonItem(customView: backButton)
navigationItem.leftBarButtonItem = barButton
}
}
这是原始协议的一个非常简化(略微修改)的版本,虽然值得解释这个例子。
如您所见,协议扩展中正在设置 #selector 。我们知道,协议扩展不会暴露给Objective-C,因此会产生错误。
我的解决方案是将处理所有UIViewController
(pop和dismiss)标准行为的方法包装在另一个协议中,并将UIViewController
扩展到它。在代码中查看:
public protocol NavigationControllerDefaultNavigable {
func defaultDismiss()
func defaultPop()
}
extension UIViewController: NavigationControllerDefaultNavigable {
public func defaultDismiss() {
dismiss(animated: true, completion: nil)
}
public func defaultPop() {
navigationController?.popViewController(animated: true)
}
}
通过此解决方法,实现UIViewController
的所有NavigationControllerCustomizable
将立即拥有NavigationControllerDefaultNavigable
中定义的方法及其默认实现,因此可以从Objective-C访问以创建表达式输入 #selector ,不会出现任何类型的错误。
我希望这个解释可以帮助别人。
答案 3 :(得分:2)
正如Matt所说,你不能在协议中实现@objc
方法。 Frédéric的答案涵盖Notifications
,但您对标准Selectors
有什么看法?
假设你有一个协议&amp;扩展,像这样
protocol KeyboardHandler {
func setupToolbar()
}
extension KeyboardHandler {
func setupToolbar() {
let toolbar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done",
style: .done,
target: self,
action: #selector(self.donePressed))
}
@objc func donePressed() {
self.endEditing(true)
}
}
如我们所知,这将产生错误。我们能做的就是利用回调。
protocol KeyboardHandler {
func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void))
}
extension KeyboardHandler {
func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void)) {
let toolbar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done",
style: .done,
target: self,
action: nil
callback(doneButton)
}
}
然后,为要实现协议的类添加扩展名
extension ViewController: KeyboardHandler {
func addToolbar(textField: UITextField) {
addToolbar(textField: textField) { doneButton in
doneButton.action = #selector(self.donePressed)
}
}
@objc func donePressed() {
self.view.endEditing(true)
}
}
不是在创建时设置操作,而是在回调中创建后立即设置。
这样,您仍然可以获得所需的功能,并且可以在您的课程中调用该功能(例如ViewController
),甚至不会看到回调!
答案 4 :(得分:0)
@FrédéricAdda的回答是to unregister your observer的缺点,因为它使用基于块的方式添加观察者。在iOS 9及更高版本中,添加观察者的“正常”方式将对观察者(因此the developer doesn't have to unregister the observer)具有微弱的引用。
以下方法将使用通过协议扩展添加观察者的“常规”方法。它使用桥接类来保存选择器。
专业人士:
骗局:
self
完全初始化后,执行一次。代码:
/// Not really the user info from the notification center, but this is what we want 99% of the cases anyway.
public typealias NotificationCenterUserInfo = [String: Any]
/// The generic object that will be used for sending and retrieving objects through the notification center.
public protocol NotificationCenterUserInfoMapper {
static func mapFrom(userInfo: NotificationCenterUserInfo) -> Self
func map() -> NotificationCenterUserInfo
}
/// The object that will be used to listen for notification center incoming posts.
public protocol NotificationCenterObserver: class {
/// The generic object for sending and retrieving objects through the notification center.
associatedtype T: NotificationCenterUserInfoMapper
/// For type safety, only one notification name is allowed.
/// Best way is to implement this as a let constant.
static var notificationName: Notification.Name { get }
/// The selector executor that will be used as a bridge for Objc - C compability.
var selectorExecutor: NotificationCenterSelectorExecutor! { get set }
/// Required implementing method when the notification did send a message.
func retrieved(observer: T)
}
public extension NotificationCenterObserver {
/// This has to be called exactly once. Best practise: right after 'self' is fully initialized.
func register() {
assert(selectorExecutor == nil, "You called twice the register method. This is illegal.")
selectorExecutor = NotificationCenterSelectorExecutor(execute: retrieved)
NotificationCenter.default.addObserver(selectorExecutor, selector: #selector(selectorExecutor.hit), name: Self.notificationName, object: nil)
}
/// Retrieved non type safe information from the notification center.
/// Making a type safe object from the user info.
func retrieved(userInfo: NotificationCenterUserInfo) {
retrieved(observer: T.mapFrom(userInfo: userInfo))
}
/// Post the observer to the notification center.
func post(observer: T) {
NotificationCenter.default.post(name: Self.notificationName, object: nil, userInfo: observer.map())
}
}
/// Bridge for using Objc - C methods inside a protocol extension.
public class NotificationCenterSelectorExecutor {
/// The method that will be called when the notification center did send a message.
private let execute: ((_ userInfo: NotificationCenterUserInfo) -> ())
public init(execute: @escaping ((_ userInfo: NotificationCenterUserInfo) -> ())) {
self.execute = execute
}
/// The notification did send a message. Forwarding to the protocol method again.
@objc fileprivate func hit(_ notification: Notification) {
execute(notification.userInfo! as! NotificationCenterUserInfo)
}
}
在我的GitHub上(您无法通过Cocoapods使用该代码):https://github.com/Jasperav/JVGenericNotificationCenter
答案 5 :(得分:0)
答案 6 :(得分:0)
这是一个类似的用例,您可以通过选择器调用方法,而无需像通过使用dynamic关键字那样迅速使用@objc。这样做是在指示编译器隐式使用动态调度。
import UIKit
protocol Refreshable: class {
dynamic func refreshTableData()
var tableView: UITableView! {get set}
}
extension Refreshable where Self: UIViewController {
func addRefreshControl() {
tableView.insertSubview(refreshControl, at: 0)
}
var refreshControl: UIRefreshControl {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
return control
} else {
let control = UIRefreshControl()
control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
_refreshControl[tmpAddress] = control
return control
}
}
}
}
fileprivate var _refreshControl = [String: AnyObject]()
class ViewController: UIViewController: Refreshable {
@IBOutlet weak var tableView: UITableView! {
didSet {
addRefreshControl()
}
}
func refreshTableData() {
// Perform some stuff
}
}