Strange Behaviour for Recursive Enum in Swift (Beta 7)

Strange behaviour for recursive enum in Swift (Beta 7)

A value type (an enum) cannot contain itself as a direct member, since not matter how big a data structure is, it cannot contain itself. Apparently associated data of enum cases are considered direct members of the enum, so the associated data cannot be the type of the enum itself. (Actually, I wish that they would make recursive enums work; it would be so great for functional data structures.)

However, if you have a level of indirection, it is okay. For example, the associated data can be an object (instance of a class), and that class can have a member that is the enum. Since class types are reference types, it is just a pointer and does not directly contain the object (and thus the enum), so it is fine.

The answer to your question is: [Tree] does not contain Tree directly as a member. The fields of Array are private, but we can generally infer that the storage for the elements of the array are not stored in the Array struct directly, because this struct has a fixed size for a given Array<T>, but the array can have unlimited number of elements.

When exactly do I need indirect with writing recursive enums?

I think part of the confusion stems from this assumption:

I thought arrays and tuples have the same memory layout, and that is why you can convert arrays to tuples using withUnsafeBytes and then binding the memory...

Arrays and tuples don't have the same memory layout:

  • Array<T> is a fixed-size struct with a pointer to a buffer which holds the array elements contiguously* in memory
    • Contiguity is promised only in the case of native Swift arrays [not bridged from Objective-C]. NSArray instances do not guarantee that their underlying storage is contiguous, but in the end this does not have an effect on the code below.
  • Tuples are fixed-size buffers of elements held contiguously in memory

The key thing is that the size of an Array<T> does not change with the number of elements held (its size is simply the size of a pointer to the buffer), while a tuple does. The tuple is more equivalent to the buffer the array holds, and not the array itself.

Array<T>.withUnsafeBytes calls Array<T>.withUnsafeBufferPointer, which returns the pointer to the buffer, not to the array itself. *(In the case of a non-contiguous bridged NSArray, _ArrayBuffer.withUnsafeBufferPointer has to create a temporary contiguous copy of its contents in order to return a valid buffer pointer to you.)


When laying out memory for types, the compiler needs to know how large the type is. Given the above, an Array<Foo> is statically known to be fixed in size: the size of one pointer (to a buffer elsewhere in memory).

Given

enum Foo {
case one((Foo, Foo))
}

in order to lay out the size of Foo, you need to figure out the maximum size of all of its cases. It has only the single case, so it would be the size of that case.

Figuring out the size of one requires figuring out the size of its associated value, and the size of a tuple of elements is the sum of the size of the elements themselves (taking into account padding and alignment, but we don't really care about that here).

Thus, the size of Foo is the size of one, which is the size of (Foo, Foo) laid out in memory. So, what is the size of (Foo, Foo)? Well, it's the size of Foo + the size of Foo... each of which is the size of Foo + the size of Foo... each of which is the size of Foo + the size of Foo...

Where Array<Foo> had a way out (Array<T> is the same size regardless of T), we're stuck in an infinite loop with no base case.

indirect is the keyword required to break out of the recursion and give this infinite reference a base case. It inserts an implicit pointer by making a given case the fixed size of a pointer, regardless of what it contains or points to. That makes the size of one fixed, which allows Foo to have a fixed size.

indirect is less about Foo referring to Foo in any way, and more about allowing an enum case to potentially contain itself indirectly (because direct containment would lead to an infinite loop).


As an aside, this is also why a struct cannot contain a direct instance of itself:

struct Foo {
let foo: Foo // error: Value type 'Foo' cannot have a stored property that recursively contains it
}

would lead to infinite recursion, while

struct Foo {
let foo: UnsafePointer<Foo>
}

is fine.

structs don't support the indirect keyword (at least in a struct, you have more direct control over storage and layout), but there have been pitches for adding support for this on the Swift forums.

Unexpected events emitted by Swift Combine PassThroughSubject

This example works as expected but I discovered during the process that you cannot use the pattern in the original code (internal Structs to define Input and Output) with @Published. This causes some fairly odd errors (and BAD_ACCESS in a Playground) and is a bug that was reported in Combine beta 3.

final class ViewModel: BindableObject {

var didChange = PassthroughSubject<Void, Never>()

@Published var isEnabled = false

private var cancelled = [AnyCancellable]()

init() {
bind()
}

private func bind() {
let t = $isEnabled
.map { _ in }
.eraseToAnyPublisher()
.subscribe(didChange)

cancelled += [t]
}
}

struct ContentView : View {
@ObjectBinding var viewModel = ViewModel()

var body: some View {
HStack {
viewModel.isEnabled ? Text("Button ENABLED") : Text("Button disabled")
Spacer()
Toggle(isOn: $viewModel.isEnabled, label: { Text("Enable") })
}
.padding()
}
}

JSON encode/decode Swift CNContact object generically

No, CNContact hasn't changed according to the official documentation, so it doesn't conform to the Codable protocol, which is just a typealias for Encodeable & Decodable. You can see the list of classes currently conforming to Encodable and Decodable and see here as well that CNContact is not amongst them.

However, you can write an extension for CNContact to make it conform to above protocols.

Here is an example of how to encode a CNContact object using JSONSerialization framework in Swift3. Please note that this is just an example, so I haven't parsed all possible fields and with this implementation if a certain value doesn't exist in the CNContact object, the key doesn't exist in the JSON either. Also, the decoder function is not implemented fully, but you can easily implement it if you check how the encoder works.

The names of the JSON keys were also chosen arbitrarily along with the structure, so you can change any of those.

Below piece of code is a full, working playground file, so you can test it yourself if you want to.

import Contacts

let contact = CNMutableContact()
contact.birthday = DateComponents(calendar: Calendar.current,year: 1887, month: 1, day: 1)
contact.contactType = CNContactType.person
contact.givenName = "John"
contact.familyName = "Appleseed"

contact.imageData = Data() // The profile picture as a NSData object

let homeEmail = CNLabeledValue(label:CNLabelHome, value: NSString(string: "john@example.com"))
let workEmail = CNLabeledValue(label:CNLabelWork, value: NSString(string: "j.appleseed@icloud.com"))
contact.emailAddresses = [homeEmail, workEmail]

contact.phoneNumbers = [CNLabeledValue(label:CNLabelPhoneNumberiPhone, value:CNPhoneNumber(stringValue:"(408) 555-0126"))]

let homeAddress = CNMutablePostalAddress()
homeAddress.street = "1 Infinite Loop"
homeAddress.city = "Cupertino"
homeAddress.state = "CA"
homeAddress.postalCode = "95014"
contact.postalAddresses = [CNLabeledValue(label:CNLabelHome, value:homeAddress)]

func encodeContactToJson(contact: CNContact)->Data?{
var contactDict = [String:Any]()
if let birthday = contact.birthday?.date {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
contactDict["birthday"] = df.string(from: birthday)
}
contactDict["givenName"] = contact.givenName
contactDict["familyName"] = contact.familyName
if let imageData = contact.imageData {
contactDict["image"] = imageData.base64EncodedString()
}
if contact.emailAddresses.count > 0 {
var emailAddresses = [String:String]()
for (index, emailAddress) in contact.emailAddresses.enumerated() {
emailAddresses[emailAddress.label ?? "email\(index)"] = (emailAddress.value as String)
}
contactDict["emailAddresses"] = emailAddresses
}
if contact.phoneNumbers.count > 0 {
var phoneNumbers = [String:String]()
for (index, phoneNumber) in contact.phoneNumbers.enumerated() {
phoneNumbers[phoneNumber.label ?? "phone\(index)"] = phoneNumber.value.stringValue
}
contactDict["phoneNumbers"] = phoneNumbers
}
if contact.postalAddresses.count > 0 {
var postalAddresses = [String:String]()
for (index, postalAddress) in contact.postalAddresses.enumerated() {
postalAddresses[postalAddress.label ?? "postal\(index)"] = (CNPostalAddressFormatter.string(from: postalAddress.value, style: .mailingAddress))
}
contactDict["postalAddresses"] = postalAddresses
}
return try? JSONSerialization.data(withJSONObject: contactDict)
}

func decodeContactsJson(jsonData: Data)->CNContact?{
if let jsonDict = (try? JSONSerialization.jsonObject(with: jsonData)) as? [String:Any] {
let contact = CNMutableContact()
print(jsonDict)
return contact as CNContact
} else {
return nil
}
}

if let jsonContact = encodeContactToJson(contact: contact) {
print(decodeContactsJson(jsonData: jsonContact) ?? "Decoding failed")
} else {
print("Encoding failed")
}


Related Topics



Leave a reply



Submit