2015-10-28 3 views
2

В беспорядке с Swift сегодня я наткнулся на странную вещь. Ниже приведен пример модульного теста, который показывает некоторые неожиданные действия при использовании AnyObject Swift.Странное поведение AnyObject Swift

class SwiftLanguageTests: XCTestCase { 

    class TestClass { 
     var name:String? 
     var xyz:String? 
    } 

    func testAccessingPropertiesOfAnyObjectInstancesReturnsNils() { 
     let instance = TestClass() 
     instance.xyz = "xyz" 
     instance.name = "name" 

     let typeAnyObject = instance as AnyObject 

     // Correct: Won't compile because 'xyz' is an unknown property in any class. 
     XCTAssertEqual("xyz", typeAnyObject.xyz) 

     // Unexpected: Will compile because 'name' is a property of NSException 
     // Strange: But returns a nil when accessed. 
     XCTAssertEqual("name", typeAnyObject.name) 

    } 
} 

Этот код является упрощение какой-то другой код, где есть Swift функция, которая может возвращать только AnyObject.

Как и следовало ожидать, после создания экземпляра TestClass, отбрасывая его AnyObject и установив другую переменную, доступ к свойству xyz не будет компилироваться, так как AnyObject не обладают таким свойством.

Но удивительно, что свойство, называемое name, принимается компилятором, потому что есть свойство под этим именем на NSException. Похоже, что Swift с удовольствием принимает любое имя свойства, если оно существует где-то во время выполнения.

Следующее неожиданное поведение и все, что было начато, это то, что попытка получить доступ к свойству name возвращает нуль. Наблюдая за различными переменными в отладчике, я вижу, что typeAnyObject указывает на исходный экземпляр TestClass, а его свойство name имеет значение «имя».

Swift не выдает ошибки при доступе к typeAnyObject.name, поэтому я ожидаю, что он найдет и вернет «имя». Но вместо этого я получаю нуль.

Мне было бы интересно, если кто-нибудь может пролить свет на то, что здесь происходит?

Моя основная забота состоит в том, что я ожидал бы, что Свифт либо выбросит ошибку при доступе к свойству, которого нет на AnyObject, либо найти и вернуть правильное значение. В настоящее время ничего не происходит.

ответ

2

Похожие, как в Objective-C, где вы можете отправить произвольные сообщения id, Произвольные свойства и методы можно вызвать в экземпляре AnyObject в Swift. Однако данные различны, и в документе «Использование Swift with Cocoa and Objective-C» задокументировано в Interacting with Objective-C APIs .

Swift включает в себя тип AnyObject, который представляет собой какой-то объект. Это похоже на тип Objective-C id. Быстрый импорт id как AnyObject, который позволяет вам писать безопасный код Swift при сохранении гибкости нетипизированного объекта.
...
Вы можете вызвать любой метод Objective-C и получить доступ к любому свойству в значение AnyObject без кастинга для более конкретного типа класса. Это включает в себя совместимые с Objective-C методы и свойства, помеченные атрибутом @objc.
...
Когда вы вызываете метод со значением AnyObject, этот вызов метода ведет себя как неявно развернутый необязательный. Вы можете использовать тот же необязательный синтаксис цепочки, который вы бы использовали для дополнительных методов в протоколах, чтобы при необходимости вызвать метод на AnyObject.

Вот пример:

func tryToGetTimeInterval(obj : AnyObject) { 
    let ti = obj.timeIntervalSinceReferenceDate // NSTimeInterval! 
    if let theTi = ti { 
     print(theTi) 
    } else { 
     print("does not respond to `timeIntervalSinceReferenceDate`") 
    } 
} 

tryToGetTimeInterval(NSDate(timeIntervalSinceReferenceDate: 1234)) 
// 1234.0 

tryToGetTimeInterval(NSString(string: "abc")) 
// does not respond to `timeIntervalSinceReferenceDate` 

obj.timeIntervalSinceReferenceDate является неявно развернутым опциональным и nil, если объект не имеет это свойство.

Вот пример для проверки и вызова метода :

func tryToGetFirstCharacter(obj : AnyObject) { 
    let fc = obj.characterAtIndex // ((Int) -> unichar)! 
    if let theFc = fc { 
     print(theFc(0)) 
    } else { 
     print("does not respond to `characterAtIndex`") 
    } 
} 

tryToGetFirstCharacter(NSDate(timeIntervalSinceReferenceDate: 1234)) 
// does not respond to `characterAtIndex` 

tryToGetFirstCharacter(NSString(string: "abc")) 
// 97 

obj.characterAtIndex является неявно развернутым опциональным закрытием. Этот код может быть упрощена с помощью дополнительного цепочки:

func tryToGetFirstCharacter(obj : AnyObject) { 
    if let c = obj.characterAtIndex?(0) { 
     print(c) 
    } else { 
     print("does not respond to `characterAtIndex`") 
    } 
} 

В вашем случае TestClass не имеет @objc свойства.

let xyz = typeAnyObject.xyz // error: value of type 'AnyObject' has no member 'xyz' 

не компилируется, потому что xyz свойство неизвестен компилятору.

let name = typeAnyObject.name // String! 

компилируется, потому что - как вы заметили - NSException имеет name свойство. Значение, однако, равно nil, потому что TestClass не имеет Objective-C совместимыйname способ. Как и выше, вы должны использовать необязательное связывание для безопасного разворачивания значения (или теста против nil).

Если ваш класс является производным от NSObject

class TestClass : NSObject { 
    var name : String? 
    var xyz : String? 
} 

затем

let xyz = typeAnyObject.xyz // String?! 

компилируется. (В качестве альтернативы, отметьте класс или свойства с @objc.) Но теперь

let name = typeAnyObject.name // error: Ambigous use of `name` 

не компилируется больше. Причина в том, что и TestClass, и NSException имеют свойство name, но с разными типами (String? vs String), , поэтому тип этого выражения неоднозначен. Эта неопределенность может быть разрешен только (необязательно) смазывают AnyObject обратно к TestClass на:

if let name = (typeAnyObject as? TestClass)?.name { 
    print(name) 
} 

Заключение:

  • Вы можете вызвать любой метод/свойство на примере AnyObject если метод/свойство совместимо с Objective-C.
  • Вы должны проверить неявно развернутый необязательный параметр nil или использовать необязательное связывание, чтобы проверить, что экземпляр действительно имеет этот метод/свойство .
  • Неоднозначность возникает, если более одного класса имеет (Objective-C) совместимые методы с тем же именем, но с разными типами.

В частности, из-за последнюю точку, я бы попытаться избежать этого механизма, если это возможно, и, необязательно, приведение к известному классу вместо (как в предыдущем примере).

+0

Спасибо за это подробное объяснение. Это действительно помогло. – drekka

0

ничего не имеет с NSException!

от компании Apple документации:

протокол AnyObject {...}

Протокол, к которому все классы неявно соответствовать.

При использовании в качестве конкретного типа, все известные способы @objc и свойства доступны, как неявно развернутый необязательные методы и свойства, соответственно, на каждом экземпляре AnyObject

имя @objc свойство, хуг не ,

попробовать это :-)

пусть typeAnyObject = экземпляр, как любой

или

@objc класса TestClass: NSObject { имя Var: String? var xyz: String? }

let instance = TestClass() instance.xyz = "xyz" экземпляр.имя = "имя"

пусть typeAnyObject = экземпляр в качестве AnyObject

typeAnyObject.name // не компилируется в настоящее время

Смежные вопросы