पुस्तक कहती है कि "फ़ंक्शन और क्लोज़र संदर्भ प्रकार हैं"। तो, अगर संदर्भ समान हैं तो आपको कैसे पता चलेगा? == और === काम नहीं करते।
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
, और अब आप इसे किसी भी ओबजेक्ट के लिए डाल सकते हैं जो इसके साथ तुलनीय है ===
। उदाहरण:
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
दिलचस्प हिस्सा यह है कि स्वतंत्र रूप से स्विफब्लॉक को ओबजब्लॉक में कैसे डाला जाता है, फिर भी वास्तव में दो डाले गए स्फ़ब्लॉक ब्लॉक हमेशा अलग मूल्य होंगे, जबकि ओब्ब्लब्लॉक नहीं होगा। जब हम ओब्जब्लॉक को स्वफ़ब्लॉक में डालते हैं, तो उनके साथ भी यही होता है, वे दो अलग-अलग मूल्य बन जाते हैं। इसलिए, संदर्भ को संरक्षित करने के लिए, इस तरह की कास्टिंग से बचा जाना चाहिए।
मैं अभी भी इस पूरे विषय को समझ रहा हूं, लेकिन एक चीज जो मैंने छोड़ी है वह है @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 (
स्व , the
एस () `)।
S
। यदि क्लोजर पॉइंटर समानता संभव थी, तो आपको यह जानकर आश्चर्य होगा कि s1.f
यह वैसा ही पॉइंटर नहीं है s2.f
(क्योंकि एक क्लोजर ऑब्जेक्ट है जो संदर्भ देता है s1
और f
दूसरा एक क्लोजर ऑब्जेक्ट है जो संदर्भ देता है s2
और f
)।
यहाँ एक संभव समाधान है (वैचारिक रूप से 'टंकाय' उत्तर के समान)। बिंदु एक वर्ग को परिभाषित करने के लिए है जो कुछ कार्यक्षमता को लपेटता है (जैसे कमांड):
स्विफ्ट:
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
जेनरिक में क्लॉज प्रकार की समानता की जांच करने में सक्षम प्रतीत होता है। तो शायद आप इसका लाभ उठा सकते हैं, कम से कम पहचान की जाँच के लिए?
सामान्य समाधान नहीं है, लेकिन अगर कोई श्रोता पैटर्न को लागू करने की कोशिश कर रहा है, तो मैंने पंजीकरण के दौरान फ़ंक्शन की "आईडी" वापस कर दी है, इसलिए मैं इसे बाद में अपंजीकृत करने के लिए उपयोग कर सकता हूं (जो मूल प्रश्न का एक प्रकार है) "श्रोताओं" के मामले के रूप में आमतौर पर अपंजीकृत समानता के कार्यों की जांच करने के लिए नीचे आता है, जो कि कम से कम अन्य उत्तरों के अनुसार "तुच्छ" नहीं है।
तो कुछ इस तरह:
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
पंजीकरण के रूप में उपयोग कर सकते हैं ।
संशोधित एपीआई इस तरह दिखता है:
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