Как сделать так, чтобы класс, переданный в качестве параметра, мог быть проанализирован из строки в Swift? - PullRequest
0 голосов
/ 09 января 2019

Я пытаюсь представить вызов функции, чтобы я мог создать небольшой язык сценариев для создания игр. Сейчас я просто пытаюсь настроить интерфейсы между всеми протоколами и классами, которые мне нужны. У меня есть класс, FunctionCall<T>. У него есть метод execute(), который выполняет текущую функцию и возвращает экземпляр типа T?. FunctionCall также имеет массив экземпляров типа FunctionCall для представления любых параметров. Он также имеет поле stringRepresentation, которое представляет собой строковое представление вызова функции, которое вводит пользователь. Эта строка может быть что-то вроде createNode(named: myCircle) или, в базовом случае, это может быть просто литерал, как myCircle. Для ясности, myCircle в этом случае - String. Я, вероятно, решу не использовать кавычки вокруг String s на моем небольшом языке сценариев.

Моя проблема связана с методом execute(). Я хочу вернуть экземпляр типа T. До сих пор я думал о том, чтобы принудительно установить, что T соответствует протоколу (назовем его Parseable), который обеспечивает наличие у него метода, который принимает String и возвращает экземпляр типа T. Проблема, которую я обнаружил при таком подходе, заключается в том, что у меня нет способа создать такой метод, потому что у меня нет способа ссылаться на тип, который будет реализовывать протокол изнутри протокола. Другими словами, если T равен SKShapeNode, у меня нет никакого способа или ссылки на SKShapeNode изнутри Parseable, так что я могу указать, что тип возвращаемого значения должен быть SKShapeNode. Другой подход, который я нашел, заключается в том, чтобы Parseable имел требуемый инициализатор, который занимает String. Это работает, когда структуры реализуют протокол, но не с классами. Проблема, которую я получаю, когда пытаюсь реализовать протокол в классе, состоит в том, что класс хочет, чтобы я сделал инициализатор required, но я не могу этого сделать, потому что не могу поместить инициализатор required в расширение.

Я хочу, чтобы класс FunctionCall выглядел примерно так

class FunctionCall<T: Parseable> {
    var parameters = [FunctionCall]()

    var stringRepresentation: String!

    init(stringRepresentation: String) {
        self.stringRepresentation = stringRepresentation
    }

    func execute() -> T? {
        guard let indexOfFirstLeftParen = stringRepresentation.firstIndex(of: "("),
            let indexOfLastRightParen = stringRepresentation.lastIndex(of: ")") else {
                // then this is a literal value, because no function is being called
                return T(string: stringRepresentation)
        }

        // TODO: implement

        return nil
    }
}

1 Ответ

0 голосов
/ 09 января 2019

Проблема, с которой я столкнулся при таком подходе, заключается в том, что у меня нет способа создать такой метод, потому что у меня нет способа ссылаться на тип, который будет реализовывать протокол из протокола.

На самом деле эту проблему можно решить с помощью Self, который относится к какому-либо соответствующему типу:

// I think this is a better name
protocol ConvertibleFromString {
    static func from(string: String) -> Self?
}

// implementation
extension Int : ConvertibleFromString {
    static func from(string: String) -> Int? {
        return Int(string)
    }
}

Для нефинальных классов вы должны создать окончательный подкласс, подобный этому:

final class SKSpriteNodeFinal : SKSpriteNode, ConvertibleFromString {
    static func from(string: String) -> SKSpriteNodeFinal? {
        ...
    }
}

Обратите внимание, что это предотвращает использование вашего протокола в качестве типа переменной:

var foo: ConvertibleFromString? = nil // error

Но я не думаю, что это будет проблемой, так как вы используете ConvertibleFromString в качестве общих ограничений, что нормально.

...