#objective-c #closures #swift #block
#objective-c #замыкания #swift #блок
Вопрос:
В Objective-C я часто обхожу блоки. Я использую их очень часто для реализации шаблонов, которые помогают избежать хранения данных в переменных экземпляра, тем самым избегая проблем с потоками / синхронизацией.
Например, я назначаю их CAAnimation
через -[CAAnimation setValue:forKey:]
, чтобы я мог выполнить блок после завершения анимации. (Objective-C может обрабатывать блоки как объекты; вы также можете выполнять [someBlock copy]
и [someBlock release]
.)
Однако попытка использовать эти шаблоны в Swift вместе с Objective-C кажется очень сложной. (Редактировать: и мы видим, что язык все еще находится в процессе изменения: адаптировали код, чтобы он работал на Xcode6-beta2, предыдущая версия работала на Xcode6-beta1.)
Например, я не могу преобразовать AnyObject
обратно в блок / замыкание. Следующее выдает ошибку компилятора:
override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
let completion : AnyObject! = anim.valueForKey("completionClosure")
(completion as (@objc_block ()->Void))()
// Cannot convert the expression's type 'Void' to type '@objc_block () -> Void'
}
Я нашел обходной путь, но он довольно уродливый, ИМХО: в моем связующем заголовке у меня есть:
static inline id blockToObject(void(^block)())
{
return block;
}
static inline void callBlockAsObject(id block)
{
((void(^)())block)();
}
И теперь я могу сделать это в Swift:
func someFunc(completion: (@objc_block ()->Void))
{
let animation = CAKeyframeAnimation(keyPath: "position")
animation.delegate = self
animation.setValue(blockToObject(completion), forKey: "completionClosure")
…
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
let completion : AnyObject! = anim.valueForKey("completionClosure")
callBlockAsObject(completion)
}
Это работает, но мне понадобилась бы новая функция для каждого типа блока, который я хотел бы использовать, и я взламываю компилятор, который тоже не может быть хорошим.
Итак, есть ли способ решить это чисто быстрым способом?
Комментарии:
1. Строка
animation.setValue(completion, forKey: "completionClosure")
не компилируется в моем проекте Xcode 6 beta 2.2. @MartinR: Спасибо, все еще использовал бета-версию 1. Обновили вопрос… становилось все хуже.
Ответ №1:
Как насчет универсального, Block
параметризованного с типом функции?
class Block<T> {
let f : T
init (_ f: T) { self.f = f }
}
Выделите одно из них; это будет подтип AnyObject
и, таким образом, его можно будет назначить в словари и массивы. Это не кажется слишком обременительным, особенно с синтаксисом завершающего замыкания. Используется:
5> var b1 = Block<() -> ()> { print ("Blocked b1") }
b1: Block<() -> ()> = {
f = ...
}
6> b1.f()
Blocked b1
и еще один пример, в котором выводится Block
тип:
11> var ar = [Block { (x:Int) in print ("Block: (x)") }]
ar: [Block<(Int) -> ()>] = 1 value {
[0] = {
f = ...
}
}
12> ar[0].f(111)
Block: 111
Комментарии:
1. Извините за поздний ответ. Это действительно работает!
let completion = anim.valueForKey("completionClosure") as? Block<()->Void> ; completion?.f()
Однако нет способа перейти к Objective-C, но пока это лучшее решение.2. Это отличное решение. Большое вам спасибо
Ответ №2:
Мне нравится решение Гозонера — обернуть блок в пользовательский класс — но поскольку вы просили фактический «Быстрый способ» для выполнения приведения между блоком и AnyObject, я просто дам ответ на этот вопрос: приведение с помощью unsafeBitCast
. (Я предполагаю, что это более или менее то же самое, что у Брайана Чена reinterpretCast
, которого больше не существует.)
Итак, в моем собственном коде:
typealias MyDownloaderCompletionHandler = @objc_block (NSURL!) -> ()
Примечание: в Swift 2 это было бы:
typealias MyDownloaderCompletionHandler = @convention(block) (NSURL!) -> ()
Вот приведение в одном направлении:
// ... cast from block to AnyObject
let ch : MyDownloaderCompletionHandler = // a completion handler closure
let ch2 : AnyObject = unsafeBitCast(ch, AnyObject.self)
Вот приведение обратно в другом направлении:
// ... cast from AnyObject to block
let ch = // the AnyObject
let ch2 = unsafeBitCast(ch, MyDownloaderCompletionHandler.self)
// and now we can call it
ch2(url)
Комментарии:
1. Это все еще работает? Я получил сообщение об ошибке «неустранимая ошибка: не удается отключить перенаправление между типами разных размеров». Для
ch
var у меня было вот что: ch = {()-> Строка в ответ «работает»}. Сработало ли это у вас?2. @Jai Я не знаю; я не использовал
unsafeBitCast
для этого долгое время. Я принял решение Гозонера об универсальной оболочке. Обратите внимание, что вам все еще может потребоваться ввести ваш тип обработчика завершения с помощью@convention(block)
, чтобы получить управление памятью Objective-C.3. хорошо, теперь это работает. В вашем коде у вас было
let ch = // a completion handler closure
, и я не знал, что туда вставить. Итак, у меня было это -> `let ch = {()-> String в ответ «да, это работает»}` Теперь это завершается ошибкой «неустранимая ошибка: не удается отключить передачу между типами разных размеров». Это сработало только тогда, когда я вставил этоlet ch: MyTypeDefToReturnAString = {()->String in return "yes"}
. Кажется, это работает только тогда, когда вы явно вводите свои пользовательские typedefalias. Жаль, что у вас не было этого в вашем коде выше, чтобы избавить меня от головной боли. В любом случае спасибо. 14. У меня действительно есть пользовательский typealias в моем коде. Это первая строка моего кода. Видишь это?
5. да, я видел это там, но не здесь
let ch = // a completion handler closure
.. простите мой нубизм, но я не знал, какой код писать после// a completion handler closure
Ответ №3:
Вот еще одно решение, позволяющее нам выполнять приведение для обмена значениями с Objective-C. Он основан на идее Гозонера о том, чтобы обернуть функцию в класс; разница в том, что наш класс является подклассом NSObject и, таким образом, может предоставить функции Objective-C управление блочной памятью без каких-либо взломов и может быть непосредственно использован как AnyObject и передан Objective-C:
typealias MyStringExpecter = (String) -> ()
class StringExpecterHolder : NSObject {
var f : MyStringExpecter! = nil
}
Вот как использовать это для переноса функции и передачи туда, где ожидается AnyObject:
func f (s:String) {println(s)}
let holder = StringExpecterHolder()
holder.f = f
let lay = CALayer()
lay.setValue(holder, forKey:"myFunction")
И вот как извлечь функцию позже и вызвать ее:
let holder2 = lay.valueForKey("myFunction") as StringExpecterHolder
holder2.f("testing")
Ответ №4:
Все, что вам нужно сделать, это использовать reinterpretCast
для выполнения принудительного приведения.
(reinterpretCast(completion) as (@objc_block Void -> Void))()
из REPL
1> import Foundation
2> var block : @objc_block Void -> Void = { println("test")}
block: @objc_block Void -> Void =
3> var obj = reinterpretCast(block) as AnyObject // this is how to cast block to AnyObject given it have @objc_block attribute
obj: __NSMallocBlock__ = {}
4> var block2 = reinterpretCast(obj) as (@objc_block Void -> Void)
block2: (@objc_block Void -> Void) =
5> block2()
test
6>
Комментарии:
1. атрибут ‘@objc_block’ удален; вместо него следует использовать ‘@convention(блок)’