📜 ⬆️ ⬇️

Swift Multiple Functions

Russian translation of the very useful article Natasha Murashev The Many Faces of Swift Functions .

Although the Objective-C syntax looks a bit strange, compared to other programming languages, the method syntax is simple and straightforward. Here is a brief excursion into the past:

+ (void)mySimpleMethod { //  "" //   //    } - (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2 { //  "" //   -  NSString , //   -  NSNumber  //     NSString  return @"hello, world!"; } 

')
In contrast, Swift syntax looks in most cases as well as in other programming languages, but at times it can be more complicated and confusing than in Objective-C .

Before I continue, I want to clarify the difference between “methods” and “functions” in Swift , as I’ll use both of these terms throughout this article. Here is the definition of “methods” given in Apple’s Swift Programming Language :



Methods are functions that are associated with a particular “type”. Classes, structures, and enumerations can define “instance” methods that encapsulate specific works and functionality for working with an “instance” of a given “type”. Classes, structures, and enumerations can also define methods for “type” that are associated with “type” as such. “Type” methods are similar to “class” methods in Objective-C.

Functions are autonomous, while methods are functions embedded in a class, struct or enum .

Anatomy of Swift Functions



Let's start with a simple “Hello, World!” Swift function:

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


If you have ever programmed in any language other than Objective-C , this function will seem familiar to you.

The func keyword is a function.
The name of this function is mySimpleFunction .
No parameters are passed to this function — because it is empty inside parentheses ().
No value is returned.
The executable function code is between curly brackets {}.
Now we turn to more complex functions:

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


This function takes one parameter named param1 type String and another parameter named param2 type Int and returns a value of type String .

Call all functions



One of the significant differences between Swift and Objective-C is how parameters work when the Swift function is called. If you like Objective-C "talkative" as I like, keep in mind that parameter names are not included by default when calling the Swift function:

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


Not so bad, until you want to add some more parameters to your function:

 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") 


To simply read hello("Mr. Roboto", 5, "San Francisco") , you will need to know what the parameter means.
In Swift, there is the concept of external parameter names to clarify this confusion:

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


Instead, simply add # in front of the parameter name to abbreviate:

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


And, of course, the rules by which the parameters for functions work differ slightly from the rules for methods ...

Method call



If the function is built into a class (or struct , or enum ), the name of the first parameter of the method is not included as external , while all subsequent parameter names are included as external when the method is called:

 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") 


Therefore, it is best practice to include the name of the first parameter in the name of your method, as in 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") 


Instead of calling my “hello” function, I renamed it helloWithName to make the name of the first parameter of the method more understandable.

If for some reason you want to omit the external parameter names in your function (I would recommend doing this only for very significant reasons), use the _ symbol for the external parameter name:

 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 are curried functions.



One very remarkable thing to note is that the “instance methods” are actually curried functions in Swift :

The basic idea of ​​currying is that the function can be partially applied, which means that some of the parameter values ​​can be defined before the function is called. Partial function application produces a new function.

So, I have a class:

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


I can create a variable that points to a function of the helloWithName class:

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


My new helloWithNameFunc function is a function of type MyHelloWorldClass -> (String) -> Sting , which takes an “instance” of my class and returns another function, which in turn takes a string value and returns a string value.
Now I can call my function like this:

 let myHelloWorldClassInstance = MyHelloWorldClass() helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto") // hello, Mr. Roboto 


Init : Special Notes



The special init method is called when the class, struct , or enum is initialized. In Swift , you can define initialization parameters, like any other method:

 class Person { init(name: String) { //   init } } Person(name: "Mr. Roboto") 


Note that, in contrast to other methods, the name of the first parameter of the init method always requires an external name when obtaining an instance of a class.
A good practice is to add a parameter other than the internal, external name — in our case fromName — to make getting an instance of the class more readable:

 class Person { init(fromName name: String) { // your init implementation } } Person(fromName: "Mr. Roboto") 


And, of course, as with other methods, you can add the _ character if you want your init method to skip the external name of the parameter. I like the readability and power of the following initialization example from the book 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 


Skipping the external name of a parameter can also be useful if you want to abstract away from how your class / enum / struct initialized. I really like using David Owen’s json-swift library:

 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 } } 


"Special" options



Compared to Objective-C , Swift has additional options regarding which parameters can be passed to functions / methods. Here are some examples.

Parameters of type Optional



Swift introduces a new concept of type Optional :

Optionals say that either “this value and it is equal to x”, or “there is no value at all.” Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just for classes. Optionals are safer and more expressive than nil pointers in Objective-C, and they are the center of many powerful features of Swift.

To show that this parameter is Optional (i.e., maybe nil ), just add a question mark? Following type specification:

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


When you work with Optionals , do not forget to "deploy" it!

 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 


If you come from Objective-C , it will take you some time to adapt when working with Optionals !

Parameters with Default Values



 func hello(name: String = "you") { println("hello, \(name)") } hello(name: "Mr. Roboto") // hello, Mr. Roboto hello() // hello, you 


Note that the parameter with a default value automatically has an external name.

Since parameters with a default value can be skipped when calling a function, it is good practice to place all parameters with default values ​​at the end of the parameter list. Here is a quote from the book "Swift Programming Language" on this topic:
Place the parameters with default values ​​at the end of the function parameter list. This ensures that all function calls use the same order of parameters for their non default arguments. Therefore, in all cases the same function is called.

I’m a big fan of using default parameters, mainly because it makes it easy to change code and provide backward compatibility. You can, for example, start with two parameters that you need at the moment, for example, to configure a custom “table cell” UITableViewCell , and if you need additional configuration when another parameter is needed (for example, a different color for your text label cells), then you simply add a new parameter with a default value - and all other places where this function has already been called can be left unchanged, and you can send a pair to a new part of your code that requires a new parameter. empl with a value other than the default values!

Variadic parameters (variable number of parameters)



Variadic parameters are simply a more readable version of the transmission of an array of elements. In fact, if you look at the type of the internal parameter name in the example below, you will see that it is of the type [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 


Remember that it is possible to transfer 0 values, which corresponds to an empty array, so do not forget to check whether an empty array is being transmitted, if necessary:

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


Another note about the variadic parameters: the variadic parameter should be the last parameter in your parameter list!

Inout parameters



With inout parameters we get the ability to manipulate external variables passed by reference:

 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 


This is a very common Objective-C pattern for the error management script.

NSJSONSerialization is one such example:

 - (void)parseJSONData:(NSData *)jsonData { NSError *error = nil; id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (!jsonResult) { NSLog(@"ERROR: %@", error.description); } } 


Since Swift is still a very young programming language, so there are still no clear error handling conventions, but there are definitely many other ways besides inout parameters! Take a look at Swift's David Owen recent blog post about error handling . A lot of material on this topic in the book "Functional Programming in Swift" .

Generic options



I'm not going to pay too much attention in this post generics , but I’ll show a very simple example of a function that accepts parameters of different types, but at the same time both parameters are of the same type:

 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 


For more information on generics , I recommend taking a look at the “Generics” section in the Apple book, Swift Programming Language.

Parameters - Variables



By default, the parameters passed to the function are constants, therefore they cannot be manipulated in the scope of the function. If you want to change this behavior, simply use the var keyword:

 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 


Notice that this is completely different than the inout parameter — the variable parameters do not change the externally passed variable!

Functions as parameters



In Swift, functions can be passed as regular variables. For example, a function may have another function as a passed parameter:

 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 


Note that only the function reference is passed as a parameter - in our case, the defaultLotteryHandler function. The function is executed later when the function that the parameter is passed to decides.

The “instance” methods can be passed in the same way:

 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 


To make the definition of a function a little more readable, consider creating an alias of type (type-aliasing) for our function (like a typedef in Objective-C ):

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


As a parameter type, there can also be a function without a name (like a block in 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 


In Objective-C, the use of blocks as parameters is very popular for completion handler management and error handler in methods with asynchronous operations. It will also be popular in Swift .

Access Controls



Swift has three levels of access control:

Public access makes it possible to use entities within any source file from a module in which these entities are defined, as well as in any source file from another module that imports a module in which these entities are defined. Usually you use public access with the specification of the public interface for the framework.
Internal access makes it possible to use entities inside any source file from a module in which these entities are defined, but in no other source file outside the module in which they are defined. Usually you use internal access when defining the internal structure of an application or framework.
Private access restricts the use of an entity to a file with the source code in which this entity is defined. Using private access hides the implementation details of a specific piece of functionality.
By default, each function and variable is internal - if you want to change this, you must use the keyword private or public before each individual method or variable:

 public func myPublicFunc() { } func myInternalFunc() { } private func myPrivateFunc() { } private func myOtherPrivateFunc() { } 


Coming from Ruby, I prefer to place my private functions at the bottom of my class, separated by a marker:

 class MyFunClass { func myInternalFunc() { } // MARK: Private Helper Methods private func myPrivateFunc() { } private func myOtherPrivateFunc() { } } 


I hope that future releases of Swift will include the option of using one private keyword to indicate all of the methods below as private , just as access controls work in other programming languages.

"Special" types of return values



In Swift , function return types and values ​​can be a bit more complex than we use in Objective-C , especially with the introduction of Optionals and multiple return types.

Optional return types



If there is a possibility that your function may return nil , you need to define the return type as Optional :

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


And, of course, if you use the Optional return value, then do not forget to "expand" it:

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


The best explanation I have seen for the Optionals was taken from @Kronusdark:
I finally got the @SwiftLang optionals that resemble Schrödinger 's cat ! You should see if the cat is alive before you use it.


Multiple return values



One of the most impressive features of Swift is the ability of a function to have multiple return values:

 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) 


As we can see, the set of return values ​​is returned as a tuple, a very simple structure of grouped values. There are two ways to use multiple return values ​​in a 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 


Many Return Values ​​and Optionals



Confusion when returning a set of values ​​occurs if the returned values ​​can be Optional , but there are two ways to control this situation.

My logic gives a "misfire" regarding the above function - if nothing is passed to the input, then my program will crash.
If the values ​​to the input of the function are not passed, then I can make the entire return value 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 


Do you see? , 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 , , . , , Swif t, Swift .

Swift !

Source: https://habr.com/ru/post/244487/


All Articles