Пользователь
22,4
рейтинг
29 ноября 2014 в 22:20

Разработка → Многоликие функции Swift перевод

Русский перевод очень полезной статьи Natasha Murashev The Many Faces of Swift Functions.

Хотя у Objective-C синтаксис выглядит несколько странно, по сравнению с другими языками программирования, синтаксис метода — прост и понятен. Вот небольшой экскурс в прошлое:

+ (void)mySimpleMethod
{
    // метод "класса"
    // нет параметров
    // нет возвращаемых значений
}

- (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2
{
    // метод "экземпляра"
    // первый параметр - типа NSString указатель, 
    // второй параметр  - типа NSNumber указатель
    // должен вернуть значение типа NSString указатель
    return @"hello, world!";
}


В противоположность этому, синтаксис Swift выглядит в большинстве случаев также, как и в других языках программирования, но временами он может быть более сложным и запутанным, чем в Objective-C.

Прежде чем я продолжу, я хочу прояснить разницу между «методами» и «функциями» в Swift, так как я повсеместно буду использовать оба этих термина в этой статье. Вот определение «методов», данное в книге Apple «Swift Programming Language»:



Методы — это функции, которые ассоциируются с определенным «типом». Классы, структуры и перечисления могут определять методы «экземпляра» (instance methods), которые инкапсулируют специфические работы и функциональность для работы с «экземпляром» заданного «типа». Классы, структуры и перечисления могут также определять методы для «типа», которые ассоциируются с «типом» как таковым. Методы «типа» подобны методам «класса» в Objective-C.

Функции автономны, в то время как методы — это функции, встроенные в class, struct или enum.

Анатомия функций Swift



Начнем с простой “Hello, World!” Swift функции:

func mySimpleFunction() {
    println("hello, world!")
}


Если вы когда-нибудь программировали на любом языке, кроме Objective-C, эта функция покажется вам знакомой.

Ключевое слово func обозначает, что это функция.
Имя этой функции — mySimpleFunction .
Этой функции не передаются параметры— так как пусто внутри круглых скобок ( ).
Не возвращается никакое значение.
Исполняемый код функции находится между фигурными скобками { }.
Теперь обратимся к более сложным функциям:

func myFunctionName(param1: String, param2: Int) -> String {
    return "hello, world!"
}


Эта функция берет один параметр с именем param1 типа String и еще один параметр с именем param2 типа Int и возвращает значение типа String.

Вызов всех функций



Одно из существенных различий между Swift и Objective-C заключается в том, как параметры работают при вызове Swift функции. Если вы любите «разговорчивость» Objective-C, как я люблю, имейте в виду, что имена параметров по умолчанию не включены при вызове функции Swift:

func hello(name: String) {
    println("hello \(name)")
}

hello("Mr. Roboto")


Не так плохо, пока вы не захотите добавить еще несколько параметров к вашей функции:

func hello(name: String, age: Int, location: String) {
    println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
}

hello("Mr. Roboto", 5, "San Francisco")


Чтобы просто прочесть hello("Mr. Roboto", 5, "San Francisco") , вам понадобится узнать, какой параметр что означает.
В Swift существует концепция внешних имен параметра для прояснения этой путаницы:

func hello(name name: String) {
    println("hello \(name)")
}

hello(name: "Robot")


Вместо этого, просто добавьте # перед именем параметра для сокращения:

func hello(#name: String) {
    println("hello \(name)")
}

hello(name: "Robot")


И, конечно, правила, по которым работают параметры для функций, немного отличаются от правил для методов

Вызов методов



Если функция встроена в class (или struct, или enum), имя первого параметра метода не включается как внешнее, в то время, как все последующие имена параметров включаются как внешние при вызове метода:

class MyFunClass {

    func hello(name: String, age: Int, location: String) {
        println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
    }

}

let myFunClass = MyFunClass()
myFunClass.hello("Mr. Roboto", age: 5, location: "San Francisco")


Поэтому наилучшей практикой является включение имени первого параметра в имя вашего метода, как в Objective-C:

class MyFunClass {

    func helloWithName(name: String, age: Int, location: String) {
        println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
    }

}

let myFunClass = MyFunClass()
myFunClass.helloWithName("Mr. Roboto", age: 5, location: "San Francisco")


Вместо вызова моей функции “hello”, я переименовала ее в helloWithName, чтобы сделать более понятным имя первого параметра метода.

Если вы по некоторым причинам хотите пропустить внешние имена параметров в вашей функции (я бы рекомендовала делать это только в случае очень существенной причины ), используйте символ _ для внешнего имени параметра:

class MyFunClass {

    func helloWithName(name: String, _ age: Int, _ location: String) {
        println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?")
    }

}

let myFunClass = MyFunClass()
myFunClass.helloWithName("Mr. Roboto", 5, "San Francisco")


Методы «экземпляра» — это каррированные функции



Одна очень замечательная вещь, которую необходимо отметить, это то, что методы «экземпляра» (instance methods) являются в действительности каррированными функциями в Swift:

Основная идея каррирования — это то, что функция может быть применена частично, что означает что некоторые из значений параметров могут быть определены перед тем, как функция вызывается. Частичное применение функции (Partial function application) производит новую функцию.

Итак, у меня есть class:

class MyHelloWorldClass {

    func helloWithName(name: String) -> String {
        return "hello, \(name)"
    }
}


Я могу создать переменную, которая указывает на функцию класса helloWithName:

let helloWithNameFunc = MyHelloWorldClass.helloWithName
// MyHelloWorldClass -> (String) -> String


Моя новая функция helloWithNameFunc является функцией типа MyHelloWorldClass -> (String) -> Sting, которая берет «экземпляр» моего класса и возвращает другую функцию, которая в свою очередь берет значение строки и возвращает значение строки.
Теперь я могу вызвать мою функцию так:

let myHelloWorldClassInstance = MyHelloWorldClass()

helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto")
// hello, Mr. Roboto


Init: Специальные заметки



Специальный метод init вызывается при инициализации class, struct, или enum. В Swift, вы можете определить параметры инициализации, подобно любым другим методам:

class Person {

    init(name: String) {
        // ваша имплиментация  init 
    }

}

Person(name: "Mr. Roboto")


Заметим, что в противоположность другим методам, имя первого параметра init метода всегда требует внешнего имени при получении экземпляра класса.
Хорошей практикой считается добавление отличного от внутреннего, внешнего имени параметра— в нашем случае fromName — чтобы сделать получение экземпляра класса более читабельным:

class Person {

    init(fromName name: String) {
        // your init implementation
    }

}

Person(fromName: "Mr. Roboto")


И, конечно, как с другими методами, вы можете добавить символ _, если хотите, чтобы ваш init метод пропускал внешнее имя параметра. Мне нравится читабельность и мощность следующего примера инициализации из книги «Swift Programming Language»:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0


Пропуск внешнего имени параметра может быть также полезен, если вы хотите абстрагироваться от того, как ваш class / enum / struct инициализируются. Мне очень нравится использование этого в json-swift библиотеке David Owen:

public struct JSValue : Equatable {

    // ... усеченный код

    /// Инициализация нового `JSValue` с `JSArrayType` значением.
    public init(_ value: JSArrayType) {
        self.value = JSBackingValue.JSArray(value)
    }

    /// Инициализация нового `JSValue` с `JSObjectType` значением.
    public init(_ value: JSObjectType) {
        self.value = JSBackingValue.JSObject(value)
    }

    /// Инициализация нового `JSValue` с `JSStringType` значением.
    public init(_ value: JSStringType) {
        self.value = JSBackingValue.JSString(value)
    }

    /// Инициализация нового `JSValue` с `JSNumberType` значением.
    public init(_ value: JSNumberType) {
        self.value = JSBackingValue.JSNumber(value)
    }

    /// Инициализация `JSValue` с `JSBoolType` значением.
    public init(_ value: JSBoolType) {
        self.value = JSBackingValue.JSBool(value)
    }

    /// Инициализация `JSValue` с `Error` значением.
    init(_ error: Error) {
        self.value = JSBackingValue.Invalid(error)
    }

    /// Инициализация `JSValue` с `JSBackingValue` значением.
    init(_ value: JSBackingValue) {
        self.value = value
    }
}


«Особенные» параметры



По сравнению с Objective-C, у Swift имеются дополнительные опции относительно того, какие параметры могут передаваться в функции / методы. Вот несколько примеров.

Параметры типа Optional



В Swift представлена новая концепция типа Optional:

Optionals говорят, что либо “это значение и оно равно x”, либо “значения нет вообще.” Optionals подобны использованию nil с указателями в Objective-C, но они работают для любых типов, а не только для классов. Optionals более безопасны и более выразительны, чем nil указатели в Objective-C, и они являются центром многих мощных возможностей Swift.

Чтобы показать, что данный параметр является Optional (то есть, может быть nil), просто добавьте знак вопроса? после спецификации типа:

func myFuncWithOptionalType(parameter: String?) {
    // function execution
}

myFuncWithOptionalType("someString")
myFuncWithOptionalType(nil)


Когда вы работаете с Optionals, не забывайте его «разворачивать»!

func myFuncWithOptionalType(optionalParameter: String?) {
    if let unwrappedOptional = optionalParameter {
        println("The optional has a value! It's \(unwrappedOptional)")
    } else {
        println("The optional is nil!")
    }
}

myFuncWithOptionalType("someString")
// The optional has a value! It's someString

myFuncWithOptionalType(nil)
// The optional is nil


Если вы пришли с Objective-C, вам потребует некоторое время на адаптацию при работе с Optionals!

Параметры с Default значениями



func hello(name: String = "you") {
    println("hello, \(name)")
}

hello(name: "Mr. Roboto")
// hello, Mr. Roboto

hello()
// hello, you


Заметим, что параметр со значением по умолчанию (default) автоматически имеет внешнее имя.

Так как параметры с default значением могут быть пропущены при вызове функции, хорошей практикой является размещение всех параметров с default значениями в конце списка параметров. Вот цитата из книги «Swift Programming Language» на эту тему:
Размещайте параметры с default значениями в конце списка параметров функции. Это гарантирует, что все обращения к функции используют один и тот же порядок параметров для их не default аргументов. Следовательно, во всех случаях вызывается одна и та же функция.

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

Variadic параметры (переменное число параметров)



Variadic параметры — это просто более читаемая версия передачи массива элементов. Фактически, если вы посмотрите на тип внутреннего параметра name в нижеприведенном примере, вы увидите, что он имеет тип [String]:

func helloWithNames(names: String...) {
    for name in names {
        println("Hello, \(name)")
    }
}

// 2 names
helloWithNames("Mr. Robot", "Mr. Potato")
// Hello, Mr. Robot
// Hello, Mr. Potato

// 4 names
helloWithNames("Batman", "Superman", "Wonder Woman", "Catwoman")
// Hello, Batman
// Hello, Superman
// Hello, Wonder Woman
// Hello, Catwoman


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

func helloWithNames(names: String...) {
    if names.count > 0 {
        for name in names {
            println("Hello, \(name)")
        }
    } else {
        println("Nobody here!")
    }
}

helloWithNames()
// Nobody here!


Другое замечание относительно variadic параметров: variadic параметр должен быть последним параметром в вашем списке параметров!

Inout параметры



С inout параметрами мы получаем возможность манипулировать внешними переменными, переданными по ссылке:

var name1 = "Mr. Potato"
var name2 = "Mr. Roboto"

func nameSwap(inout name1: String, inout name2: String) {
    let oldName1 = name1
    name1 = name2
    name2 = oldName1
}

nameSwap(&name1, &name2)

name1
// Mr. Roboto

name2
// Mr. Potato


Это очень обычный паттерн в Objective-C для сценария управления ошибками (errors).

NSJSONSerialization является одним из таких примеров:

- (void)parseJSONData:(NSData *)jsonData
{
    NSError *error = nil;
    id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];

    if (!jsonResult) {
        NSLog(@"ERROR: %@", error.description);
    }
}


Так как Swift — еще очень молодой язык программирования, поэтому пока отсутствуют ясные соглашения по обработке ошибок, но, определенно, есть много других способов помимо inout параметров! Взгляните на недавний блог-пост об обработке ошибок в Swift David Owen. Много материала на эту тему в книге «Functional Programming in Swift».

Generic параметры



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

func valueSwap<T>(inout value1: T, inout value2: T) {
    let oldValue1 = value1
    value1 = value2
    value2 = oldValue1
}

var name1 = "Mr. Potato"
var name2 = "Mr. Roboto"

valueSwap(&name1, &name2)

name1 // Mr. Roboto
name2 // Mr. Potato

var number1 = 2
var number2 = 5

valueSwap(&number1, &number2)

number1 // 5
number2 // 2


Для получения большей информации относительно generics, я рекомендую взглянуть на раздел «Generics» в книге Apple «Swift Programming Language».

Параметры — переменные



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

var name = "Mr. Roboto"

func appendNumbersToName(var name: String, #maxNumber: Int) -> String {
    for i in 0..<maxNumber {
        name += String(i + 1)
    }
    return name
}

appendNumbersToName(name, maxNumber:5)
// Mr. Robot12345

name
// Mr. Roboto


Заметьте, что это совсем другое, чем inout параметр — переменные параметры не изменяют внешне переданную переменную!

Функции в качестве параметров



В Swift функции могут быть передаваться как обычные переменные. Например, функция может иметь другую функцию в качестве переданного параметра:

func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
    let luckyNumber = Int(arc4random() % 100)
    return lotteryHandler(name, luckyNumber)
}

func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
    return "\(name), your lucky number is \(luckyNumber)"
}

luckyNumberForName("Mr. Roboto", lotteryHandler: defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38


Заметим, что в качестве параметра передается только ссылка на функцию — в нашем случае на функцию defaultLotteryHandler. Функция выполняется позже, когда решит функция, которой передан этот параметр.

Методы «экземпляра» можно передать тем же путем:

func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
    let luckyNumber = Int(arc4random() % 100)
    return lotteryHandler(name, luckyNumber)
}

class FunLottery {

    func defaultLotteryHandler(name: String, luckyNumber: Int) -> String {
        return "\(name), your lucky number is \(luckyNumber)"
    }

}

let funLottery = FunLottery()
luckyNumberForName("Mr. Roboto", lotteryHandler: funLottery.defaultLotteryHandler)
// Mr. Roboto, your lucky number is 38


Для того, чтобы сделать определение функции немного более читаемым, рассмотрим создание алиаса типа (type-aliasing) для нашей функции (подобно typedef в Objective-C):

typealias lotteryOutputHandler = (String, Int) -> String

func luckyNumberForName(name: String, #lotteryHandler: lotteryOutputHandler) -> String {
    let luckyNumber = Int(arc4random() % 100)
    return lotteryHandler(name, luckyNumber)
}


В качестве типа параметра может быть также функция без имени (подобно блоку в Objective-C):

func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String {
    let luckyNumber = Int(arc4random() % 100)
    return lotteryHandler(name, luckyNumber)
}

luckyNumberForName("Mr. Roboto", lotteryHandler: {name, number in
    return "\(name)'s' lucky number is \(number)"
})
// Mr. Roboto's lucky number is 74


В Objective-C использование блоков в качестве параметров является очень популярным для управления завершением (completion handler) и управления ошибками ( error handler) в методах с асинхронными операциями. Это также будет популярно и в Swift.

Управление доступом (Access Controls)



Swift имеет три уровня управления доступом:

Public доступ делает возможным использование сущностей внутри любого файла с исходным кодом из модуля, в котором эти сущности определены, а также в любом файле с исходным кодом из другого модуля, который импортирует модуль, в котором эти сущности определены. Обычно вы используете public доступ при спецификации public интерфейса для framework.
Internal доступ делает возможным использование сущностей внутри любого файла с исходным кодом из модуля, в котором эти сущности определены, но ни в каком другом файле с исходным кодом за пределами модуля, в котором они определены. Обычно вы используете internal доступ при определении внутренней структуры приложения или framework.
Private доступ ограничивает использование сущности файлом с исходным кодом, в котором определена эта сущность. Использование private доступа скрывает детали реализацию специфического куска функциональности.
По умолчанию каждая функция и переменная являются internal — если вы хотите это изменить, вы должны использовать ключевое слово private или public перед каждым отдельным методом или переменной:

public func myPublicFunc() {

}

func myInternalFunc() {

}

private func myPrivateFunc() {

}

private func myOtherPrivateFunc() {

}


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

class MyFunClass {

    func myInternalFunc() {

    }

    // MARK: Private Helper Methods

    private func myPrivateFunc() {

    }

    private func myOtherPrivateFunc() {

    }
}


Надеюсь, что будущие релизы Swift будут включать опцию использования одного ключевого слова private для индикации всех нижерасположенных методов как private, подобно тому как управление доступом (access controls) работает в других языках программирования.

«Особенные» типы возвращаемых значений



В Swift возвращаемые функцией типы и значения могут быть немного более сложными, чем мы используем в Objective-C, особенно с введением в употребление Optionals и множественных типов возвращаемых значений.

Optional возвращаемые типы



Если есть вероятность, что ваша функция может вернуть nil, вам нужно определить возвращаемый тип как Optional:

func myFuncWithOptonalReturnType() -> String? {
    let someNumber = arc4random() % 100
    if someNumber > 50 {
        return "someString"
    } else {
        return nil
    }
}

myFuncWithOptonalReturnType()


И, конечно, если вы используете Optional возвращаемое значение, то не забудьте его «развернуть»:

let optionalString = myFuncWithOptonalReturnType()

if let someString = optionalString {
    println("The function returned a value: \(someString)")
} else {
    println("The function returned nil")
}


Наилучшее объяснение Optionals, которое я видела, было взято из твиттера @Kronusdark:
Я наконец получил @SwiftLang optionals, которые напоминают кота Шредингера! Вы должны посмотреть живой ли кот прежде, чем вы его будете использовать.


Множество возвращаемых значений



Одна из наиболее впечатляющих возможностей Swift — это возможность функции иметь множество возвращаемых значений:

func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int) {

    var min = numbers[0]
    var max = numbers[0]

    for number in numbers {
        if number > max {
            max = number
        }

        if number < min {
            min = number
        }
    }

    return (min, max)
}

findRangeFromNumbers(1, 234, 555, 345, 423)
// (1, 555)


Как мы видим, множество возвращаемых значений возвращается в виде кортежа (tuple), очень простой структуры сгруппированных значений. Существует два способа использования множество возвращаемых значений в кортеже (tuple):

let range = findRangeFromNumbers(1, 234, 555, 345, 423)
println("From numbers: 1, 234, 555, 345, 423. The min is \(range.min). The max is \(range.max).")
// From numbers: 1, 234, 555, 345, 423. The min is 1. The max is 555.

let (min, max) = findRangeFromNumbers(236, 8, 38, 937, 328)
println("From numbers: 236, 8, 38, 937, 328. The min is \(min). The max is \(max)")
// From numbers: 236, 8, 38, 937, 328. The min is 8. The max is 937


Множество возвращаемых значений и Optionals



Запутанность при возвращении множества значений возникает, если возвращаемые значения могут быть Optional , но есть два способа управления этой ситуацией.

Моя логика дает «осечку» относительно представленной выше функции — если на вход не будет ничего передано, то моя программа закончится аварийно.
Если значения на вход функции не передаются, то я могу сделать возвращаемое значение целиком Optional:

 func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int)? {

    if numbers.count > 0 {

        var min = numbers[0]
        var max = numbers[0]

       for number in numbers {
            if number > max {
                max = number
            }

            if number < min {
                min = number
            }
        }

        return (min, max)
    } else {
        return nil
    }
}

if let range = findRangeFromNumbers() {
    println("Max: \(range.max). Min: \(range.min)")
} else {
    println("No numbers!")
}
// No numbers!


В других случаях, имеет смысл сделать каждое отдельное значение в кортеже (tuple) Optional, вместо того, чтобы делать Optional весь кортеж целиком:

func componentsFromUrlString(urlString: String) -> (host: String?, path: String?) {
    let url = NSURL(string: urlString)
    return (url.host, url.path)
}


Если вы решили, что некоторые из значений вашего кортежа являются Optional, становится немного труднее их «разворачивать», так как вам нужно рассматривать отдельно каждую комбинацию Optional значений:

let urlComponents = componentsFromUrlString("http://name.com/12345;param?foo=1&baa=2#fragment")

switch (urlComponents.host, urlComponents.path) {
case let (.Some(host), .Some(path)):
    println("This url consists of host \(host) and path \(path)")
case let (.Some(host), .None):
    println("This url only has a host \(host)")
case let (.None, .Some(path)):
    println("This url only has path \(path). Make sure to add a host!")
case let (.None, .None):
    println("This is not a url!")
}
// This url consists of host name.com and path /12345


Видите? Это не так просто, как в Objective-C !

Возвращение функции



В Swift любая функция может вернуть функцию:

func myFuncThatReturnsAFunc() -> (Int) -> String {
    return { number in
        return "The lucky number is \(number)"
    }
}

let returnedFunction = myFuncThatReturnsAFunc()

returnedFunction(5) // The lucky number is 5


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

typealias returnedFunctionType = (Int) -> String

func myFuncThatReturnsAFunc() -> returnedFunctionType {
    return { number in
        return "The lucky number is \(number)"
    }
}

let returnedFunction = myFuncThatReturnsAFunc()

returnedFunction(5) // The lucky number is 5


Вложенные функции



В этом посте мы не будем развивать эту тему, но полезно знать, что в Swift у вас может быть функция внутри функции:

func myFunctionWithNumber(someNumber: Int) {

    func increment(var someNumber: Int) -> Int {
        return someNumber + 10
    }

    let incrementedNumber = increment(someNumber)
    println("The incremented number is \(incrementedNumber)")
}

myFunctionWithNumber(5)
// The incremented number is 15


end
У Swift функций есть множество опций и огромная мощность. Если вы начали писать на Swift, помните: с большой силой приходит большая ответственность. Делайте оптимизацию кода преимущественно для ЧИТАБЕЛЬНОСТИ, а не для ловкости и хитроумия!

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

Успешного кодирования в Swift!
Перевод: Natasha Murashev
@WildGreyPlus
карма
30,0
рейтинг 22,4
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (27)

  • +15
    Поставил плюс вашей статье за перевод, но вот не понимаю тягость современных программистов-блоггеров к пересказу документации. Apple выпустила на столько детализированную и лаконичную книгу, что она читается за 2 часа. Кроме того, она так отлично разбита на главы, что часто можно просто перейти к интересующей главе. Но не смотря на это выходит какое-то невообразимое количество статей в англоязычном сегменте о том как сделать сабкласс на Swift, let и var в Swift, коллекции в Swift.
    • –2
      Я бы не отнесла эту статью к разряду просто пересказа документации. В документации все есть, но в разных местах: часть — в Functions, часть (метод init) — в Classes and Structures, Enumerations. Для тех, кто пришел с Objective C, где все было просто, есть некоторая путаница, когда в методах нужно внешнее имя, а когда — нет. Здесь она собрала все в одном месте. Мне показалось это полезным. Кроме того, следует обратить внимание на автора поста Natasha Murashev — у нее очень интересные посты — короткие и по делу. И, вообще, она сейчас «звезда» Swift сообщества и выступает на всех Swift мероприятиях.
  • 0
    Другое замечание относительно variadic параметров: variadic параметр должен быть последним параметром в вашем списке параметров!

    Впервые читаю про синтаксис Swift'а и на этом месте сделал грустишку. Простейший для реализации синтаксиса момент, почему его игнорируют во многих языках? В этом плане впервые порадовало Ruby (у кого-то могло быть иначе), конечно. Представим себе прототип функции (абстрактного языка в вакууме) — «func(param1, param2, ...variadic_param..., param4, param5)», вычленить массив параметров в рамках variadic_param проще пареной репы — взяли массив всех параметров, убрали два именованных спереди и сзади — остальное variadic. Ну и запрет на два variadic подряд по вполне понятным причинам и прежде всего из-за логики.
    • +1
      Предлагаемая вами фича не сочетается со значениями аргументов по умолчанию и некоторыми другими фичами. Функции с переменным числом аргументов даже при текущих ограничениях способны приводить к неоднозначным и неочевидным ситуациям при перегрузке функций.
      • 0
        Да, с перегрузкой согласен. Только что коллега-рубироид подсказал, что в рубях при этом нет перегрузки в принципе.
    • +1
      Swift — язык молодой (декларирован 4 июня 2014 года на WWDC — 2014) и пока все время меняется, даже концептуально. Есть проблемы и ограничения, но Apple team их устраняет от релиза к релизу. Пока, конечно, много критики, но уверена — все сделают в «лучшем » виде, хотя и не сразу.
  • +1
    Меня мучает вопрос: как в Swift сделать функцию, которая возвращает функцию которая возвращает функцию?
    • +1
      func add (a1:Int, a2:Int) ->Int {return (a1 + a2)}
      func multiply (a1:Int, a2:Int) ->Int {return (a1 * a2)}
      
      func funcReturnFunc (funcOperation: (Int,Int) -> Int) -> (Int, Int) ->Int {
         
          return funcOperation
      }
      
      func funcReturnFunc2 (funcOperation: (Int,Int) -> Int) -> (Int, Int) ->Int {
          return funcReturnFunc(funcOperation)
      }
      
      let returnedFunction = funcReturnFunc2 (multiply)
      let returnedFunction1 = funcReturnFunc2 (add)
      
      
      returnedFunction(5,6)    // Получим 30 на playground
      returnedFunction1(5,6)  // Получим 11 на playground
      
      <source>
      
      • 0
        Вы сделали функцию, которая возвращает функцию. Как это сделать в книжке в примерах пишут. А нужна функция, которая возвращает функцию, и каковая тоже возвращает функцию. Не вижу как синтаксически это оформить без мудреного typealias.

        Извините за мой JavaScript:

        var test = function (i) {
            return function (j) {
                return function (k) {
                  // oh finally!
                  return i + j + k;
                };
            };
        };
        
        test(1)(2)(3); // == 6
        
        • 0
          Вы имели ввиду каррированные функции ( curried functions).
          Их можно реализовать в Swift двумя способами:

          func add (i:Int, j:Int, k:Int, l:Int) -> Int {return(i + j + k + l)}
          
          // 1-  ый метод
          
          func curry<A,B,C,D,R>(f: (A,B,C,D) -> R) -> A -> B -> C -> D -> R {
              return { a in { b in { c in { d in f(a,b,c,d) } } } }
          }
          
          let sum4 = curry (add )
          
          // 2-  ый метод (curried function in Swift)
          
          func sum4Swift(i: Int)(j:Int)(k: Int)(l:Int) -> Int {
              return add(i, j, k, l)
          }
          
          
          sum4 (1)(2)(3)(5)                     // playground 11
          sum4Swift (1)(j: 2)(k: 3)(l: 5)    // playground 11
          

      • +1
        Однако сейчас все получается.

        func chained(i: Int) -> (Int) -> (Int) -> Int {
            return { (j: Int) -> (Int) -> Int in
                return { (k: Int) -> Int in
                    return i + j + k;
                }
            }
        }
        
        chained(1)(2)(3)
        
        
        • +3
          Можно так:

          func chained (i:Int) -> Int-> Int -> Int {
              return { j in
                  return { k in
                      return i + j + k;
                  }
              }
          }
          
          chained(5)(6)(7)  // playground 18
          
          • +1
            Так гораздо лучше. Спасибо за это.
  • +2
    Проблема встроенных optionals в том, что для них нет монадических комбинаторов. Но уже есть swiftz :)
    • 0
      «Магическое» будущее Swift описано в замечательных статьях ALEXANDROS SALAZAR The Culmination: Final Part (есть перевод «Кульминация — финальная часть.),
    • +1
      Их проблема ещё и в том, что один и тот же терм может иметь разный тип, например 5 — это может быть и Int и Int?. В теории типов, вообще говоря, каждый терм должен иметь один тип. Неспроста в других языках, более уважающих теорию типов, значения в опционалах засовывают во что-то другое, например Some(5) в Rust или Just 5 в Haskell. В этом случае понятно, как доставать что-то из Option с помощью pattern-matching'а без необходимости создания специального синтаксиса для работы с optionals. Скажем тот же if let в Rust более функционален чем в Swift. В Swift он работает только с опционалами, а в Rust с чем угодно:
      if let Some(x) = y ...
      
      Этот код извлечёт значение из Option и присвоит его переменной x, если в этом Option есть значение. Но с помощью того-же if let можно сделать например так:
      if let (x, true) = y ...
      
      — извлечь первый элемент из tuple, если второй равен true.

      Тот путь, по которому пошёл Swift, вынуждает создавать специальный синтаксис для работы с optionals. Ещё это создаёт определённые проблемы при выводе типов.

      Если говорить про монады, то Some/Just является одной из функций необходимых для определения монады, тот самый return в Haskell.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Ну к этому можно привыкнуть, зато как удобно искать методы в Xcode

      <img src="" alt=«image»/>
      • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        Sorry, это я действительно не в тему.
        Но вас-то что раздражает?

        Методы выглядят абсолютно одинаково, что в Swft, что в Objective-C

           override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { } //Swift
        
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {} // Objective-C
        
          override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {} //Swift
        
        - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {} //Objective-C
        <source>
        
        
        • 0
          Методы в Obj-C очень литературные, читается легко и понятно.
          В Swift возникает мешанина скобок и двоеточий, которая не сказать прямо что помогает.
          • 0
            Согласна. Но сейчас держит Cocoa Touch, написанная на Objective-C: весь API оттуда. Если перепишут Cocoa Touch на Swift, то, возможно, будет другой API.
            • 0
              а планируют ли вообще переписывать, не знаете случайно?
              • 0
                Конечно, не знаю. Но думаю, что будут, так как слишком много «некрасивостей» в Swift вылезает из Cocoa Touch на Objective-C.
                • 0
                  ну может встречали где-то в блогах инфу такую )
                  ну что-же, нам остается только надеяться и ждать.
                  • 0
                    А зачем ждать? Все работает. Есть книжка «Using Swift with Cocoa and Objective-C». Связи Swift c Objective-C отработаны более- менее. Весь Cocoa Touch API подстроен для доступа из Swift. Вперед. Ну а эстетика может подождать. Сам по себе Swift — замечательный.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.