{"id":38796,"date":"2023-03-17T14:03:51","date_gmt":"2023-03-17T18:03:51","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=38796"},"modified":"2023-08-09T15:48:16","modified_gmt":"2023-08-09T19:48:16","slug":"equality-in-swift-nsobject-subclasses-and-existentials","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2023\/03\/17\/equality-in-swift-nsobject-subclasses-and-existentials\/","title":{"rendered":"Equality in Swift: NSObject, Subclasses, and Existentials"},"content":{"rendered":"<p><a href=\"https:\/\/medium.com\/@thenewt15\/equatable-pitfalls-in-ios-d250534bd7cc\">Mark Newton<\/a>:<\/p>\n<blockquote cite=\"https:\/\/medium.com\/@thenewt15\/equatable-pitfalls-in-ios-d250534bd7cc\"><p>Conformance to the <code>Equatable<\/code> protocol seems pretty straightforward. You simply override the <code>==<\/code> function.<\/p><p>[&#8230;]<\/p><p>This works great for objects like structs, or classes with no superclass. However, we can run into problems with the <code>==<\/code> function if we&rsquo;re dealing with <code>NSObject<\/code> subclasses.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/www.jayeshkawli.com\/using-equatable\/\">Jayesh Kawli<\/a> (via <a href=\"https:\/\/twitter.com\/krzyzanowskim\/status\/1345151380428902400\">Marcin Krzyzanowski<\/a>):<\/p>\n<blockquote cite=\"https:\/\/www.jayeshkawli.com\/using-equatable\/\"><p>And now if you try to do following comparison, they will either won&rsquo;t work, will be buggy or fail to compile<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/noahgilmore.com\/blog\/nsobject-equatable\/\">Noah Gilmore<\/a>:<\/p>\n<blockquote cite=\"https:\/\/noahgilmore.com\/blog\/nsobject-equatable\/\">\n<p>Swift can be tricky sometimes. For example, what does the following print?<\/p>\n<pre>class A: NSObject {\n  let x: Int\n\n  init(x: Int) {\n    self.x = x\n  }\n}\n\nfunc ==(left: A, right: A) -&gt; Bool {\n  return left.x == right.x\n}\n\nprint(A(x: 1) == A(x: 1))\nprint([A(x: 1)] == [A(x: 1)])\n<\/pre>\n<p>[&#8230;]<\/p>\n<p>The best way to make an <code>NSObject<\/code> subclass use custom equality inside an array is to override <code>isEqual:<\/code>[&#8230;]<\/p>\n<\/blockquote>\n<p>His reasoning for why the custom <code>==<\/code> didn&rsquo;t work as expected is <a href=\"https:\/\/twitter.com\/mjtsai\/status\/1255690602416496640\">wrong<\/a>, but the <a href=\"https:\/\/stackoverflow.com\/questions\/34835359\/compare-two-nsobject-in-swift\">solution<\/a> is correct. Similarly, you should <a href=\"https:\/\/stackoverflow.com\/questions\/33319959\/nsobject-subclass-in-swift-hash-vs-hashvalue-isequal-vs\">override <code>hashValue<\/code><\/a> (not <code>hash(into:)<\/code>) if you need to change how it is <code>Hashable<\/code>.<\/p>\n\n<p>For non-<code>NSObject<\/code> classes, a similar issue applies. If you have something like:<\/p>\n<pre>class Base: Equatable {\n    static func == (lhs: Base, rhs: Base) -&gt; Bool {\n        return lhs === rhs\n    }\n}\n\nclass A: Base {\n    let x: Int\n\n    init(x: Int) {\n      self.x = x\n    }\n}\n\nfunc ==(left: A, right: A) -&gt; Bool {\n    return left.x == right.x\n}<\/pre>\n<p>The results may not be what you expect:<\/p>\n<pre>A(x: 1) == A(x: 1) \/\/ true\n[A(x: 1)] == [A(x: 1)] \/\/ false<\/pre>\n\n<p><code>Array<\/code> uses the <code>==<\/code> from where its elements conformed to <code>Equatable<\/code>.<\/p>\n\n\n<p><a href=\"https:\/\/nilcoalescing.com\/blog\/CheckIfTwoValuesOfTypeAnyAreEqual\/\">Natalia Panferova<\/a> (<a href=\"https:\/\/twitter.com\/natpanferova\/status\/1536264063922995200\">tweet<\/a>):<\/p>\n<blockquote cite=\"https:\/\/nilcoalescing.com\/blog\/CheckIfTwoValuesOfTypeAnyAreEqual\/\"><p>In Swift 5.7 that comes with Xcode 14 we can more easily check if two values of type <code>Any<\/code> are equal, because we can cast values to <code>any Equatable<\/code> and also use <code>any Equatable<\/code> as a parameter type thanks to <a href=\"https:\/\/github.com\/apple\/swift-evolution\/blob\/main\/proposals\/0309-unlock-existential-types-for-all-protocols.md\">Unlock existentials for all protocols<\/a> change.<\/p><p>[&#8230;]<\/p><p>Inside <code>isEqual()<\/code> method we have access to <code>Self<\/code>, so we can try to cast the <code>other<\/code> value to the same type as the concrete type of the value this method is called on. If the cast fails, the two values cannot be equal, so we return <code>false<\/code>. If it succeeds, then we can check the two values for equality, using <code>==<\/code> function on values of the same concrete type.<\/p><\/blockquote>\n<p>There are some edge cases to be aware of, however, so it is preferred to <a href=\"https:\/\/forums.swift.org\/t\/why-cant-existential-types-be-compared\/59118\/18\">use <code>AnyHashable<\/code><\/a>.<\/p>\n\n<p><a href=\"https:\/\/twitter.com\/AirspeedSwift\/status\/1536354980600217602\">Ben Cohen<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/AirspeedSwift\/status\/1536354980600217602\"><p>You can define something similar for <code>Comparable<\/code>[&#8230;]<\/p><\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/09\/12\/swift-5-7\/\">Swift 5.7<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2019\/05\/17\/dynamic-equality-checking-and-equatable\/\">Dynamic Equality Checking and Equatable<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2017\/03\/31\/some-swift-types-are-more-equatable-than-others\/\">Some Swift Types Are More Equatable Than Others<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2015\/09\/01\/swift-protocols\/\">Swift Protocols<\/a><\/li>\n<\/ul>\n\n<p id=\"equality-in-swift-nsobject-subclasses-and-existentials-update-2023-08-09\">Update (2023-08-09): See also: <a href=\"https:\/\/mastodon.social\/@helge\/110821913593514165\">Helge He&szlig;<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Mark Newton: Conformance to the Equatable protocol seems pretty straightforward. You simply override the == function.[&#8230;]This works great for objects like structs, or classes with no superclass. However, we can run into problems with the == function if we&rsquo;re dealing with NSObject subclasses. Jayesh Kawli (via Marcin Krzyzanowski): And now if you try to do [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2023-03-17T18:03:54Z","apple_news_api_id":"5bcf1454-6242-4b22-af21-f5c7f2561ed5","apple_news_api_modified_at":"2023-08-09T19:48:18Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAA==","apple_news_api_share_url":"https:\/\/apple.news\/AW88UVGJCSyKvIfXH8lYe1Q","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2185,46,30,2223,71,901],"class_list":["post-38796","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-16","tag-languagedesign","tag-mac","tag-macos-13-ventura","tag-programming","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/38796","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=38796"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/38796\/revisions"}],"predecessor-version":[{"id":40290,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/38796\/revisions\/40290"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=38796"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=38796"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=38796"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}