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-sizestruct
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.
- Contiguity is promised only in the case of native Swift arrays [not bridged from Objective-C].
- 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.
struct
s 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
Swift: Nsstatusitem Menu Behaviour in 10.10 (E.G. Show Only on Right Mouse Click)
Search Multiple Words in One String in Swift
Ckcontainer.Discoverallidentities Always Fails
How to Import Modules Without an Xcode Project in Swift
How to Make Player Move to Opposite Side While Is in a Path
Get Color of Point in a Skscene Swift
Can a Subclass Override a Function and Make More Restrictive Return
How to Copy Skspritenode with Skphysicsbody
Context Menu Not Updating in Swiftui
Draw Mkpointannotation with Title in Mksnapshot Image
Protocol Extension Initializer Forcing to Call Self.Init
Add Datepicker in Uiactionsheet Using Swift
Cllocation Distancefromlocation (In Swift)
Sending an Email from Your App with an Image Attached in Swift