[Перевод] Многоликие функции Swift
Русский перевод очень полезной статьи Natasha MurashevThe 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, но покажу очень простой пример функции, которая принимает параметры различных типов, но при этом оба параметра имеют один и тот же тип : Для получения большей информации относительно generics, я рекомендую взглянуть на раздел «Generics» в книге Apple «Swift Programming Language».
Параметры — переменные По умолчанию параметры, передаваемые в функцию, являются константами, следовательно ими нельзя манипулировать в области действия функции. Если вы хотите изменить такое поведение, просто используйте ключевое слово var:
var name = «Mr. Roboto»
func appendNumbersToName (var name: String, #maxNumber: Int) → String {
for i in 0… appendNumbersToName (name, maxNumber:5)
// Mr. Robot12345 name
// Mr. Roboto
Заметьте, что это совсем другое, чем inout Методы «экземпляра» можно передать тем же путем:
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!
параметр — переменные параметры не изменяют внешне переданную переменную!
Функции в качестве параметров
В Swift функции могут быть передаваться как обычные переменные. Например, функция может иметь другую функцию в качестве переданного параметра: Заметим, что в качестве параметра передается только ссылка на функцию — в нашем случае на функцию defaultLotteryHandler. Функция выполняется позже, когда решит функция, которой передан этот параметр.