什么是在`id`上设置属性的快速等价物?

时间:2015-06-16 14:30:27

标签: objective-c swift

我想知道在id上调用方法的Swift等价物是什么,其中方法的可用性是在运行时确定的。具体来说,我希望在Swift中使用这种模式:

-(IBAction) handleEvent:(id) sender {
    BOOL didDisable = NO;
    if([sender respondsToSelector:@selector(setEnabled:)]) {
        [sender setEnabled:NO];
        didDisable = YES;
    }
    [self doSomethingAsyncWithCompletionHandler:^{
        if(didDisable) {
            [sender setEnabled:YES];
        }
    }];
}

最大的问题是setEnabled:作为属性(例如UIBarItem)在Swift中导入,并且以下任何构造都没有编译

func handleEvent(sender: AnyObject) {
    // Error: AnyObject does not have a member named "enabled"
    sender.enabled? = false

    // Error: (BooleanLiteralCompatible) -> _ is not identical to Bool
    sender.setEnabled?(false)
}

4 个答案:

答案 0 :(得分:24)

事实上,您可以完全按照以前的方式执行此操作:通过调用respondsToSelector:。实际上,这正是你提出的表达式所做的:

sender.setEnabled?(false)

该表达式实际上是一种简写 - 它首先调用respondsToSelector:,然后仅在setEnabled:测试通过时才调用respondsToSelector:。不幸的是,正如你所说,你无法编译代码。然而,这仅仅是斯威夫特已知的可用方法库的一个怪癖。事实是,虽然编译它有点棘手,但它可以完成 - 一旦你得到它编译,它的行为就像你期望的那样。

但是,我不打算解释如何进行编译,因为我不想鼓励这种诡计。在Swift中不鼓励这种动态消息传递。一般而言,Swift中不需要动态消息传递技巧,例如键值编码,内省等,并且与Swift的强类型方法不一致。最好以Swift的方式做事,通过选择性地转换为你有理由相信这个东西并且具有enabled属性的东西。例如:

@IBAction func doButton(sender: AnyObject) {
    switch sender {
    case let c as UIControl: c.enabled = false
    case let b as UIBarItem: b.enabled = false
    default:break
    }
}

或者:

@IBAction func doButton(sender: AnyObject) {
    (sender as? UIControl)?.enabled = false
    (sender as? UIBarItem)?.enabled = false
}

答案 1 :(得分:6)

在Swift 2.0 beta 4中,您的祈祷得到了回应;这段代码变得合法:

EventLogReader log2 = new EventLogReader("Microsoft-Windows-TaskScheduler/Operational");

for (EventRecord eventInstance = log2.ReadEvent(); null != eventInstance; eventInstance = log2.ReadEvent())
{
    if (!eventInstance.Properties.Select(p => p.Value).Contains("\\{YOUR SCHEDULED TASK NAME}}"))
    {
        continue;
    }

    Console.WriteLine("-----------------------------------------------------");
    Console.WriteLine("Event ID: {0}", eventInstance.Id);
    Console.WriteLine("Publisher: {0}", eventInstance.ProviderName);

    try
    {
        Console.WriteLine("Description: {0}", eventInstance.FormatDescription());
    }
    catch (EventLogException)
    {
    }

    EventLogRecord logRecord = (EventLogRecord)eventInstance;
    Console.WriteLine("Description: {0}", logRecord.FormatDescription());
}

答案 2 :(得分:2)

如果您想避免使用respondsToSelector:方法,则可以定义协议。然后扩展您要使用的类,这些类已经符合此协议的定义(已启用),并使用符合您协议的通用变量定义该函数。

protocol Enablable{
    var enabled:Bool { get set }
}

extension UIButton        : Enablable {}
extension UIBarButtonItem : Enablable {}

//....

func handleEvent<T:Enablable>(var sender: T) {
    sender.enabled = false
}

如果您需要将其与IBAction方法一起使用,则需要进行一些处理,因为您不能直接在它们上使用泛型。

@IBAction func handleEventPressed(sender:AnyObject){
    handleEvent(sender);
}

我们还需要一个没有Enablable一致性的匹配泛型函数,这样我们就可以在不知道发送者是否可用的情况下调用handleEvent。幸运的是,编译器足够聪明,可以找出要使用的两个通用函数中的哪一个。

func handleEvent<T>(var sender: T) {
    //Do Nothing case if T does not conform to Enablable
}

答案 3 :(得分:1)

作为解决方法/替代方案,您可以使用键值编码

@IBAction func handler(sender: AnyObject) {

    if sender.respondsToSelector("setEnabled:") {
        sender.setValue(false, forKey:"enabled")
    }
}

这适用于Swift 1.2(Xcode 6.4)和Swift 2.0(Xcode 7 beta)。