Подтвердить что ты не робот

Литые затворы/блоки

В 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?

4b9b3361

Ответ 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>