У книзі сказано, що "функції та закриття є еталонними типами". Отже, як дізнатись, чи однакові посилання? == і === не працюють.
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-адреси нижче, щоб дізнатися, чому і як це працює:
Як бачите, він здатний перевіряти лише особу (2-й тест дає 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())
), ви створюєте новий об'єкт закриття. Цей об'єкт зберігає вказівник на S.f
(яким можна спільно користуватися) , but also to your instance (
self , the
S () `).
S
. Якщо покажчик закриття рівність було можливо, то ви будете здивовані, виявивши , що s1.f
це не те ж саме , як покажчик s2.f
(бо один є об'єктом закриття , які посилання s1
і f
, а інший об'єкт закриття , які посилання s2
і f
).
Ось одне з можливих рішень (концептуально те саме, що і відповідь "tuncay"). Суть полягає у визначенні класу, який обгортає деяку функціональність (наприклад, Command):
Стрімкий:
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
Java:
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
пункт у загальних виробах, здається, може перевірити рівність типів. То, можливо, ви можете використати це, принаймні для перевірки особи?
Не загальне рішення, але якщо хтось намагається реалізувати шаблон слухача, я в підсумку повертаю "ідентифікатор" функції під час реєстрації, щоб я міг використати його для скасування реєстрації пізніше (це своєрідний спосіб вирішення вихідного питання для випадку "слухачів", як правило, незареєстрація зводиться до перевірки функцій на рівність, що, принаймні, не є "тривіальним", як за іншими відповідями).
Отож приблизно так:
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
повернену функцією "register" і передати її під час скасування реєстрації.
Моє рішення було обернути функції до класу, який розширює 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
)