1-877-547-7272
1-877-547-7272

Reputation: 303

Swift - Why can't self be used in a closure before all stored properties are initialized?

I am trying to make an Actor class as well as a Movie class that can be decoded from a JSON file. There is also a dictionary, allActors, containing each Actor instance so that each movie that is decoded references the same Actor instance. Also, when the Actor is added to the Movie's cast array, the Movie is appended to the Actor's filmography array. Here is the code I have so far:

public var allActors = [String: Actor]()

public final class Actor {
    public let name: String
    public var filmography: [Movie]

    public init(name: String, filmography: [Movie] = Array()) {
        self.name = name
        self.filmography = filmography
    }
}

public final class Movie: Codable {
    public let title: String
    public var cast: [Actor]

    enum CodingKeys: String, CodingKey {
        case title
        case cast
    }

    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decode(String.self, forKey: .title)
        cast = Array() // fourth line of this initializer
        cast = try values.decode([String].self, forKey: .cast).map {
            let actor: Actor
            if let recurringActor = allActors[$0] {
                actor = recurringActor
            } else {
                let newActor = Actor(name: $0)
                allActors[$0] = newActor
                actor = newActor
            }
            actor.filmography.append(self)
            return actor
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title, forKey: .title)
        try container.encode(cast.map {$0.name}, forKey: .cast)
    }
}

This code works, however, at the fourth line of the Movie's init(from:) initializer, cast is set to an empty array. This seems redundant, but if I remove it, I get the following error:

'self' captured by a closure before all members were initialized

Is there a way I can avoid this error? I'm not using any properties of self, so I don't see why it shouldn't be possible to use the Movie's reference in a closure.

Upvotes: 0

Views: 162

Answers (2)

Leo Dabus
Leo Dabus

Reputation: 236340

Not a direct answer to your question but you can simply decode your movies, reduce it into a dictionary and map its key/value pairs into your actors array:

struct Movie: Codable {
    let title: String
    let cast: [String]
}

struct Actor: CustomStringConvertible {
    let name: String
    let filmography: [String]
    var description: String { "Name: \(name) - Movies: \(filmography)" }
}

Playground testing:

let movies = try! JSONDecoder().decode([Movie].self, from: Data(json.utf8))

let actors = movies.reduce(into: [:]) { dict, movie in
    for name in movie.cast {
        dict[name, default: []].append(movie.title)
    }
}.map(Actor.init)

print(actors)

This will print:

[Name: Kevin Bacon - Movies: ["Diner", "Footloose", "Flatliners"], Name: Mark Ruffalo - Movies: ["Spotlight"], Name: John Laughlin - Movies: ["Footloose"], Name: Beth Grant - Movies: ["Flatliners"], Name: Liev Schreiber - Movies: ["Spotlight"], Name: John Slattery - Movies: ["Spotlight"], Name: Paul Guilfoyle - Movies: ["Spotlight"], Name: Len Cariou - Movies: ["Spotlight"], Name: Neal Huff - Movies: ["Spotlight"], Name: Colette Blonigan - Movies: ["Diner"], Name: Frances Lee McCain - Movies: ["Footloose"], Name: Dianne Wiest - Movies: ["Footloose"], Name: Ellen Barkin - Movies: ["Diner"], Name: Michael Tucker - Movies: ["Diner"], Name: Rachel McAdams - Movies: ["Spotlight"], Name: Joshua Rudoy - Movies: ["Flatliners"], Name: Jamey Sheridan - Movies: ["Spotlight"], Name: Patricia Belcher - Movies: ["Flatliners"], Name: Stanley Tucci - Movies: ["Spotlight"], Name: Hope Davis - Movies: ["Flatliners"], Name: Maureen Keiller - Movies: ["Spotlight"], Name: Tim Daly - Movies: ["Diner"], Name: Billy Crudup - Movies: ["Eat Pray Love", "Spotlight"], Name: Steve Guttenberg - Movies: ["Diner"], Name: Hadi Subiyanto - Movies: ["Eat Pray Love"], Name: Mike O 'Malley - Movies: ["Eat Pray Love"], Name: Brian d'Arcy James - Movies: ["Spotlight"], Name: Gene Amoroso - Movies: ["Spotlight"], Name: Rushita Singh - Movies: ["Eat Pray Love"], Name: Julia Roberts - Movies: ["Flatliners", "Eat Pray Love"], Name: Kiefer Sutherland - Movies: ["Flatliners"], Name: Clement Fowler - Movies: ["Diner"], Name: Michael Cyril Creighton - Movies: ["Spotlight"], Name: Kimberly Scott - Movies: ["Flatliners"], Name: Viola Davis - Movies: ["Eat Pray Love"], Name: Sophie Thompson - Movies: ["Eat Pray Love"], Name: Javier Bardem - Movies: ["Eat Pray Love"], Name: Tuva Novotny - Movies: ["Eat Pray Love"], Name: John Lithgow - Movies: ["Footloose"], Name: Laurie Heineman - Movies: ["Spotlight"], Name: Tim Progosh - Movies: ["Spotlight"], Name: Arlene Tur - Movies: ["Eat Pray Love"], Name: Lynne Marta - Movies: ["Footloose"], Name: Jim Youngs - Movies: ["Footloose"], Name: Daniel Stern - Movies: ["Diner"], Name: James Franco - Movies: ["Eat Pray Love"], Name: Gita Reddy - Movies: ["Eat Pray Love"], Name: Jessica James - Movies: ["Diner"], Name: Kelle Kipp - Movies: ["Diner"], Name: Christine Hakim - Movies: ["Eat Pray Love"], Name: Douglas Dirkson - Movies: ["Footloose"], Name: Kathryn Dowling - Movies: ["Diner"], Name: William Baldwin - Movies: ["Flatliners"], Name: Benjamin Mouton - Movies: ["Flatliners"], Name: Sarah Jessica Parker - Movies: ["Footloose"], Name: Lori Singer - Movies: ["Footloose"], Name: Paul Reiser - Movies: ["Diner"], Name: Chris Penn - Movies: ["Footloose"], Name: Oliver Platt - Movies: ["Flatliners"], Name: Michael Keaton - Movies: ["Spotlight"], Name: Mickey Rourke - Movies: ["Diner"], Name: Claudia Cron - Movies: ["Diner"], Name: Richard Jenkins - Movies: ["Eat Pray Love", "Spotlight"], Name: Luca Argentero - Movies: ["Eat Pray Love"]]

Upvotes: 0

matt
matt

Reputation: 534987

You can’t say

actor.filmography.append(self)

until self is fully initialized. That doesn’t happen until its cast property has a value. (And all its other properties too of course.) That fully explains the phenomenon.

Upvotes: 2

Related Questions