From bc82eae4afa25d485bb5c4a1e364711be73ef023 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Nov 2024 09:26:05 -0800 Subject: [PATCH] wip --- .../Contents.swift | 478 ++++++++++++++ .../contents.xcplayground | 2 + .../project.pbxproj | 389 +++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/BackToBasicsEquatable.xcscheme | 84 +++ .../BackToBasicsEquatable/main.swift | 11 + .../BackToBasicsEquatable.xctestplan | 29 + .../BackToBasicsEquatableTests.swift | 64 ++ .../SwiftDataTests.swift | 41 ++ 0299-back-to-basics-equatable-pt3/README.md | 5 + .../Contents.swift | 602 ++++++++++++++++++ .../contents.xcplayground | 2 + .../project.pbxproj | 387 +++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/BackToBasicsEquatable.xcscheme | 84 +++ .../BackToBasicsEquatable/main.swift | 11 + .../BackToBasicsEquatable.xctestplan | 29 + .../BackToBasicsEquatableTests.swift | 64 ++ .../SwiftDataTests.swift | 41 ++ 0300-back-to-basics-equatable-pt4/README.md | 5 + 0301-sqlite-pt1/README.md | 5 + .../project.pbxproj | 445 +++++++++++++ .../contents.xcworkspacedata | 7 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 + .../Assets.xcassets/Contents.json | 6 + .../SQLiteExplorations/ContentView.swift | 17 + .../SQLiteExplorationsApp.swift | 110 ++++ .../SQLiteExplorationsTests.swift | 17 + README.md | 3 + 30 files changed, 2998 insertions(+) create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable/main.swift create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift create mode 100644 0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift create mode 100644 0299-back-to-basics-equatable-pt3/README.md create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable/main.swift create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift create mode 100644 0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift create mode 100644 0300-back-to-basics-equatable-pt4/README.md create mode 100644 0301-sqlite-pt1/README.md create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.pbxproj create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/Contents.json create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/ContentView.swift create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/SQLiteExplorationsApp.swift create mode 100644 0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorationsTests/SQLiteExplorationsTests.swift diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift new file mode 100644 index 00000000..42cb03e2 --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift @@ -0,0 +1,478 @@ +//let x: any Equatable + +struct User: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.id == rhs.id +// && lhs.isAdmin == rhs.isAdmin +// && lhs.name == rhs.name +// lhs.id == rhs.id +// lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(isAdmin) + hasher.combine(name) + } +} + +struct SlowDictionary { + let keyValues: [(Key, Value)] + + subscript(key: Key) -> Value? where Key: Equatable { + for (k, v) in keyValues { + if key == k { + return v + } + } + return nil + } +} + +do { + let blob = User(id: 42, isAdmin: false, name: "Blob") + var friends: [User: [User]] = [:] + + friends[blob] = [ + User(id: 43, name: "Blob Jr."), + User(id: 44, name: "Blob Sr."), + ] + + friends[blob] + blob.hashValue + let blobJr = User(id: 43, name: "Blob Jr.") + friends[blobJr] + + blob.hashValue + blobJr.hashValue + + let users = Set([ + blob, + blob, + blob, + blob, + blobJr, + blobJr, + blobJr, + ]) + users.contains(blob) + users.contains(User(id: 44, name: "Blob Sr.")) + + var blobDraft = blob + blobDraft.isAdmin.toggle() + blob == blobDraft + blob.hashValue == blobDraft.hashValue + friends[blob] + friends[blobDraft] + +// friends[blobDraft] = [] + + var friendsSlow = SlowDictionary( + keyValues: [ + (blob, [ + User(id: 43, name: "Blob Jr."), + User(id: 44, name: "Blob Sr."), + ]) + ] + ) + + friendsSlow[blob] + friendsSlow[blobDraft] +} + +let id42Partition = [ + User(id: 42, isAdmin: true, name: "Blob"), + User(id: 42, isAdmin: false, name: "Blob, Esq."), + User(id: 42, isAdmin: true, name: "Blob, Esq."), + User(id: 42, isAdmin: true, name: "Blob, MD"), + User(id: 42, isAdmin: true, name: "Blob, MD"), + // ... +] + +id42Partition.allSatisfy { lhs in + id42Partition.allSatisfy { rhs in + lhs == rhs + } +} + +struct Mod12: Equatable { + var value: Int + init(_ value: Int) { + self.value = value + } + + static func == (lhs: Self, rhs: Self) -> Bool { + (rhs.value - lhs.value).isMultiple(of: 12) + } + + func add(to other: Self) -> Self { + Self(value + other.value) + } + var isBig: Bool { + value >= 100 + } +} +func algorithm(lhs: Mod12, rhs: Mod12) { + let bothAreBig = lhs == rhs + ? lhs.isBig + : lhs.isBig && rhs.isBig +} + +do { + let three = Mod12(3) + let fifteen = Mod12(15) + Mod12(2).add(to: three) == Mod12(2).add(to: fifteen) + + Mod12(2) == Mod12(50) + Mod12(2).isBig == Mod12(50).isBig + Mod12(2) == Mod12(110) + Mod12(2).isBig == Mod12(110).isBig + Mod12(2).isBig + Mod12(110).isBig +} + +Mod12(1) == Mod12(6) +Mod12(1) == Mod12(13) +Mod12(1) == Mod12(25) + +struct NeverEqual: Equatable { + var value: A + init(_ value: A) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { false } +} + +do { + let neverEqual1 = NeverEqual(1) + neverEqual1 == neverEqual1 + + let values = [NeverEqual(true), NeverEqual(false)] + values.count + values.contains(NeverEqual(true)) + values.contains(NeverEqual(false)) +} + +struct LessThanOrEqual: Equatable { + var value: Int + init(_ value: Int) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.value <= rhs.value + } +} + +LessThanOrEqual(2) == LessThanOrEqual(3) +LessThanOrEqual(3) == LessThanOrEqual(2) + +extension Array where Element: Equatable { + func firstOffset(of needle: Element) -> Int? { + for (offset, element) in enumerated() { + if needle == element { +// if element == needle { + return offset + } + } + return nil + } +} + +[ + LessThanOrEqual(2), + LessThanOrEqual(3), +] + .firstOffset(of: LessThanOrEqual(3)) + +struct Approximation: Equatable { + var value: Double + init(_ value: Double) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { + abs(lhs.value - rhs.value) < 0.1 + } +} + +Approximation(1) == Approximation(1.05) +Approximation(1.05) == Approximation(1.1) +Approximation(1) == Approximation(1.1) + +extension Array where Element: Equatable { + func uniques() -> Array { + var result = Array() + for element in self { + if !result.contains(element) { + result.append(element) + } + } + return result + } +} + +[ + Approximation(1), + Approximation(1.05), + Approximation(1.025), + Approximation(1.075), + Approximation(1.1), + Approximation(1.15), +] + .uniques() + .map(\.value) + +func f(_ a: A) -> Bool { + // + fatalError() +} +do { +// let x: Int +// let y: Int +// if x == y { +// f(x) == f(y) +// } +} +func algorithm(lhs: A, rhs: A) -> Bool { + if lhs == rhs { + // fast path using 'lhs' + } else { + // slow path using both 'lhs' and 'rhs' + } + fatalError() +} + +Double.nan + +Double.zero / .zero + +import Foundation + +sqrt(-1) +Double.infinity * .zero + +let nanValue = Double.nan +nanValue == nanValue + +let veryLargeNumber: Double = 1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000 +veryLargeNumber == veryLargeNumber + 1 + +Double.zero +-Double.zero + +Double.zero == -.zero + +func isSpecialNumber(_ value: Double) -> Bool { + 1 / value == .infinity +} + +isSpecialNumber(.zero) +isSpecialNumber(-.zero) + +do { + let name = "Blob" + + let cafe = "Café" + + name == name + cafe == name + name == cafe + + let cafe1 = "Caf\u{e9}" + let cafe2 = "Cafe\u{301}" + cafe1 == cafe2 + + struct Flag: Equatable { + let id: UUID + var isEnabled = false + } + + let dejavu = "déjà-vu" + let dejavu1 = "d\u{e9}j\u{e0}-vu" + let dejavu2 = "de\u{301}j\u{e0}-vu" + let dejavu3 = "d\u{e9}ja\u{300}-vu" + let dejavu4 = "de\u{301}ja\u{300}-vu" + + dejavu1 == dejavu2 + dejavu2 == dejavu3 + dejavu3 == dejavu4 + dejavu4 == dejavu1 + + dejavu1.hashValue + dejavu2.hashValue + dejavu3.hashValue + dejavu4.hashValue + + Array(cafe1.unicodeScalars).description + Array(cafe2.unicodeScalars).description + cafe1.unicodeScalars.elementsEqual(cafe2.unicodeScalars) + + Array(cafe1.utf8).description + Array(cafe2.utf8).description + cafe1.utf8.elementsEqual(cafe2.utf8) + + func specialAlgorithm(_ str: String) -> Bool { + if str.utf8.count <= 5 { + // Fast path + return true + } else { + // Slow path + return false + } + } + + specialAlgorithm(cafe1) + specialAlgorithm(cafe2) + cafe1 == cafe2 +} + +do { +// let x: Hashable +// let y: Hashable +// if x == y { +// x.hashValue == y.hashValue +// } +// if x.hashValue == y.hashValue { +// x == y +// } +} + +final class UserRef: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + init(id: Int, isAdmin: Bool = false, name: String) { + self.id = id + self.isAdmin = isAdmin + self.name = name + } + + static func == (lhs: UserRef, rhs: UserRef) -> Bool { + lhs.id == rhs.id + && lhs.isAdmin == rhs.isAdmin + && lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(isAdmin) + hasher.combine(name) + } +} + +do { + let blob = User(id: 42, name: "Blob") + let blob2 = User(id: 42, name: "Blob") + + blob == blob2 + + var blobJr = User(id: 43, name: "Blob Jr.") + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + users.count + + blobJr.name = "Blob II" + + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) + users.count + + users.insert(blobJr) + users.contains(blobJr) + users.count + +} + +do { + let blob = UserRef(id: 42, name: "Blob") + let blob2 = UserRef(id: 42, name: "Blob") + + blob === blob2 + + let blobJr = UserRef(id: 43, name: "Blob Jr.") + + ObjectIdentifier(blob) + ObjectIdentifier(blobJr) + + blob === blob + blob === blobJr + blobJr === blob + + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + users.count + + blobJr.name = "Blob II" + + ObjectIdentifier(blobJr) + + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) + users.count + + users.insert(blobJr) + users.count + + Set(Array(users)) + +} + +final class UserRefCorrect: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + init(id: Int, isAdmin: Bool = false, name: String) { + self.id = id + self.isAdmin = isAdmin + self.name = name + } + + static func == (lhs: UserRefCorrect, rhs: UserRefCorrect) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} + +do { + let blob = UserRefCorrect(id: 42, name: "Blob") + let blobJr = UserRefCorrect(id: 43, name: "Blob Jr.") + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + + blobJr.name = "Blob II" + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) +} + +import UIKit + +UIViewController() == UIViewController() diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground new file mode 100644 index 00000000..7f43ce8c --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj new file mode 100644 index 00000000..bee0622e --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj @@ -0,0 +1,389 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXCopyFilesBuildPhase section */ + 4B7FD74B2CAB1B8100205C7D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BackToBasicsEquatableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A2B2E952CAC6C10001A982B /* BackToBasicsEquatable.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = BackToBasicsEquatable.xctestplan; path = BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan; sourceTree = ""; }; + 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BackToBasicsEquatable; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B7FD7572CAB1B9000205C7D /* BackToBasicsEquatable.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = BackToBasicsEquatable.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BackToBasicsEquatableTests; + sourceTree = ""; + }; + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BackToBasicsEquatable; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A2B2E8B2CAC6AA1001A982B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B7FD74A2CAB1B8100205C7D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4B7FD7442CAB1B8100205C7D = { + isa = PBXGroup; + children = ( + 2A2B2E952CAC6C10001A982B /* BackToBasicsEquatable.xctestplan */, + 4B7FD7572CAB1B9000205C7D /* BackToBasicsEquatable.playground */, + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + 4B7FD74E2CAB1B8100205C7D /* Products */, + ); + sourceTree = ""; + }; + 4B7FD74E2CAB1B8100205C7D /* Products */ = { + isa = PBXGroup; + children = ( + 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2A2B2E8D2CAC6AA1001A982B /* BackToBasicsEquatableTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A2B2E942CAC6AA1001A982B /* Build configuration list for PBXNativeTarget "BackToBasicsEquatableTests" */; + buildPhases = ( + 2A2B2E8A2CAC6AA1001A982B /* Sources */, + 2A2B2E8B2CAC6AA1001A982B /* Frameworks */, + 2A2B2E8C2CAC6AA1001A982B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + ); + name = BackToBasicsEquatableTests; + packageProductDependencies = ( + ); + productName = BackToBasicsEquatableTests; + productReference = 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 4B7FD74C2CAB1B8100205C7D /* BackToBasicsEquatable */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B7FD7542CAB1B8100205C7D /* Build configuration list for PBXNativeTarget "BackToBasicsEquatable" */; + buildPhases = ( + 4B7FD7492CAB1B8100205C7D /* Sources */, + 4B7FD74A2CAB1B8100205C7D /* Frameworks */, + 4B7FD74B2CAB1B8100205C7D /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */, + ); + name = BackToBasicsEquatable; + packageProductDependencies = ( + ); + productName = BackToBasicsEquatable; + productReference = 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4B7FD7452CAB1B8100205C7D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + TargetAttributes = { + 2A2B2E8D2CAC6AA1001A982B = { + CreatedOnToolsVersion = 16.0; + }; + 4B7FD74C2CAB1B8100205C7D = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 4B7FD7482CAB1B8100205C7D /* Build configuration list for PBXProject "BackToBasicsEquatable" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4B7FD7442CAB1B8100205C7D; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 4B7FD74E2CAB1B8100205C7D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4B7FD74C2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8D2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A2B2E8C2CAC6AA1001A982B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2A2B2E8A2CAC6AA1001A982B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B7FD7492CAB1B8100205C7D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2A2B2E922CAC6AA1001A982B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.BackToBasicsEquatableTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 2A2B2E932CAC6AA1001A982B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.BackToBasicsEquatableTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 4B7FD7522CAB1B8100205C7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4B7FD7532CAB1B8100205C7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 4B7FD7552CAB1B8100205C7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 4B7FD7562CAB1B8100205C7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A2B2E942CAC6AA1001A982B /* Build configuration list for PBXNativeTarget "BackToBasicsEquatableTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2B2E922CAC6AA1001A982B /* Debug */, + 2A2B2E932CAC6AA1001A982B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B7FD7482CAB1B8100205C7D /* Build configuration list for PBXProject "BackToBasicsEquatable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B7FD7522CAB1B8100205C7D /* Debug */, + 4B7FD7532CAB1B8100205C7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B7FD7542CAB1B8100205C7D /* Build configuration list for PBXNativeTarget "BackToBasicsEquatable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B7FD7552CAB1B8100205C7D /* Debug */, + 4B7FD7562CAB1B8100205C7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4B7FD7452CAB1B8100205C7D /* Project object */; +} diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme new file mode 100644 index 00000000..0360b52d --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable/main.swift b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable/main.swift new file mode 100644 index 00000000..c3998546 --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatable/main.swift @@ -0,0 +1,11 @@ +// +// main.swift +// BackToBasicsEquatable +// +// Created by Point-Free on 9/30/24. +// + +import Foundation + +print("Hello, World!") + diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan new file mode 100644 index 00000000..ba9db17c --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan @@ -0,0 +1,29 @@ +{ + "configurations" : [ + { + "id" : "E46DF23E-B3AC-45B3-BD7C-8E13816520AA", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:BackToBasicsEquatable.xcodeproj", + "identifier" : "4B7FD74C2CAB1B8100205C7D", + "name" : "BackToBasicsEquatable" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:BackToBasicsEquatable.xcodeproj", + "identifier" : "2A2B2E8D2CAC6AA1001A982B", + "name" : "BackToBasicsEquatableTests" + } + } + ], + "version" : 1 +} diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift new file mode 100644 index 00000000..602283cd --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift @@ -0,0 +1,64 @@ +import Testing + +struct BackToBasicsEquatableTests { + @Test + func notifier() async throws { + var timerTickCount = 0 + let cancellable = NotificationCenter.default + .publisher(for: Notification.Name("TimerNotifier-main")) + .sink { _ in timerTickCount += 1 } + + var notifiers = Set([TimerNotifier(name: "main")]) + + do { + let notifier = TimerNotifier(name: "main") + notifier.startTimer() + #expect(notifiers.insert(notifier).inserted) + } + + try await Task.sleep(for: .seconds(1.1)) + #expect(timerTickCount == 1) + + _ = cancellable + } +} + +import Foundation + +final class TimerNotifier: Hashable { + static func == (lhs: TimerNotifier, rhs: TimerNotifier) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + + let name: String + var task: Task? + + init(name: String) { + self.name = name + } + + deinit { + task?.cancel() + } + + func startTimer() { + task = Task { [name] in + while true { + try await Task.sleep(for: .seconds(1)) + NotificationCenter.default.post( + name: Notification.Name("TimerNotifier-\(name)"), + object: nil + ) + } + } + } + + func stopTimer() { + task?.cancel() + task = nil + } +} diff --git a/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift new file mode 100644 index 00000000..0914225c --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift @@ -0,0 +1,41 @@ +import SwiftData +import Testing + +@Model +class Item { + var name: String + init(name: String) { + self.name = name + } +} + +@Test +func model() async throws { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) + let modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) + let context = ModelContext(modelContainer) + + let item = Item(name: "Blob") + context.insert(item) + try context.save() + + let fetchedItem = try context.fetch(FetchDescriptor()).first! + + #expect(fetchedItem == item) + #expect(fetchedItem === item) + + do { + let context = ModelContext(item.modelContext!.container) + + let fetchedItem = try context.fetch(FetchDescriptor()).first! + + #expect(fetchedItem.name == item.name) + #expect(fetchedItem.id == item.id) + + #expect(fetchedItem == item) + #expect(fetchedItem === item) + } +} diff --git a/0299-back-to-basics-equatable-pt3/README.md b/0299-back-to-basics-equatable-pt3/README.md new file mode 100644 index 00000000..c7b92e18 --- /dev/null +++ b/0299-back-to-basics-equatable-pt3/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Back to Basics: Hashable References](https://www.pointfree.co/episodes/ep299-back-to-basics-hashable-references) +> +> We’ve studied `Equatable` and `Hashable`, their laws, and saw how value types as simple bags of data easily conform via “structural” equality. What about reference types? Reference types are an amalgamation of data and behavior, and that data can be mutated in place at any time, so how can they reasonably conform to these protocols? diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift new file mode 100644 index 00000000..01bc7ee3 --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/Contents.swift @@ -0,0 +1,602 @@ +//let x: any Equatable + +struct User: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.id == rhs.id +// && lhs.isAdmin == rhs.isAdmin +// && lhs.name == rhs.name +// lhs.id == rhs.id +// lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(isAdmin) + hasher.combine(name) + } +} + +struct SlowDictionary { + let keyValues: [(Key, Value)] + + subscript(key: Key) -> Value? where Key: Equatable { + for (k, v) in keyValues { + if key == k { + return v + } + } + return nil + } +} + +do { + let blob = User(id: 42, isAdmin: false, name: "Blob") + var friends: [User: [User]] = [:] + + friends[blob] = [ + User(id: 43, name: "Blob Jr."), + User(id: 44, name: "Blob Sr."), + ] + + friends[blob] + blob.hashValue + let blobJr = User(id: 43, name: "Blob Jr.") + friends[blobJr] + + blob.hashValue + blobJr.hashValue + + let users = Set([ + blob, + blob, + blob, + blob, + blobJr, + blobJr, + blobJr, + ]) + users.contains(blob) + users.contains(User(id: 44, name: "Blob Sr.")) + + var blobDraft = blob + blobDraft.isAdmin.toggle() + blob == blobDraft + blob.hashValue == blobDraft.hashValue + friends[blob] + friends[blobDraft] + +// friends[blobDraft] = [] + + var friendsSlow = SlowDictionary( + keyValues: [ + (blob, [ + User(id: 43, name: "Blob Jr."), + User(id: 44, name: "Blob Sr."), + ]) + ] + ) + + friendsSlow[blob] + friendsSlow[blobDraft] +} + +let id42Partition = [ + User(id: 42, isAdmin: true, name: "Blob"), + User(id: 42, isAdmin: false, name: "Blob, Esq."), + User(id: 42, isAdmin: true, name: "Blob, Esq."), + User(id: 42, isAdmin: true, name: "Blob, MD"), + User(id: 42, isAdmin: true, name: "Blob, MD"), + // ... +] + +id42Partition.allSatisfy { lhs in + id42Partition.allSatisfy { rhs in + lhs == rhs + } +} + +struct Mod12: Equatable { + var value: Int + init(_ value: Int) { + self.value = value + } + + static func == (lhs: Self, rhs: Self) -> Bool { + (rhs.value - lhs.value).isMultiple(of: 12) + } + + func add(to other: Self) -> Self { + Self(value + other.value) + } + var isBig: Bool { + value >= 100 + } +} +func algorithm(lhs: Mod12, rhs: Mod12) { + let bothAreBig = lhs == rhs + ? lhs.isBig + : lhs.isBig && rhs.isBig +} + +do { + let three = Mod12(3) + let fifteen = Mod12(15) + Mod12(2).add(to: three) == Mod12(2).add(to: fifteen) + + Mod12(2) == Mod12(50) + Mod12(2).isBig == Mod12(50).isBig + Mod12(2) == Mod12(110) + Mod12(2).isBig == Mod12(110).isBig + Mod12(2).isBig + Mod12(110).isBig +} + +Mod12(1) == Mod12(6) +Mod12(1) == Mod12(13) +Mod12(1) == Mod12(25) + +struct NeverEqual: Equatable { + var value: A + init(_ value: A) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { false } +} + +do { + let neverEqual1 = NeverEqual(1) + neverEqual1 == neverEqual1 + + let values = [NeverEqual(true), NeverEqual(false)] + values.count + values.contains(NeverEqual(true)) + values.contains(NeverEqual(false)) +} + +struct LessThanOrEqual: Equatable { + var value: Int + init(_ value: Int) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.value <= rhs.value + } +} + +LessThanOrEqual(2) == LessThanOrEqual(3) +LessThanOrEqual(3) == LessThanOrEqual(2) + +extension Array where Element: Equatable { + func firstOffset(of needle: Element) -> Int? { + for (offset, element) in enumerated() { + if needle == element { +// if element == needle { + return offset + } + } + return nil + } +} + +[ + LessThanOrEqual(2), + LessThanOrEqual(3), +] + .firstOffset(of: LessThanOrEqual(3)) + +struct Approximation: Equatable { + var value: Double + init(_ value: Double) { + self.value = value + } + static func == (lhs: Self, rhs: Self) -> Bool { + abs(lhs.value - rhs.value) < 0.1 + } +} + +Approximation(1) == Approximation(1.05) +Approximation(1.05) == Approximation(1.1) +Approximation(1) == Approximation(1.1) + +extension Array where Element: Equatable { + func uniques() -> Array { + var result = Array() + for element in self { + if !result.contains(element) { + result.append(element) + } + } + return result + } +} + +[ + Approximation(1), + Approximation(1.05), + Approximation(1.025), + Approximation(1.075), + Approximation(1.1), + Approximation(1.15), +] + .uniques() + .map(\.value) + +func f(_ a: A) -> Bool { + // + fatalError() +} +do { +// let x: Int +// let y: Int +// if x == y { +// f(x) == f(y) +// } +} +func algorithm(lhs: A, rhs: A) -> Bool { + if lhs == rhs { + // fast path using 'lhs' + } else { + // slow path using both 'lhs' and 'rhs' + } + fatalError() +} + +Double.nan + +Double.zero / .zero + +import Foundation + +sqrt(-1) +Double.infinity * .zero + +let nanValue = Double.nan +nanValue == nanValue + +let veryLargeNumber: Double = 1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000 +veryLargeNumber == veryLargeNumber + 1 + +Double.zero +-Double.zero + +Double.zero == -.zero + +func isSpecialNumber(_ value: Double) -> Bool { + 1 / value == .infinity +} + +isSpecialNumber(.zero) +isSpecialNumber(-.zero) + +do { + let name = "Blob" + + let cafe = "Café" + + name == name + cafe == name + name == cafe + + let cafe1 = "Caf\u{e9}" + let cafe2 = "Cafe\u{301}" + cafe1 == cafe2 + + struct Flag: Equatable { + let id: UUID + var isEnabled = false + } + + let dejavu = "déjà-vu" + let dejavu1 = "d\u{e9}j\u{e0}-vu" + let dejavu2 = "de\u{301}j\u{e0}-vu" + let dejavu3 = "d\u{e9}ja\u{300}-vu" + let dejavu4 = "de\u{301}ja\u{300}-vu" + + dejavu1 == dejavu2 + dejavu2 == dejavu3 + dejavu3 == dejavu4 + dejavu4 == dejavu1 + + dejavu1.hashValue + dejavu2.hashValue + dejavu3.hashValue + dejavu4.hashValue + + Array(cafe1.unicodeScalars).description + Array(cafe2.unicodeScalars).description + cafe1.unicodeScalars.elementsEqual(cafe2.unicodeScalars) + + Array(cafe1.utf8).description + Array(cafe2.utf8).description + cafe1.utf8.elementsEqual(cafe2.utf8) + + func specialAlgorithm(_ str: String) -> Bool { + if str.utf8.count <= 5 { + // Fast path + return true + } else { + // Slow path + return false + } + } + + specialAlgorithm(cafe1) + specialAlgorithm(cafe2) + cafe1 == cafe2 +} + +do { +// let x: Hashable +// let y: Hashable +// if x == y { +// x.hashValue == y.hashValue +// } +// if x.hashValue == y.hashValue { +// x == y +// } +} + +final class UserRef: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + init(id: Int, isAdmin: Bool = false, name: String) { + self.id = id + self.isAdmin = isAdmin + self.name = name + } + + static func == (lhs: UserRef, rhs: UserRef) -> Bool { + lhs.id == rhs.id + && lhs.isAdmin == rhs.isAdmin + && lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(isAdmin) + hasher.combine(name) + } +} + +do { + let blob = User(id: 42, name: "Blob") + let blob2 = User(id: 42, name: "Blob") + + blob == blob2 + + var blobJr = User(id: 43, name: "Blob Jr.") + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + users.count + + blobJr.name = "Blob II" + + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) + users.count + + users.insert(blobJr) + users.contains(blobJr) + users.count + +} + +do { + let blob = UserRef(id: 42, name: "Blob") + let blob2 = UserRef(id: 42, name: "Blob") + + blob === blob2 + + let blobJr = UserRef(id: 43, name: "Blob Jr.") + + ObjectIdentifier(blob) + ObjectIdentifier(blobJr) + + blob === blob + blob === blobJr + blobJr === blob + + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + users.count + + blobJr.name = "Blob II" + + ObjectIdentifier(blobJr) + + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) + users.count + + users.insert(blobJr) + users.count + + Set(Array(users)) + +} + +final class UserRefCorrect: Equatable, Hashable { + let id: Int + var isAdmin = false + var name: String + + init(id: Int, isAdmin: Bool = false, name: String) { + self.id = id + self.isAdmin = isAdmin + self.name = name + } + + static func == (lhs: UserRefCorrect, rhs: UserRefCorrect) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} + +do { + let blob = UserRefCorrect(id: 42, name: "Blob") + let blobJr = UserRefCorrect(id: 43, name: "Blob Jr.") + var users = Set([ + blob, + blobJr, + ]) + + users.contains(blob) + users.contains(blobJr) + + blobJr.name = "Blob II" + users.map(\.name) + + users.contains(blob) + users.contains(blobJr) +} + +actor Status: Hashable { + var isLoading = false + + static func == (lhs: Status, rhs: Status) -> Bool { + lhs === rhs + } + nonisolated func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} + +let status = Status() +ObjectIdentifier(status) +status === status + +func operation(_ value: some Hashable) { + _ = value.hashValue +} +operation(status) + + +final class UserRef2: Hashable { + let id: Int + let isAdmin: Bool + let name: String + init(id: Int, isAdmin: Bool, name: String) { + self.id = id + self.isAdmin = isAdmin + self.name = name + } + static func == (lhs: UserRef2, rhs: UserRef2) -> Bool { + lhs.id == rhs.id + && lhs.isAdmin == rhs.isAdmin + && lhs.name == rhs.name + } + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(isAdmin) + hasher.combine(name) + } +} + +//do { +// let never: Never +// never == never +//} + +import SwiftUI + +Animation.linear == .linear +Animation.linear == .easeOut + +print(Animation.linear) +print(Animation.easeOut) + +Animation.linear == .linear(duration: 0.35) +Animation.linear == .linear(duration: 0.75) + +Color.white == .white +Color.white == .red + +Color(white: 1) == .white + +Color(.displayP3, white: 1) == Color(.sRGB, white: 1) + +do { + var path = Path() + path.addLine(to: .init(x: 10, y: 10)) + path.addLine(to: .init(x: 10, y: 0)) + path.closeSubpath() + + var path2 = Path() + path2.addLine(to: .init(x: 10, y: 0)) + path2.addLine(to: .init(x: 10, y: 10)) + path2.closeSubpath() + + path == path2 +} + +Text("Hello") == Text("Hello") +Text("Hello") == Text("Hello Blob") +Text("Hello \("Blob")") == Text("Hello \("Blob")") +let text = Text("Hello \("Blob")") +text == text + +let task1 = Task {} +let task2 = Task {} +task1 == task1 +task1 == task2 + +\String.count == \String.count +\String.count == \String.description + +(\String.count as AnyKeyPath) == \String.count + +\String.count == \[Int].count + +\[Int].count == \[String].count + +(\String.count).hashValue +(\String.description).hashValue +(\[String].count).hashValue +(\[Int].count).hashValue + +var propertiesAccessed: Set> = [] +propertiesAccessed.insert(\.name) +propertiesAccessed.insert(\.id) +propertiesAccessed + +extension Binding: Equatable where Value: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.wrappedValue == rhs.wrappedValue + } +} + +@Observable class Model { + var count = 0 +} +do { + @Bindable var model = Model() + let count = $model.count + + count.wrappedValue + count.wrappedValue += 1 + count.wrappedValue + model.count +} diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground new file mode 100644 index 00000000..e8c513a4 --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj new file mode 100644 index 00000000..e158171d --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.pbxproj @@ -0,0 +1,387 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXCopyFilesBuildPhase section */ + 4B7FD74B2CAB1B8100205C7D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BackToBasicsEquatableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BackToBasicsEquatable; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B7FD7572CAB1B9000205C7D /* BackToBasicsEquatable.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = BackToBasicsEquatable.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BackToBasicsEquatableTests; + sourceTree = ""; + }; + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = BackToBasicsEquatable; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A2B2E8B2CAC6AA1001A982B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B7FD74A2CAB1B8100205C7D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4B7FD7442CAB1B8100205C7D = { + isa = PBXGroup; + children = ( + 4B7FD7572CAB1B9000205C7D /* BackToBasicsEquatable.playground */, + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + 4B7FD74E2CAB1B8100205C7D /* Products */, + ); + sourceTree = ""; + }; + 4B7FD74E2CAB1B8100205C7D /* Products */ = { + isa = PBXGroup; + children = ( + 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2A2B2E8D2CAC6AA1001A982B /* BackToBasicsEquatableTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A2B2E942CAC6AA1001A982B /* Build configuration list for PBXNativeTarget "BackToBasicsEquatableTests" */; + buildPhases = ( + 2A2B2E8A2CAC6AA1001A982B /* Sources */, + 2A2B2E8B2CAC6AA1001A982B /* Frameworks */, + 2A2B2E8C2CAC6AA1001A982B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 2A2B2E8F2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + ); + name = BackToBasicsEquatableTests; + packageProductDependencies = ( + ); + productName = BackToBasicsEquatableTests; + productReference = 2A2B2E8E2CAC6AA1001A982B /* BackToBasicsEquatableTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 4B7FD74C2CAB1B8100205C7D /* BackToBasicsEquatable */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B7FD7542CAB1B8100205C7D /* Build configuration list for PBXNativeTarget "BackToBasicsEquatable" */; + buildPhases = ( + 4B7FD7492CAB1B8100205C7D /* Sources */, + 4B7FD74A2CAB1B8100205C7D /* Frameworks */, + 4B7FD74B2CAB1B8100205C7D /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 4B7FD74F2CAB1B8100205C7D /* BackToBasicsEquatable */, + ); + name = BackToBasicsEquatable; + packageProductDependencies = ( + ); + productName = BackToBasicsEquatable; + productReference = 4B7FD74D2CAB1B8100205C7D /* BackToBasicsEquatable */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4B7FD7452CAB1B8100205C7D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + TargetAttributes = { + 2A2B2E8D2CAC6AA1001A982B = { + CreatedOnToolsVersion = 16.0; + }; + 4B7FD74C2CAB1B8100205C7D = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 4B7FD7482CAB1B8100205C7D /* Build configuration list for PBXProject "BackToBasicsEquatable" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4B7FD7442CAB1B8100205C7D; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 4B7FD74E2CAB1B8100205C7D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4B7FD74C2CAB1B8100205C7D /* BackToBasicsEquatable */, + 2A2B2E8D2CAC6AA1001A982B /* BackToBasicsEquatableTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A2B2E8C2CAC6AA1001A982B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2A2B2E8A2CAC6AA1001A982B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4B7FD7492CAB1B8100205C7D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2A2B2E922CAC6AA1001A982B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.BackToBasicsEquatableTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 2A2B2E932CAC6AA1001A982B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.BackToBasicsEquatableTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 4B7FD7522CAB1B8100205C7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 4B7FD7532CAB1B8100205C7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 4B7FD7552CAB1B8100205C7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 4B7FD7562CAB1B8100205C7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A2B2E942CAC6AA1001A982B /* Build configuration list for PBXNativeTarget "BackToBasicsEquatableTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2B2E922CAC6AA1001A982B /* Debug */, + 2A2B2E932CAC6AA1001A982B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B7FD7482CAB1B8100205C7D /* Build configuration list for PBXProject "BackToBasicsEquatable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B7FD7522CAB1B8100205C7D /* Debug */, + 4B7FD7532CAB1B8100205C7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4B7FD7542CAB1B8100205C7D /* Build configuration list for PBXNativeTarget "BackToBasicsEquatable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B7FD7552CAB1B8100205C7D /* Debug */, + 4B7FD7562CAB1B8100205C7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4B7FD7452CAB1B8100205C7D /* Project object */; +} diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme new file mode 100644 index 00000000..0360b52d --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable.xcodeproj/xcshareddata/xcschemes/BackToBasicsEquatable.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable/main.swift b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable/main.swift new file mode 100644 index 00000000..c3998546 --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatable/main.swift @@ -0,0 +1,11 @@ +// +// main.swift +// BackToBasicsEquatable +// +// Created by Point-Free on 9/30/24. +// + +import Foundation + +print("Hello, World!") + diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan new file mode 100644 index 00000000..ba9db17c --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatable.xctestplan @@ -0,0 +1,29 @@ +{ + "configurations" : [ + { + "id" : "E46DF23E-B3AC-45B3-BD7C-8E13816520AA", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:BackToBasicsEquatable.xcodeproj", + "identifier" : "4B7FD74C2CAB1B8100205C7D", + "name" : "BackToBasicsEquatable" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:BackToBasicsEquatable.xcodeproj", + "identifier" : "2A2B2E8D2CAC6AA1001A982B", + "name" : "BackToBasicsEquatableTests" + } + } + ], + "version" : 1 +} diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift new file mode 100644 index 00000000..602283cd --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/BackToBasicsEquatableTests.swift @@ -0,0 +1,64 @@ +import Testing + +struct BackToBasicsEquatableTests { + @Test + func notifier() async throws { + var timerTickCount = 0 + let cancellable = NotificationCenter.default + .publisher(for: Notification.Name("TimerNotifier-main")) + .sink { _ in timerTickCount += 1 } + + var notifiers = Set([TimerNotifier(name: "main")]) + + do { + let notifier = TimerNotifier(name: "main") + notifier.startTimer() + #expect(notifiers.insert(notifier).inserted) + } + + try await Task.sleep(for: .seconds(1.1)) + #expect(timerTickCount == 1) + + _ = cancellable + } +} + +import Foundation + +final class TimerNotifier: Hashable { + static func == (lhs: TimerNotifier, rhs: TimerNotifier) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + + let name: String + var task: Task? + + init(name: String) { + self.name = name + } + + deinit { + task?.cancel() + } + + func startTimer() { + task = Task { [name] in + while true { + try await Task.sleep(for: .seconds(1)) + NotificationCenter.default.post( + name: Notification.Name("TimerNotifier-\(name)"), + object: nil + ) + } + } + } + + func stopTimer() { + task?.cancel() + task = nil + } +} diff --git a/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift new file mode 100644 index 00000000..0914225c --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/BackToBasicsEquatable/BackToBasicsEquatableTests/SwiftDataTests.swift @@ -0,0 +1,41 @@ +import SwiftData +import Testing + +@Model +class Item { + var name: String + init(name: String) { + self.name = name + } +} + +@Test +func model() async throws { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) + let modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) + let context = ModelContext(modelContainer) + + let item = Item(name: "Blob") + context.insert(item) + try context.save() + + let fetchedItem = try context.fetch(FetchDescriptor()).first! + + #expect(fetchedItem == item) + #expect(fetchedItem === item) + + do { + let context = ModelContext(item.modelContext!.container) + + let fetchedItem = try context.fetch(FetchDescriptor()).first! + + #expect(fetchedItem.name == item.name) + #expect(fetchedItem.id == item.id) + + #expect(fetchedItem == item) + #expect(fetchedItem === item) + } +} diff --git a/0300-back-to-basics-equatable-pt4/README.md b/0300-back-to-basics-equatable-pt4/README.md new file mode 100644 index 00000000..b4901cae --- /dev/null +++ b/0300-back-to-basics-equatable-pt4/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Back to Basics: Advanced Hashable](https://www.pointfree.co/episodes/ep300-back-to-basics-advanced-hashable) +> +> We zoom out a bit to get a greater appreciation for how `Equatable` and `Hashable` are used throughout the greater language and ecosystem, including actors, standard library types, SwiftUI, and more. diff --git a/0301-sqlite-pt1/README.md b/0301-sqlite-pt1/README.md new file mode 100644 index 00000000..f6125432 --- /dev/null +++ b/0301-sqlite-pt1/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [SQLite: The C Library](https://www.pointfree.co/episodes/ep301-sqlite-the-c-library) +> +> SQLite is one of the most well-crafted, battle-tested, widely-deployed pieces of software in history, and it’s a great fit for apps with more complex persistence needs than user defaults or a JSON file. Let’s get familiar with the library, starting with a crash course in interacting with C code from Swift. diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.pbxproj b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ce42ca6f --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.pbxproj @@ -0,0 +1,445 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXContainerItemProxy section */ + 2A2D2A8A2CD2BAD00071022C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2A2D2A712CD2BACC0071022C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2A2D2A782CD2BACC0071022C; + remoteInfo = SQLiteExplorations; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2A2D2A792CD2BACC0071022C /* SQLiteExplorations.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SQLiteExplorations.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A2D2A892CD2BAD00071022C /* SQLiteExplorationsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SQLiteExplorationsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 2A2D2A7B2CD2BACC0071022C /* SQLiteExplorations */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = SQLiteExplorations; + sourceTree = ""; + }; + 2A2D2A8C2CD2BAD00071022C /* SQLiteExplorationsTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = SQLiteExplorationsTests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A2D2A762CD2BACC0071022C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2D2A862CD2BAD00071022C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2A2D2A702CD2BACC0071022C = { + isa = PBXGroup; + children = ( + 2A2D2A7B2CD2BACC0071022C /* SQLiteExplorations */, + 2A2D2A8C2CD2BAD00071022C /* SQLiteExplorationsTests */, + 2A2D2A7A2CD2BACC0071022C /* Products */, + ); + sourceTree = ""; + }; + 2A2D2A7A2CD2BACC0071022C /* Products */ = { + isa = PBXGroup; + children = ( + 2A2D2A792CD2BACC0071022C /* SQLiteExplorations.app */, + 2A2D2A892CD2BAD00071022C /* SQLiteExplorationsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2A2D2A782CD2BACC0071022C /* SQLiteExplorations */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A2D2A9D2CD2BAD00071022C /* Build configuration list for PBXNativeTarget "SQLiteExplorations" */; + buildPhases = ( + 2A2D2A752CD2BACC0071022C /* Sources */, + 2A2D2A762CD2BACC0071022C /* Frameworks */, + 2A2D2A772CD2BACC0071022C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 2A2D2A7B2CD2BACC0071022C /* SQLiteExplorations */, + ); + name = SQLiteExplorations; + packageProductDependencies = ( + ); + productName = SQLiteExplorations; + productReference = 2A2D2A792CD2BACC0071022C /* SQLiteExplorations.app */; + productType = "com.apple.product-type.application"; + }; + 2A2D2A882CD2BAD00071022C /* SQLiteExplorationsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A2D2AA02CD2BAD00071022C /* Build configuration list for PBXNativeTarget "SQLiteExplorationsTests" */; + buildPhases = ( + 2A2D2A852CD2BAD00071022C /* Sources */, + 2A2D2A862CD2BAD00071022C /* Frameworks */, + 2A2D2A872CD2BAD00071022C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2A2D2A8B2CD2BAD00071022C /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 2A2D2A8C2CD2BAD00071022C /* SQLiteExplorationsTests */, + ); + name = SQLiteExplorationsTests; + packageProductDependencies = ( + ); + productName = SQLiteExplorationsTests; + productReference = 2A2D2A892CD2BAD00071022C /* SQLiteExplorationsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2A2D2A712CD2BACC0071022C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1610; + LastUpgradeCheck = 1610; + TargetAttributes = { + 2A2D2A782CD2BACC0071022C = { + CreatedOnToolsVersion = 16.1; + }; + 2A2D2A882CD2BAD00071022C = { + CreatedOnToolsVersion = 16.1; + TestTargetID = 2A2D2A782CD2BACC0071022C; + }; + }; + }; + buildConfigurationList = 2A2D2A742CD2BACC0071022C /* Build configuration list for PBXProject "SQLiteExplorations" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2A2D2A702CD2BACC0071022C; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 2A2D2A7A2CD2BACC0071022C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2A2D2A782CD2BACC0071022C /* SQLiteExplorations */, + 2A2D2A882CD2BAD00071022C /* SQLiteExplorationsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A2D2A772CD2BACC0071022C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2D2A872CD2BAD00071022C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2A2D2A752CD2BACC0071022C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2D2A852CD2BAD00071022C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 2A2D2A8B2CD2BAD00071022C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2A2D2A782CD2BACC0071022C /* SQLiteExplorations */; + targetProxy = 2A2D2A8A2CD2BAD00071022C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 2A2D2A9B2CD2BAD00071022C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 2A2D2A9C2CD2BAD00071022C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2A2D2A9E2CD2BAD00071022C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SQLiteExplorations; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2A2D2A9F2CD2BAD00071022C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SQLiteExplorations; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 2A2D2AA12CD2BAD00071022C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SQLiteExplorationsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SQLiteExplorations.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SQLiteExplorations"; + }; + name = Debug; + }; + 2A2D2AA22CD2BAD00071022C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.SQLiteExplorationsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SQLiteExplorations.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SQLiteExplorations"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A2D2A742CD2BACC0071022C /* Build configuration list for PBXProject "SQLiteExplorations" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2D2A9B2CD2BAD00071022C /* Debug */, + 2A2D2A9C2CD2BAD00071022C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A2D2A9D2CD2BAD00071022C /* Build configuration list for PBXNativeTarget "SQLiteExplorations" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2D2A9E2CD2BAD00071022C /* Debug */, + 2A2D2A9F2CD2BAD00071022C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A2D2AA02CD2BAD00071022C /* Build configuration list for PBXNativeTarget "SQLiteExplorationsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2D2AA12CD2BAD00071022C /* Debug */, + 2A2D2AA22CD2BAD00071022C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2A2D2A712CD2BACC0071022C /* Project object */; +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AccentColor.colorset/Contents.json b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AppIcon.appiconset/Contents.json b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..23058801 --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/Contents.json b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/ContentView.swift b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/ContentView.swift new file mode 100644 index 00000000..f6a40a67 --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/ContentView.swift @@ -0,0 +1,17 @@ +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/SQLiteExplorationsApp.swift b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/SQLiteExplorationsApp.swift new file mode 100644 index 00000000..42c066af --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorations/SQLiteExplorationsApp.swift @@ -0,0 +1,110 @@ +import SQLite3 +import SwiftUI + +@main +struct SQLiteExplorationsApp: App { + init() { + let databasePath = URL.documentsDirectory.appending(path: "db.sqlite") + .path() + print("open", databasePath) + var db: OpaquePointer? + guard + sqlite3_open_v2( + databasePath, + &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + nil + ) == SQLITE_OK + else { + fatalError("Could not open database at \(databasePath)") + } + print( + "Create table", + sqlite3_exec( + db, + """ + CREATE TABLE IF NOT EXISTS "players" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, + "name" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL + ) + """, + nil, + nil, + nil + ) == SQLITE_OK + ) + let name = "Blob" + let createdAt = Date() + var statement: OpaquePointer? + sqlite3_prepare_v2( + db, + """ + INSERT INTO "players" + ("name", "createdAt") + VALUES + (?, ?) + """, + -1, + &statement, + nil + ) + let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) + print( + "Binding name", + sqlite3_bind_text( + statement, + 1, + name, + -1, + SQLITE_TRANSIENT + ) == SQLITE_OK + ) + print( + "Binding createdAt", + sqlite3_bind_int64( + statement, + 2, + Int64(createdAt.timeIntervalSince1970) + ) == SQLITE_OK + ) + print( + "Insert Blob", + sqlite3_step(statement) == SQLITE_DONE + ) + print( + "Finalize insert", + sqlite3_finalize(statement) == SQLITE_OK + ) + statement = nil + sqlite3_prepare_v2( + db, + """ + SELECT * FROM "players" WHERE "createdAt" > ? + """, + -1, + &statement, + nil + ) + sqlite3_bind_int64(statement, 1, Int64(Date().addingTimeInterval(-10).timeIntervalSince1970)) + while sqlite3_step(statement) == SQLITE_ROW { + struct Player { + let id: Int64 + let name: String + let createdAt: Date + } + let id: Int64 = sqlite3_column_int64(statement, 0) + let name = String(cString: sqlite3_column_text(statement, 1)) + let createdAt = Date(timeIntervalSince1970: Double(sqlite3_column_int64(statement, 2))) + let player = Player(id: id, name: name, createdAt: createdAt) + print(player) + } + sqlite3_finalize(statement) + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorationsTests/SQLiteExplorationsTests.swift b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorationsTests/SQLiteExplorationsTests.swift new file mode 100644 index 00000000..eca431bc --- /dev/null +++ b/0301-sqlite-pt1/SQLiteExplorations/SQLiteExplorationsTests/SQLiteExplorationsTests.swift @@ -0,0 +1,17 @@ +// +// SQLiteExplorationsTests.swift +// SQLiteExplorationsTests +// +// Created by Point-Free on 10/30/24. +// + +import Testing +@testable import SQLiteExplorations + +struct SQLiteExplorationsTests { + + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + } + +} diff --git a/README.md b/README.md index e61b22ab..f2a77a8d 100644 --- a/README.md +++ b/README.md @@ -300,3 +300,6 @@ This repository is the home of code written on episodes of [Point-Free](https:// 1. [Cross-Platform Swift: Persistence](0296-cross-platform-pt7) 1. [Back to Basics: Equatable](0297-back-to-basics-equatable-pt1) 1. [Back to Basics: Hashable](0298-back-to-basics-equatable-pt2) +1. [Back to Basics: Hashable References](0299-back-to-basics-equatable-pt3) +1. [Back to Basics: Advanced Hashable](0300-back-to-basics-equatable-pt4) +1. [SQLite: The C Library](0301-sqlite-pt1)