Подтвердить что ты не робот

Каково практическое использование вложенных функций в swift?

Каково практическое использование вложенных функций? Это только затрудняет чтение кода и не облегчает конкретный случай.

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backwards ? stepBackward : stepForward
}

Источник

4b9b3361

Ответ 1

Я думаю, что суть вашего вопроса: почему бы не использовать закрытую функцию вместо уродливой вложенной функции?

Проще говоря, вложенные функции могут облегчить чтение и инкапсуляцию.

Читаемость

Доступ к закрытой функции по-прежнему можно получить из других функций вашего класса. То же самое не относится к вложенным функциям. Вы говорите своим разработчикам, что это относится только к этой функции (в отличие от частных функций, где они принадлежат всему классу). Отойдите и не связывайтесь с этим, если вам нужна подобная возможность, пишите свой!

В тот момент, когда вы видите приватную функцию, вы должны подумать, какая функция ее вызовет. Это первая функция или последняя? Позвольте мне искать это. Тем не менее, с вложенной функцией вам не нужно смотреть вверх и вниз. Уже известно, какая функция будет вызывать его.

Кроме того, если у вас есть 5 закрытых функций, из которых 3 из них вызываются в одной общедоступной функции, то, вкладывая их в одну и ту же общедоступную функцию, вы сообщаете другим разработчикам, что эти частные функции связаны.

Короче говоря, точно так же, как вы не хотите загрязнять свое публичное пространство имен, вы не хотите загрязнять свое личное пространство имен.

Инкапсуляция

Другое удобство заключается в том, что он может получить доступ ко всем локальным параметрам для своей родительской функции. Вам больше не нужно передавать их. В конечном итоге это означало бы на одну функцию меньше для тестирования, потому что вы поместили одну функцию в другую. Кроме того, если вы вызываете эту функцию в блоке не вложенной функции, вам не нужно заключать ее в self или думать о создании утечек. Это потому, что жизненный цикл вложенной функции связан с жизненным циклом ее содержащей функции.

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

extractAllHebrewNames() // right to left language
extractAllAmericanNames() // left to right language
extractAllJapaneseNames() // top to bottom language

Теперь, если у вас есть личный функционал с именем printName (который печатает имена), он будет работать, если вы переключите регистр в зависимости от языка, но что если вы просто не/не можете этого сделать. Вместо этого вы можете написать свои собственные вложенные функции (теперь они могут иметь одно и то же имя. Потому что каждая из них находится в отдельном пространстве имен.) Внутри каждой отдельной функции извлечения и печатать имена.

Ваш вопрос несколько схож с тем, почему бы не использовать "если еще" вместо "Переключить регистр".

Я думаю, что это просто удобство (для чего-то, что может быть решено без использования вложенных функций).

ПРИМЕЧАНИЕ. Вложенная функция должна быть написана до ее вызова в функции.

Ошибка: использование локальной переменной "вложенной" перед ее объявлением

func doSomething(){
    nested()

    func nested(){

    }
}

Нет ошибок:

func doSomething(){
    func nested(){

    }

    nested()
}

Аналогично, локальная переменная, используемая во вложенной функции, должна быть объявлена перед вложенной функцией


ВНИМАНИЕ:

Если вы используете вложенные функции, то компилятор не будет применять проверки self.

Компилятор недостаточно умен. Это НЕ выдаст ошибку:

Call to method 'doZ' in closure requires explicit 'self.' to make capture semantics explicit 

Это может привести к трудностям поиска утечек памяти. например,

class P {
    var name: String

    init(name: String) {
        print("p was allocated")
        self.name = name
    }

    func weaklyNested() {
        weak var _self = self
        func doX() {
            print("nested:", _self?.name as Any)
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            doX()
        }
    }

    func stronglyNested() {
        func doZ() {
            print("nested:", name)
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            doZ() // will NOT throw error of: 'Call to method 'doZ' in closure requires explicit 'self.' to make capture semantics explicit'
        }
    }

    deinit {
        print("class P was deinitialized")
    }
}

class H {
    var p: P

    init(p: P) {
        self.p = p
    }
}

var h1: H? = H(p: P(name: "john"))
h1?.p.weaklyNested()
h1 = nil // will deallocate immediately, then print nil after 2 seconds

var h2: H? = H(p: P(name: "john"))
h2?.p.stronglyNested()
h2 = nil // will NOT deallocate immediately, will print "john" after 2 seconds, then deallocates

tl;dr решение:

  • используйте weak var _self = self и внутри ссылки на вложенную функцию на self со ссылкой weak.
  • вообще не используйте вложенные функции :( или не используйте переменные экземпляра внутри вложенных функций.

Эта часть была написана благодаря этому оригинальному сообщению от . Собирается ли само себя во вложенной функции?

Ответ 2

Одним из вариантов использования является работа над рекурсивными структурами данных.

Рассмотрим, например, этот код для поиска в двоичных деревьях поиска:

func get(_ key: Key) -> Value? {
    func recursiveGet(cur: Node) -> Value? {
        if cur.key == key {
            return cur.val
        } else if key < cur.key {
            return cur.left != nil ? recursiveGet(cur: cur.left!) : nil
        } else {
            return cur.right != nil ? recursiveGet(cur: cur.right!) : nil
        }
    }

    if let root = self.root {
        return recursiveGet(cur: root)
    } else {
        return nil
    }
}

Конечно, вы можете преобразовать рекурсию в цикл, избавившись от вложенной функции. Тем не менее, я нахожу рекурсивный код часто более ясным, чем итеративные варианты. (Сделка по сравнению с эксплуатационной стоимостью!)

Вы также можете определить recursiveGet как (частный) элемент вне get, но это будет плохой дизайн (если recursiveGet не используется в нескольких методах).

Ответ 3

В вашем примере вы можете создать variable, который также является function следующим образом:

var myStepFunction = chooseStepFunction(true)

myStepFunction(4)

Преимущество вложенной функции очень приятно. Например, скажем, вы создаете приложение для калькулятора, вы можете использовать всю свою логику в одной функции, как показано ниже:

func doOperation(operation: String) -> ((Double, Double) -> Double)? {
    func plus(s: Double, d: Double) -> Double {
        return s + d
    }
    func min(s: Double, d: Double) -> Double{
        return s - d
    }
    switch operation {
        case "+":
            return plus
    case "-" :
        return min
    default :
        return nil
    }
}

var myOperationFunction = doOperation("-")?(4, 4) // 0
var myOperationFunction2 = doOperation("+")?(4, 5) //9

В некоторых случаях вам не разрешается видеть реализацию какой-либо функции или не несет ответственности за них. Тогда хэширование их внутри другой функции - действительно хороший подход. Например, предположим, что ваш коллега реанимирован для разработки функций plus и min, он/она сделает это, и вы просто используете внешнюю функцию.

Отложено от закрытия, потому что в clouser вы передаете свою логику другой логике, которая будет называться вашей. Это своего рода плагин. Например, после вызова http-запроса вы можете передать код, который хотите, чтобы ваше приложение выполняло при получении ответа с сервера

Ответ 4

Существует принцип (поскольку Objective-C), что "код может быть данными". Вы можете передать код, сохранить его в переменной, объединить его с другим кодом. Это чрезвычайно мощный инструмент. Честно говоря, если бы у меня не было возможности рассматривать код как данные, большая часть кода, который я пишу, была бы в десять раз сложнее писать и в десять раз сложнее читать.

Функции в Swift - это просто замыкания, поэтому вложенные функции имеют смысл, так как вы пишете закрытие, когда не хотите использовать одно из многих доступных ярлыков.