Ok, here we go. The LLM has created a 5-point plan for today.
- Concept Overview
- Code Examples
- Hands-on Exercises
- Interview Angle
- Resources Review
I'm going to go through this bit by bit, and see how I feel. Maybe I'll mix these plan items up or merge them on subsequent days.
Concept Overview
Codable
Ok, straightforward start, I know this. Codable's been around quite a long time. What I already know is that conforming to the Codable protocol means that the runtime can easily serialize/deserialize the object into some other format. It's usually JSON, but I suppose it could be any kind of data container.
The LLM says:
A type alias for Encodable & Decodable. It powers Swift’s native JSON (and plist) encoding/decoding. Works with JSONDecoder/JSONEncoder. Eliminates boilerplate serialization code.
This makes sense. I didn't know that Codable was a typealias though. I should maybe look that up.
Ahh yes. It got that sentence straight from the Docs Page For Codable.
Looking at that page, I can see the Encodable & Decodable protocols. I'm also seeing the protocols CodingKey, CodingKeyRepresentable, and CodingUserInfoKey. I'm less familiar with these, but I presume they're used for unique identification somehow. I did a little bit of reading on them, and am going to add them to a running list of concepts that I want to come back to.
🔲 Understand CodingKey, CodingKeyRepresentable, and CodingUserInfoKey; check out this section of the docs
I've used Codable before, so it's not unfamiliar. I'm not automatic in my usage of JSONEncoder and JSONDecoder, but I expect to be by the end of these two weeks.
One thing that I remember is if you declare that your struct conforms to Codable, then every member of that struct also needs to conform. Luckly, most of Swift's standard types are already confirming, so if we stick with those, then it shouldn't be a problem. It only gets tricky when you are trying to conform and you have a member that is non-conforming, like:
struct Classroom: Codable {
var teacher: Teacher // <- Teacher is non-conforming
}
struct Teacher {
}
// This won't compile. You'll get an error that says:
// Type 'Classroom' does not conform to protocol 'Decodable'
// Protocol requires initializer 'init(from:)' with type 'Decodable' (Swift.Decodable.init)
// Cannot automatically synthesize 'Decodable' because 'Teacher' does not conform to 'Decodable'
// You'll see the same problem with 'Encodable'
I've run into this before, so it's good to keep in mind.
Hashable
This one's important. Code Academy describes hashing as:
Hashing is the process of converting data — text, numbers, files, or anything, really — into a fixed-length string of letters and numbers.
Cool, I knew that. The LLM wants me to know that Hashable is a:
Protocol that allows values to be stored in sets or used as dictionary keys. Synthesized automatically for structs/enums with Equatable properties. Important for ForEach in SwiftUI.
This sounds correct to me. I'm pretty familiar with the concept of hashing, so I'm not going to dwell on this too much. But this bit about the Equatable properties is interesting, I'm going to dive a little deeper on that. The Hashable Documentation Page has this interesting nugget when discussing custom types:
Hashing a value means feeding its essential components into a hash function, represented by the Hasher type. Essential components are those that contribute to the type’s implementation of Equatable. Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.
What this means is that not every aspect of a type needs to be included in a hashed value of that type. We only need to include those values that can be compared with the Equatable protocol. But we do need to include all those values.
Oh, interesting. From the same page:
The Hashable protocol inherits from the Equatable protocol, so you must also satisfy that protocol’s requirements.
That's probably enough on Hashable. There's more on using different hash functions, using the Hasher property, but I don't need to get into that right now.
Result
I've used a Result in other languages, so I'm relatively comfortable with what it does. But I haven't used it as much in Swift. Let's see what the LLM wants me to know!
Enum that encapsulates success/failure without exceptions
enum Result<Success, Failure: Error> { case success(Success), failure(Failure) }
Standard for async completion handlers and error handling
Yep, that seems right. Let's check the Swift docs to make sure.
Oh that's interesting. The Swift docs have something somewhat different:
A value that represents either a success or a failure, including an associated value in each case.
@frozen
enum Result<Success, Failure> where Failure : Error, Success : ~Copyable, Success : ~Escapable
So this may be a case of the LLM hallucinating something, or maybe this was an old signature, and newer syntax is after the knowledge cutoff point? Either way, I'm glad I looked.
Now, the signature in the docs has some things I'm not totally sure of, so I'll look them up:
@frozen
Thanks to Wesley de Groot, I now know that this is a compiler directive for optimizing memory layout and improving performance. The upshot is that because Apple has marked this Result interface as @frozen, we can be certain that it will never change.
~Copyable and ~Escapable
I know that these are both protocols and I have two questions:
- What do they do?
- What is the tilde (~)?
It looks like Copyable is:
A type whose values can be implicitly or explicitly copied.
I presume that by 'copied', they're talking about memory; in the way that a value type doesn't have a pointer to a memory address that's passed around, but a reference type does. So if something conforms to Copyable, then it can be duplicated in memory.
It doesn't look like I need to worry about Copyable for the most part.
Escapable was harder to find. There doesn't seem to be a simple docs page I can look at. However, I did find the pitch for it, which gives some good context:
We would like to propose adding a new type constraint ~Escapable for types that can be locally copied but cannot be assigned or transferred outside of the immediate context.
Note that the pitch is for ~Escapable, with the tilde. Which brings me to what the tilde is actually for!
There's a little blurb in the Copyable documentation that explains it:
To suppress an implicit conformance to Copyable you write ~Copyable.
So the tilde basically negates some implicit conformance that this thing may have.
And so bringing it back to the Result above, then this is basically saying - if I understand correctly - that a Result is an enum that has a Success case that will NOT be Copyable, nor will it be Escapable.
I'm not sure if I'm entirely satisfied with this explanation, but it'll do for now. I probably won't have to worry about it too often, and if I do, I'll dig deeper then.
KeyPath
Oooo, this one's fun. LLM Says:
A strongly-typed reference to a property, e.g. \User.name. Useful for dynamic access, generics, and SwiftUI bindings.
The Swift Docs on KeyPath aren't quite as specific:
A key path from a specific root type to a specific resulting value type.
Digging a little deeper, The Swift Programming Language Online Book has a nice section on Key-Path Expressions:
A key-path expression refers to a property or subscript of a type.
I'm not sure that quite agrees with the LLM's definition. I don't think the LLM is completely out to lunch, I think it's trying to say that it's a way of safely referencing a member of some type, instead of referencing the instance of that member on an instance of the type. Which is what KeyPaths are.
Well, maybe it is. From the same page:
At compile time, a key-path expression is replaced by an instance of the KeyPath class.
So that makes sense. A KeyPath is a strongly-typed reference to a type's property. A Key-Path Expression is a way of creating a KeyPath object at compile time. It's the code you write that ultimately generates a KeyPath (if the type you referenced exists)
Either way, it's useful for referencing types themselves, instead of values or instances of the types.
some vs any
LLM says:
• some → Opaque type. The caller doesn’t know the concrete type, but the compiler enforces a single consistent type (common in SwiftUI’s some View).
• any → Existential type. Used when you need heterogeneity at runtime, but you lose static guarantees and performance optimizations.
This one I know, I think. These are both type annotations that are used in different spots. Both of them are used to broaden the types that the compiler will accept in that spot.
I don't think I need to verify the LLM here.
Code Examples
The LLM has generated a bunch of Swift code here, that gives examples of all the things we've seen above. I guess this is a good opportunity to wade through it and figure out if it's gotten things right.
Codable
This works, though I'm not in love with the force-unwrap at the end of the json. I suppose it's fine because we're putting valid JSON in-line.
What is with the #"..."# hashes on either side of the string?
I replicated it in Xcode with let json = #"{"id":1, "name":"Francis"}"#, and that appears to be valid Swift code.
Aha, Thank you Paul Hudson! This appears to be a "Raw string", introduced in Swift 5 - no wonder I didn't recognise it. Raw strings basically let you use the pound sign as a custom string delimiter, so that you can use special characters in the string, and it won't be interpreted in a raw string.
// Codable
struct User: Codable, Hashable {
let id: Int
let name: String
}
let json = #"{"id":1,"name":"Francis"}"#.data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
Hashable
This is interesting. Why did it choose this code snippet to demonstrate Hashable? Doing a little spelunking of the docs makes me wonder if it's missing a step here.
I think the below snippet is a better showcasing of the Identifiable protocol, which is definitely used in SwiftUI.
The Identifiable protocol has a generic associatedtype called ID, which has to be Hashable. It also has a property id: Self.ID, so yeah. In the below example, List probably conforms to Identifiable, which in turn has an id that is a Hashable type.
// Hashable + SwiftUI
struct UserRow: View {
let users: [User]
var body: some View {
List(users, id: \.self) { user in
Text(user.name)
}
}
}
Result
Ok, this is similar to Result in other languages. I thought that Dart has a Result class as well, that works roughly the same way. However, I can't easily find any kind of built-in version. No matter.
Below, we're just running a function (JSONDecoder.decode()), and if it throws an error, we catch it, and return the Result enum with the error. If we successfully decode the JSON, then we can return the Result with the object we decoded.
// Result
func loadData(completion: (Result<User, Error>) -> Void) {
do {
let user = try JSONDecoder().decode(User.self, from: json)
completion(.success(user))
} catch {
completion(.failure(error))
}
}
KeyPath
This is an example of a method that maps over a collection, and transforms an array of type T, using a KeyPath U.
// KeyPath
func values<T, U>(from objects: [T], at keyPath: KeyPath<T, U>) -> [U] {
objects.map { $0[keyPath: keyPath] }
}
let names = values(from: [user], at: \.name) // ["Francis"]
some vs any
I wasn't really quite understanding this, so I checked out Donny Wals's blog post on the subject.
The need for these keywords is due to SwiftUI, and its use of opaque result types in the View object. Well, Opaque Types are certainly used in other places too, but that's out-of-scope for me right now.
At any rate, it has to do with the desire to hide implementation details of protocol-oriented code from users of said code. It's worth noting that there are official names here:
some == Opaque Protocol Type
Effectively, a some return type will return only one type that conforms to the protocol that is being labelled.
any == Boxed Protocol Type
An any return type will return one or more types that conform to the protocol being labelled.
So this is why in the example below, the makeDog() function ONLY returns a Dog, while randomAnimal() can return anything that conforms to the Animal protocol.
The Apple documentation page on Opaque and Boxed Protocol Types has a great little description that highlighted one of the biggest practical differences for me:
A function or method that returns an opaque type hides its return value’s type information. Instead of providing a concrete type as the function’s return type, the return value is described in terms of the protocols it supports. Opaque types preserve type identity — the compiler has access to the type information, but clients of the module don’t. ... A boxed protocol type can store an instance of any type that conforms to the given protocol. Boxed protocol types don’t preserve type identity — the value’s specific type isn’t known until runtime, and it can change over time as different values are stored.
A lot of the value of this is tied in with the Swift's strong type system, and all the lovely helpful optimizations that the compiler can give you as it teases apart your code. If you start throwing protocols all over the place, and the compiler can't figure out which types will ACTUALLY be used, then this causes issues. Opaque protocol types are meant to give you the information-hiding ability of protocols, but with the strong compiler guarantees that we like in Swift.
// some vs any
protocol Animal { func speak() -> String }
struct Dog: Animal { func speak() -> String { "Woof" } }
struct Cat: Animal { func speak() -> String { "Meow" } }
func makeDog() -> some Animal { Dog() } // always a Dog
func randomAnimal() -> any Animal { Bool.random() ? Dog() : Cat() }
Hands-On Exercises
Ok, now we're cooking! Actually, not really. Life stuff has pushed Day 1 into Day 3. Oh well!
Codable & Result
Create a small function that fetches JSON from a public API and decodes it into a struct using
Resultin the completion.
// SwiftCrashCourse.playground
import Cocoa
struct BreedList: Decodable {
let breeds: [Breed]
private enum CodingKeys: String, CodingKey { case breeds = "data" }
}
struct Breed: Decodable {
let name: String
let description: String
let hypoallergenic: Bool
private enum CodingKeys: String, CodingKey {
case attributes
}
private enum AttributesKeys: String, CodingKey {
case name, description, hypoallergenic
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let attributes = try container.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes)
name = try attributes.decode(String.self, forKey: .name)
description = try attributes.decode(String.self, forKey: .name)
hypoallergenic = try attributes.decode(Bool.self, forKey: .hypoallergenic)
}
}
func getBreeds(completion: (Result<[BreedList], Error>) -> Void) {
let breedURL = URL(string: "https://dogapi.dog/api/v2/breeds")!
do {
let raw = try String(contentsOf: breedURL, encoding: .utf8)
let breeds = try JSONDecoder().decode(BreedList.self, from: raw.data(using: .utf8)!)
completion(.success([breeds]))
} catch {
completion(.failure(error))
}
}
getBreeds { result in
switch result {
case .success(let breeds):
print(breeds.prefix(3))
case .failure(let error):
print(error)
}
}
It's not pretty, but it's making sense now.
Hashable & SwiftUI
Build a SwiftUI List of unique items (e.g., city names). Try adding duplicates — observe how Hashable affects rendering.
I want to use my playground here, but have to do a quick refresher on how;
// Note that "Apia, Samoa" appears twice
let rawCities = """
Adamstown, Pitcairn Islands
Alofi, Niue, New Zealand
Antalaha, Madagascar
Apia, Samoa
Apia, Samoa
Atuona, Hiva Oa, French Polynesia
Avarua, Cook Islands
Bandar Seri Begawan, Brunei
Bluefields, Nicaragua
Bocas del Toro, Panama
Boende, Democratic Republic of the Congo
Buenaventura, Colombia
Castries, Saint Lucia (bordering on Am)
Changuinola, Panama
Colombo, Sri Lanka
"""
struct HashablePracticeView: View {
let cities = rawCities.components(separatedBy: .newlines)
var body: some View {
List(cities, id: \.self) { city in
Text("\(city.hash)-\(city)")
}
}
}
PlaygroundPage.current.setLiveView(HashablePracticeView())
When I'm using just bare String as city names, "Apia, Samoa" appears twice. I added the city's hash value to the text I'm printing out, and it shows that the hash for that city is exactly the same in both rows.
I didn't quite understand what the LLM was trying to suggest with
Observe how
Hashableaffects rendering
On asking for clarification, I now understand it better. Effectively, List uses the hash of each element inside it to disambiguate between the different elements. The subtle issues arise when you you try to make changes to the List. If you have two elements whose hashes are the same, then List can't really tell them apart.
I tried to play with the line:
List(cities, id: \.self) { city in
to change the id: parameter, but effectively, the hash & hashValue between two identical Strings is always the same.
KeyPath Utility
Write a function that takes an array of structs and a
KeyPathand returns the maximum value for that property
struct ValueBox {
let value: Int
}
let values = [
ValueBox(value: 70),
ValueBox(value: 10),
ValueBox(value: 520),
ValueBox(value: 80),
ValueBox(value: 430),
ValueBox(value: 50),
ValueBox(value: 200),
ValueBox(value: 100),
]
func getMax(boxes: [ValueBox], path: KeyPath<ValueBox, Int>) -> Int? {
boxes.map { $0[keyPath: path]}.max()
}
getMax(boxes: values, path: \ValueBox.value)
This is a simplistic example, but we're using KeyPath in the getMax function to abstract out the actual property to the call site.
So if ValueBox didn't just have value, but say a weight & height, we could call:
getMax(boxes: values, path: \ValueBox.weight)
getMax(boxes: values, path: \ValueBox.height)
So that would allow you to use the same function on whichever property you wanted at the time.
Interview Angle
For these, the LLM just provided me with a few Questions and Answers. I think my job will be to verify them from another source.
Q1: What's the difference between some and any in Swift?
A:
some-> Opaque, compiler-enforced single concrete type. Static dispatch, good for SwiftUI.any-> Existential, can hold multiple conforming types. Dynamic Dispatch, more flexible but slower.
Verification: Just from going through everything in this post, I certainly agree with the some being a "single concrete type", and any being "multiple conforming types". While Opaque makes sense for some, it sounds like Apple's docs use the term Boxed for any, rather than "Existential. I'm not sure exactly what's meant by that last word.
Also, I can't find confirmation that some vs any has a method dispatch difference, but it WOULD make sense. If you know for sure that you're only going to see one type, the compiler can make optmizations ahead of runtime. But if it could be any conforming type, there's probably a lookup table that has to be kept somewhere.
Q2: How does Swift's Codable work under the hood?
A:
- It's type-safe synthesis of
init(from:)andencode(to:)for structs/enums. - Uses coding keys (CodingKey) to map JSON keys.
- Supports nested containers, custom encoding if needed.
Verification: Yeah, ok sure. This all seems largely correct. The init(from:) and encode(to:) methods are what are used to do the actual encoding and decoding, and these can be overridden for custom versions. CodingKey is used to map your own variable names to the JSON keys, so that's technically true as well.
That said, I wouldn't call these bullet points a comprehensive answer. It says nothing about how Codable is actually a typealias for two protocols: Decodable and Encodable, or how it all fits together. It doesn't mention that all your struct/enum members must be themselves Codable, or you'll get a compiler error. This is fine if you're using standard Swift types, but if you've got custom types in there, you may have to do a bit of extra work.
Q3: Why is Hashable important in SwiftUI Lists?
A:
- SwiftUI diffing relies on identity (id: parameter).
- Hashable lets SwiftUI uniquely identify and diff elements for efficient rendering.
- Without it, you’d need manual id properties.
Verification: I like that the first bullet point mentions diffing and identity. I think that's the real key here. The algorithm that decides that something has changed on-screen really needs to be able to identify UI tree elements uniquely. If two elements' hash values are the same, then SwiftUI won't be able to tell them apart, and strange things might happen.
I guess another point that should be made is that Hashing is the process of converting a complex set of information (like a struct and all its variables) into a known-size simpler piece of information that can be easily compared (ie. a UUID or a String). That's WHY Hashable is important, because it allows SwiftUI to simplify UI elements into simple objects that can be easily compared for equality.
Resources for Deeper Dive
- Apple Docs: Swift Language Guide -> Protocols & Generics
- WWDC 2022: Embrace Swift Generics - covers
someandany - SE-0244: Opaque Result Types - formal proposal introducing
some
These are pretty decent for a deeper dive. I like that it dropped in the proposal for opaque result types.. that feels like a pretty deep cut.
Wrapping Up Day ... 5?
Life is doing its thing, and I haven't been able to devote as much time to this as I'd like. That's ok, I'll just keep plugging away. Day 2 starts tomorrow!