Swift Struct Type Recursion

Swift struct type recursion

The answer is in your question: structs are value types. If you include a substruct B into a struct A, it means, that one object of type A will have a size sizeof(all_other_fields_of_A) + sizeof(B). So, a value type can not be recursive: it would have infinite size.

Recursive generic structs with different types in Swift 4

You have 2 options for doing this.

The simple solution, improving on @Cristik's answer, is to have another initialiser that doesn't expect another generic parameter:

struct Lock<Element> {
var element: Element

init(_ element: Element) {
self.element = element
}
init<T>(_ element: Element, _ args: [Lock<T>]?) {
self.init(element)
}
}

You can then have the code you want above, but you lose the information that Element conforms to Hashable.


Your second option is to create a protocol using an associatedtype. Using a similar trick with having 2 inits, you can do the same except explicitly defining the types:

protocol Lock {
associatedtype Element

init(_ element: Element)
init<T>(_ element: Element, _ args: [T]?) where T: Lock
}

struct HashableLock<H: Hashable>: Lock {
typealias Element = H

var element: Element

init(_ element: Element) {
self.element = element
}
init<T>(_ element: Element, _ args: [T]?) where T: Lock {
self.init(element)
}
}

struct IntLock: Lock {
typealias Element = Int

var element: Int

init(_ element: Int) {
self.element = element
}
init<T>(_ element: Int, _ args: [T]?) where T: Lock {
self.init(element)
}
}

Then you can create locks like this:

let stringStringLock = HashableLock("element", [HashableLock("100")])
let stringIntLock = HashableLock("element", [IntLock(100)])

The first version is a lot cleaner but it's more limiting. It's up to you which one to use, depends on your needs.

Swift Codable struct recursively containing itself as property

A simple way is to just change the struct into a class:

class Message: Codable {
let content: String
// ...other values
let reference: Message? // optional - the recursion has to end somewhere right?
}

But this could break other parts of your code, since structs and classes have vastly different semantics.

An alternative would be to make a reference type Box:

class Box<T: Codable>: Codable {
let wrappedValue: T
required init(from decoder: Decoder) throws {
wrappedValue = try T(from: decoder)
}

func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}

Then,

struct Message: Codable {
let content: String
// ...other values

let boxedReference: Box<Message>?

// you can still refer to 'reference' as 'reference' in your swift code
var reference: Message? { boxedReference?.wrappedValue }

enum CodingKeys: String, CodingKey {
case content, boxedReference = "reference"
}
}

Asynchronous closure recursion for structs

Consider this code block

let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply

if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
reply.replies = replies
}
}

If Comment was a struct, this will fetching reply, adding a copy of it to the replies array, and then, in fetchReplies you are mutating the original reply (which you must have changed from let to var for this line to even compile), not the copy in the array.

So, you might want to refer to replies[replyIndex] in your fetchReplies closure, e.g.:

let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply

if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
replies[replyIndex].replies = replies
}
}

By the way,

  • dispatch group must not be a property, but rather must be a local var (especially as you appear to be calling this method recursively!);
  • you have several paths of execution where you are not leaving the group (if data was nil or if reply.repliesIDs was nil or if JSON parsing failed); and
  • you have paths of execution where you leave the group prematurely (if reply.repliesIDs was not nil, you must move the leave() call into that completion handler closure).

I have not tested it out, but I would suggest something like:

private func fetchReplies(for comment: Comment, completionHandler: @escaping ([Comment?]) -> Void) {
var replies = [Comment?](repeating: nil, count: comment.repliesIDs!.count)
let group = DispatchGroup() // local var

for (replyIndex, replyID) in comment.repliesIDs!.enumerated() {
let replyURL = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(replyID).json")!

group.enter()

URLSession.shared.dataTask(with: replyURL) { data, _, _ in
guard let data = data else {
group.leave() // leave on failure, too
return
}

do {
let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply

if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
replies[replyIndex].replies = replies
group.leave() // if reply.replieIDs was not nil, we must not `leave` until this is done
}
} else {
group.leave() // leave if reply.repliesIDs was nil
}
} catch {
group.leave() // leave on failure, too
print(error)
}
}.resume()
}

dispatchGroup.notify(queue: .main) { // do this on main to avoid synchronization headaches
completionHandler(replies)
}
}

Swift: Recursive Value Type

TL;DR:

What you are trying to achieve is easily accomplished using split:

for s in split("first/second/third", { c in c == "/" } ) {
println("\(s)")
}

Discussion:

It seems like you are trying to write a linked list of value types. The problem is that Swift couples the concepts of copy semantics with value / reference access. (unlike say C++ which allows you to create the same object on the stack or heap). The solution would seem to be wrapping it in a reference container aka class.

class SplitString { //splits a string into parts before and after the first "/"

var preSlash: String = String()
var postSlash: Wrapped? = nil

init(_ str: String) {
var arr = Array(str)
var x = 0
for ; x < arr.count && arr[x] != "/"; x++ { preSlash.append(arr[x]) }
if x + 1 < arr.count { //if there is a slash
var postSlashStr = String()
for x++; x < arr.count; x++ {
postSlashStr.append(arr[x])
}
postSlash = Wrapped(postSlashStr)
}
}

}

class Wrapped {
var split:SplitString

init(var _ str:String) {
split = SplitString(str)
}
}

Note that this code compiles as a proof of concept but I haven't delved into your algorithm or tested it.

Edit:

In response to your edits above, this code will exercise your code above and yield the splits you want:

for (var s:SplitString? = split; s != nil; s = s?.postSlash?.split) {
println("\(s!.preSlash)")
}

Obviously having turtles all the way down doesn't make sense, per the discussion above, so you need to break the cycle, as was the case with the class containing your struct.

Note that I have been trying to answer the question that you posted, not the problem that you have. The solution to the problem that you have, is to use SequenceOf and GeneratorOf to create a sequence wrapped generator that iterates through the slashes and returns the substrings between. This is actually done for you via the split function:

for s in split("first/second/third", { c in c == "/" } ) {
println("\(s)")
}


Related Topics



Leave a reply



Submit