Литые затворы/блоки
В 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 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)
}
Он работает, но мне нужна новая функция для каждого типа блока, который я хотел бы использовать, и я взламываю компилятор, который тоже не может быть хорошим.
Итак, есть ли способ решить это чистым способом Swift?
Ответы
Ответ 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
Ответ 2
Мне нравится решение GoZoner - оберните блок в пользовательский класс, но, поскольку вы попросили фактический "быстрый способ" выполнить бросок между блоком и AnyObject, я просто дам ответ на этот вопрос: cast с 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)
Ответ 3
Здесь еще одно решение, позволяющее использовать для обмена значениями с Objective-C. Он основан на идее GoZoner об упаковке функции в классе; разница заключается в том, что наш класс является подклассом 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>