В книге говорится, что «функции и замыкания являются ссылочными типами». Итак, как узнать, равны ли ссылки? == и === не работают.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
В книге говорится, что «функции и замыкания являются ссылочными типами». Итак, как узнать, равны ли ссылки? == и === не работают.
func a() { }
let å = a
let b = å === å // Could not find an overload for === that accepts the supplied arguments
å
для ссылки a
действительно интересно. Есть ли здесь конвенция, которую вы изучаете? (Не знаю, нравится мне это или нет; но похоже, что это могло бы быть очень мощным, особенно в чисто функциональном программировании.)
Ответы:
Крис Латтнер написал на форумах разработчиков:
Это функция, которую мы намеренно не хотим поддерживать. Есть множество вещей, которые могут привести к сбою или изменению равенства указателей функций (в смысле системы быстрого типа, который включает несколько видов замыканий) в зависимости от оптимизации. Если бы для функций было определено «===», компилятору не разрешалось бы объединять идентичные тела методов, совместно использовать преобразователи и выполнять определенные оптимизации захвата в замыканиях. Кроме того, равенство такого рода было бы чрезвычайно неожиданным в некоторых контекстах обобщения, где вы можете получить преобразователи реабстракции, которые корректируют фактическую сигнатуру функции в соответствии с ожидаемой типом функции.
https://devforums.apple.com/message/1035180#1035180
Это означает, что вам не следует даже пытаться сравнивать замыкания на предмет равенства, поскольку оптимизация может повлиять на результат.
Я много искал. Кажется, нет способа сравнения указателей на функции. Лучшее решение, которое я получил, - это инкапсулировать функцию или закрытие в хешируемый объект. Подобно:
var handler:Handler = Handler(callback: { (message:String) in
//handler body
}))
@objc_block
Самый простой способ - обозначить тип блока как , и теперь вы можете преобразовать его в AnyObject, сравнимый с ===
. Пример:
typealias Ftype = @objc_block (s:String) -> ()
let f : Ftype = {
ss in
println(ss)
}
let ff : Ftype = {
sss in
println(sss)
}
let obj1 = unsafeBitCast(f, AnyObject.self)
let obj2 = unsafeBitCast(ff, AnyObject.self)
let obj3 = unsafeBitCast(f, AnyObject.self)
println(obj1 === obj2) // false
println(obj1 === obj3) // true
Я тоже искал ответ. И наконец я его нашел.
Что вам нужно, так это фактический указатель на функцию и его контекст, скрытый в объекте функции.
func peekFunc<A,R>(f:A->R)->(fp:Int, ctx:Int) {
typealias IntInt = (Int, Int)
let (hi, lo) = unsafeBitCast(f, IntInt.self)
let offset = sizeof(Int) == 8 ? 16 : 12
let ptr = UnsafePointer<Int>(lo+offset)
return (ptr.memory, ptr.successor().memory)
}
@infix func === <A,R>(lhs:A->R,rhs:A->R)->Bool {
let (tl, tr) = (peekFunc(lhs), peekFunc(rhs))
return tl.0 == tr.0 && tl.1 == tr.1
}
А вот и демонстрация:
// simple functions
func genericId<T>(t:T)->T { return t }
func incr(i:Int)->Int { return i + 1 }
var f:Int->Int = genericId
var g = f; println("(f === g) == \(f === g)")
f = genericId; println("(f === g) == \(f === g)")
f = g; println("(f === g) == \(f === g)")
// closures
func mkcounter()->()->Int {
var count = 0;
return { count++ }
}
var c0 = mkcounter()
var c1 = mkcounter()
var c2 = c0
println("peekFunc(c0) == \(peekFunc(c0))")
println("peekFunc(c1) == \(peekFunc(c1))")
println("peekFunc(c2) == \(peekFunc(c2))")
println("(c0() == c1()) == \(c0() == c1())") // true : both are called once
println("(c0() == c2()) == \(c0() == c2())") // false: because c0() means c2()
println("(c0 === c1) == \(c0 === c1)")
println("(c0 === c2) == \(c0 === c2)")
См. Приведенные ниже URL-адреса, чтобы узнать, почему и как это работает:
Как видите, он способен проверять только личность (второй тест дает результат false
). Но этого должно быть достаточно.
Это отличный вопрос, и хотя Крис Латтнер намеренно не хочет поддерживать эту функцию, я, как и многие разработчики, также не могу отпустить свои чувства, исходящие от других языков, где это тривиальная задача. unsafeBitCast
Примеров много , большинство из них не показывают полной картины, вот более подробный :
typealias SwfBlock = () -> ()
typealias ObjBlock = @convention(block) () -> ()
func testSwfBlock(a: SwfBlock, _ b: SwfBlock) -> String {
let objA = unsafeBitCast(a as ObjBlock, AnyObject.self)
let objB = unsafeBitCast(b as ObjBlock, AnyObject.self)
return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}
func testObjBlock(a: ObjBlock, _ b: ObjBlock) -> String {
let objA = unsafeBitCast(a, AnyObject.self)
let objB = unsafeBitCast(b, AnyObject.self)
return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}
func testAnyBlock(a: Any?, _ b: Any?) -> String {
if !(a is ObjBlock) || !(b is ObjBlock) {
return "a nor b are ObjBlock, they are not equal"
}
let objA = unsafeBitCast(a as! ObjBlock, AnyObject.self)
let objB = unsafeBitCast(b as! ObjBlock, AnyObject.self)
return "a is ObjBlock: \(a is ObjBlock), b is ObjBlock: \(b is ObjBlock), objA === objB: \(objA === objB)"
}
class Foo
{
lazy var swfBlock: ObjBlock = self.swf
func swf() { print("swf") }
@objc func obj() { print("obj") }
}
let swfBlock: SwfBlock = { print("swf") }
let objBlock: ObjBlock = { print("obj") }
let foo: Foo = Foo()
print(testSwfBlock(swfBlock, swfBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testSwfBlock(objBlock, objBlock)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testObjBlock(swfBlock, swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testObjBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true
print(testAnyBlock(swfBlock, swfBlock)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(objBlock, objBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true
print(testObjBlock(foo.swf, foo.swf)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: false
print(testSwfBlock(foo.obj, foo.obj)) // a is ObjBlock: false, b is ObjBlock: false, objA === objB: false
print(testAnyBlock(foo.swf, foo.swf)) // a nor b are ObjBlock, they are not equal
print(testAnyBlock(foo.swfBlock, foo.swfBlock)) // a is ObjBlock: true, b is ObjBlock: true, objA === objB: true
Интересно то, как swift свободно переводит SwfBlock в ObjBlock, но на самом деле два приведенных блока SwfBlock всегда будут иметь разные значения, а ObjBlocks - нет. Когда мы приводим ObjBlock к SwfBlock, с ними происходит то же самое, они становятся двумя разными значениями. Таким образом, чтобы сохранить ссылку, следует избегать такого преобразования.
Я все еще разбираюсь в этой теме, но я оставил одну вещь, которую я хотел, - это возможность использовать @convention(block)
методы класса / структуры, поэтому я отправил запрос функции, который требует голосования или объяснения, почему это плохая идея. Я также чувствую, что этот подход может быть плохим в целом, если да, может ли кто-нибудь объяснить, почему?
Struct S { func f(_: Int) -> Bool }
, у вас действительно есть функция типа, у S.f
которой есть тип (S) -> (Int) -> Bool
. Этой функцией можно поделиться. Он параметризуется исключительно своими явными параметрами. Когда вы используете его как метод экземпляра (либо путем неявной привязки self
параметра, вызывая метод к объекту, например S().f
, либо путем явной привязки, например S.f(S())
), вы создаете новый объект закрытия. Этот объект хранит указатель на self S () ` S.f
(который может использоваться совместно ). , but also to your instance (
, the
S
. Если бы было возможно равенство указателя закрытия, вы были бы удивлены, обнаружив, что s1.f
это не тот же указатель, что и s2.f
(потому что один является объектом закрытия, который ссылается на s1
и f
, а другой является объектом закрытия, который ссылается на s2
и f
).
Вот одно из возможных решений (концептуально то же, что и ответ tuncay). Дело в том, чтобы определить класс, который объединяет некоторые функции (например, Command):
Swift:
typealias Callback = (Any...)->Void
class Command {
init(_ fn: @escaping Callback) {
self.fn_ = fn
}
var exec : (_ args: Any...)->Void {
get {
return fn_
}
}
var fn_ :Callback
}
let cmd1 = Command { _ in print("hello")}
let cmd2 = cmd1
let cmd3 = Command { (_ args: Any...) in
print(args.count)
}
cmd1.exec()
cmd2.exec()
cmd3.exec(1, 2, "str")
cmd1 === cmd2 // true
cmd1 === cmd3 // false
Ява:
interface Command {
void exec(Object... args);
}
Command cmd1 = new Command() {
public void exec(Object... args) [
// do something
}
}
Command cmd2 = cmd1;
Command cmd3 = new Command() {
public void exec(Object... args) {
// do something else
}
}
cmd1 == cmd2 // true
cmd1 == cmd3 // false
Что ж, прошло 2 дня, и никто не предложил решение, поэтому я заменю свой комментарий на ответ:
Насколько я могу судить, вы не можете проверить равенство или идентичность функций (например, в вашем примере) и метаклассов (например, MyClass.self
):
Но - и это всего лишь идея - я не могу не заметить, что where
предложение в дженериках, похоже, может проверять равенство типов. Так, может быть, вы сможете использовать это, по крайней мере, для проверки личности?
Не общее решение, но если кто-то пытается реализовать шаблон слушателя, я закончил тем, что вернул «id» функции во время регистрации, поэтому я могу использовать его для отмены регистрации позже (что является своего рода обходным путем к исходному вопросу для случая "слушателей", поскольку обычно отмена регистрации сводится к проверке функций на равенство, что, по крайней мере, не является "тривиальным" согласно другим ответам).
Так что примерно так:
class OfflineManager {
var networkChangedListeners = [String:((Bool) -> Void)]()
func registerOnNetworkAvailabilityChangedListener(_ listener: @escaping ((Bool) -> Void)) -> String{
let listenerId = UUID().uuidString;
networkChangedListeners[listenerId] = listener;
return listenerId;
}
func unregisterOnNetworkAvailabilityChangedListener(_ listenerId: String){
networkChangedListeners.removeValue(forKey: listenerId);
}
}
Теперь вам просто нужно сохранить key
возвращаемый функцией "регистр" и передать его при отмене регистрации.
Мое решение заключалось в переносе функций в класс, расширяющий NSObject.
class Function<Type>: NSObject {
let value: (Type) -> Void
init(_ function: @escaping (Type) -> Void) {
value = function
}
}
Я знаю, что отвечаю на этот вопрос с опозданием на шесть лет, но я думаю, что стоит посмотреть на мотивацию, стоящую за вопросом. Спрашивающий прокомментировал:
Однако, не имея возможности удалять замыкания из списка вызовов по ссылке, нам необходимо создать наш собственный класс-оболочку. Это затруднение, и в этом нет необходимости.
Итак, я предполагаю, что спрашивающий хочет вести список обратных вызовов, например:
class CallbackList {
private var callbacks: [() -> ()] = []
func call() {
callbacks.forEach { $0() }
}
func addCallback(_ callback: @escaping () -> ()) {
callbacks.append(callback)
}
func removeCallback(_ callback: @escaping () -> ()) {
callbacks.removeAll(where: { $0 == callback })
}
}
Но мы не можем так писать removeCallback
, потому что==
что не работает для функций. (Также===
.)
Вот другой способ управлять списком обратных вызовов. Верните объект регистрации из addCallback
и используйте объект регистрации для удаления обратного вызова. Здесь, в 2020 году, мы можем использоватьAnyCancellable
качестве регистрации.
Обновленный API выглядит так:
class CallbackList {
private var callbacks: [NSObject: () -> ()] = [:]
func call() {
callbacks.values.forEach { $0() }
}
func addCallback(_ callback: @escaping () -> ()) -> AnyCancellable {
let key = NSObject()
callbacks[key] = callback
return .init { self.callbacks.removeValue(forKey: key) }
}
}
Теперь, когда вы добавляете обратный вызов, вам не нужно держать его, чтобы перейти к нему removeCallback
позже. Нет никакого removeCallback
метода. Вместо этого вы сохраняете AnyCancellable
и вызываете его cancel
метод для удаления обратного вызова. Еще лучше, если вы сохраните AnyCancellable
в свойстве экземпляра, тогда оно автоматически отменится, когда экземпляр будет уничтожен.
MyClass.self
)