В книге говорится, что «функции и замыкания являются ссылочными типами». Итак, как узнать, равны ли ссылки? == и === не работают.
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)