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 init
s, 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
wasnil
or ifreply.repliesIDs
wasnil
or if JSON parsing failed); and - you have paths of execution where you leave the group prematurely (if
reply.repliesIDs
was notnil
, you must move theleave()
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
How to Install Swift Package via Package Manager
Using a Property as a Default Parameter Value for a Method in the Same Class
How to Mock Uiapplication in Swift
Get Path to Swift Script from Within Script
How Does Optional Covariance Work in Swift
Swiftui: How Do Style Text View with Different Font and Colour on String Subranges
How to Sort Objects by Its Enum Value
Swift Arc4Random_Uniform(Max) in Linux
Bitwise Operations with Cgbitmapinfo and Cgimagealphainfo
Swift 2.0 Constraintswithvisualformat
Can You Use String/Character Literals Within Swift String Interpolation
Swift Combine Alternative to Rx Observable.Create
Swift Package Manager - Swift 4 Syntax
Change What Print(Object) Displays in Swift 2.0
Create Instance of Class Known at Runtime in Swift