Swift is a new programming language for iOS, OS X, watchOS, and tvOS apps that builds on the best of C and Objective-C, without the constraints of C compatibility
iOS 개발을 시작하면서 스위프트 (Swift) 공부를 시작했습니다. 스위프트는 애플이 새롭게 소개한 iOS, OS X 개발을 위한 언어입니다. 새로운 언어로의 전환은 굉장히 큰 일이죠. 구글이 안드로이드를 만드는 언어로 자바와 완전히 호환되는 새로운 언어를 공개한다고 상상해보니 이게 얼마나 중요한 일인지 더 와닿는 것 같습니다. 처음 시작하는 저에게는 Objective-C 나 스위프트나 새롭기는 매한가지지만, 이왕 배우려면 스위프트를 배우는 게 낫겠죠?
스위프트를 간단히 공부해보면서 느낀 점은 두 가지입니다. 재밌다, 그리고 생소하다. 세미콜론도 없고 간단한 syntax 는 불필요한 작업 없이 코드를 빠르게 작성하게 해주었습니다. 스위프트 (Swift; 재빠른, 신속한)의 이름만 봐도 알 수 있죠. 하지만 생략할 수 있는 부분이 많다보니, 같은 코드라도 여러 가지로 표현이 가능했습니다. 생산성이 높아지겠지만 읽기가 어려웠습니다. 물론 자바도 개발자마다 코딩 스타일이 달라서 프로젝트마다 코딩 스타일 가이드가 있긴 합니다만 이 정도로 다르진 않았습니다. 게다가 너무 줄여놓으면 다른 사람이 만든 코드를 읽기가 어려울 것 같았습니다. 유지보수를 위해 소스의 가독성이 높은 코드가 인정을 받았다면 이제는 생산성이 더 중요한 것인가 싶기도 합니다.
애플이 만든 Swift 공식 가이드를 iBooks 에서 다운받을 수 있었습니다. 혹은 애플 개발자 홈페이지 에서 확인가능합니다. 그래서 공부할 자료가 따로 필요 없었죠. 한 문장 한 문장 번역을 할 순 없을 것 같고 코드만 가져오겠습니다. 가이드 내에 있는 모든 코드를 작성해보고 공부한 내용을 정리하는 식으로 진행하려고 합니다. 좋은 책은 예제에 모든 내용을 함축하고 있다고 생각하거든요.
Swift Tour
첫 장은 Swift Tour 입니다. 자세한 설명보다는 전체적으로 스위프트를 훑어보는 장이군요. 먼저 ‘Hello, world!’ 를 출력해봅시다. 세미콜론을 생략할 수 있습니다. 세미콜론 안쓰는게 처음에는 낯설었는데 금방 익숙해지네요.
1
print("Hello, World!")
Simple Values
변수는 값을 할당한 후 변경이 가능하고, 상수는 값을 한번만 할당 가능합니다. 변수와 상수 모두 선언 시 반드시 값을 할당해야 합니다.
1 2 3 4 5 6
// 변수 var myVariable =42 myVariable =50
// 상수 let myConstant =42
타입 선언입니다. 타입을 지정하지 않으면 초기값으로 타입을 유추합니다. 타입이 맞지 않으면 에러가 납니다. 타입은 한번 정해지면 변경할 수 없습니다.
1 2 3 4
let implicitInteger =70 let implicitDouble =70.0 let explicitDouble: Double=70 let explicitFloat: Float=4// 타입 에러
타입의 암묵적 변환은 할 수 없고 명시적으로만 가능합니다.
1 2 3 4
let label ="The width is" let width =94 let widthLabel = label +String(width) // 타입 변환 widthLabel = label + width // 타입 에러
문자열 안에 값을 표현하려면 백슬래시() 안에 표현할 값을 넣습니다.
1 2 3 4
let apples =3 let oranges =5 let appleSummary ="I have \(apples) apples." let fruitSummary ="I have \(apples + oranges) pieces of fruit."
배열 (Array)은 인덱스 (index)를 가지고 순차적으로 값을 저장하는 자료구조입니다.
1 2
var shoppingList = ["catfish", "water", "tulips", "blue paint"] shoppingList[1] ="bottle of water"
let emptyArray = [String]() let emptyDictionary = [String: Float]()
// 타입을 지정하기 전에는 다음과 같이 빈 값을 할당 shoppingList = [] occupations = [:]
Control Flow
조건문과 반복문을 알아봅시다.
조건문
if
switch
반복문
for-in
for
while
repeat-while
1 2 3 4 5 6 7 8 9 10
let individualScroes = [75, 42, 103, 87, 12] var teamScore =0 for score in individualScroes { if score >50 { teamScore +=3 } else { teamScore +=1 } } print(teamScore)
if
if 조건부에 오는 값은 반드시 Boolean 표현식이어야 합니다.
1 2 3
if teamScore { // 타입 에러 // ... }
타입 뒤에 물음표 (?) 를 붙이면 Optional 값이 됩니다. Optinal 값은 값이 없을 수도 있음을 의미합니다. 빈 값이라던가 의미 없는 값이라던가 하는 것도 결국 값이 있는 것이고, 값이 없다는 것은 아직 값이 할당되지 않은 상태를 명시적으로 표시하는 방법입니다. nil 은 값이 없음을 표현합니다 (기존의 null 을 생각하면 됩니다).
1 2
var optionalString: String? ="Hello" print(optionalString ==nil)
if 와 let 을 같이 쓰는 경우, optionalName 이 nil 이 아니면 true, nil 이면 false 값을 갖습니다. 만약 nil 이 아니면 해당 블록 내에서 name 이라는 상수로 사용 가능합니다.
1 2 3 4 5 6 7 8 9
var optionalName: String? ="John Appleseed" var greeting ="Hello!"
let nickName: String? =nil let fullName: String="John Appleseed" let informalGreeting ="Hi \(nickName ?? fullName)" print(informalGreeting)
Switch
switch 문을 살펴봅시다. 다양한 값과 비교 연산자를 사용 가능하고, break 문이 필요없습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
let vagetable ="red pepper"
switch vagetable { case"celery": print("Add some raisins and make ants on a log.") case"cucumber", "watercress": // 여러 케이스를 지정하는 경우 print("that would make a good tea sandwich.") caselet x where x.hasSuffix("pepper"): print("Is it a spicy \(x)?") default: // default 존재하지 않으면 에러가 난다 print("Everything tastes good in soup.") }
for-in
반복문 중 하나인 for-in 문 입니다. 배열과 Dictionary 를 순회할 수 있습니다.
funccalculateStatistics(scores: [Int]) -> (min:Int, max:Int, sum:Int) { var min = scores[0] var max = scores[0] var sum =0
for score in scores { if score > max { max = score } elseif score < min { min = score }
sum += score }
return (min, max, sum) }
let statistics = calculateStatistics([5, 3, 100, 3, 9])
print(statistics.sum) // . 으로 접근 가능 print(statistics.2) // 0, 1, 2 순서로 sum 을 가리킴
함수는 여러개의 가변인자를 받을 수 있고, 가변 인자는 배열처럼 접근 가능합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
funcsumOf(numbers: Int...) -> Int { var sum =0
print(numbers.count) // 배열처럼 사용 가능
for number in numbers { sum += number } return sum }
sumOf() // 인자가 없어도 에러가 나지 않는다 sumOf(42, 597, 12)
함수 내부에 함수를 선언할 수 있습니다 (중첩함수; Nested function). 중첩함수는 바깥쪽 함수에서 변수처럼 사용이 가능합니다. 또한 자세히 보시면 중첩함수인 add() 에서 바깥쪽 함수의 변수인 y 에 접근하고 있는 걸 볼 수 있습니다. 이것이 클로저 (Closure)의 개념 중 하나입니다.
1 2 3 4 5 6 7 8 9 10 11 12
funcreturnFifteen() -> Int { var y =10
funcadd() { y +=5 }
add() return y }
returnFifteen()
함수는 1급 타입입니다. javaScript 의 함수를 생각하시면 됩니다.
1급 타입이라는 것은,
변수에 담을 수 있다.
인자로 받을 수 있다.
반환이 가능하다.
을 의미합니다.
다음 코드는 함수를 리턴하고 함수를 변수에 담는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// -> ((Int) -> Int) // Int 를 파라미터로 받고 Int 를 리턴하는 함수 타입을 리턴 funcmakeIncrementer() -> ((Int) -> Int) { funcaddOne(number: Int) -> Int { return1+ number }
// addOne 이라는 함수 자체를 리턴 return addOne }
// 리턴되는 함수를 변수에 받아서 실행 가능 var increment = makeIncrementer() increment(7)
for item in list { if condition(item) { returntrue } }
returnfalse }
funclessThanTen(number: Int) -> Bool { return number <10 }
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, condition: lessThanTen)
Closure
클로저는 어떠한 함수와 그 함수의 환경 (컨텍스트)를 묶어놓은 것입니다. 그 환경은 클로저를 어떻게 만드느냐에 따라 결정됩니다. 중첨첩 함수에서도 클로저를 볼 수 있었습니다. 클로저에 대한 자세한 내용은 따로 포스트를 만들어야 할 것 같네요. 어쨌든 Swift 의 클로저는 무명함수 (Anonymous Functions) 로 선언 가능합니다. in 키워드 앞에는 클로저의 파라미터와 리턴 타입을 명시합니다. 아래 코드의 클로저는 배열의 각 숫자에 3을 곱하는 작업을 수행하게 됩니다.
1 2 3 4 5
numbers.map({ (number: Int) -> Intin// 파라미터와 리턴 타입 let result =3* number return result })
여기서 함수의 파라미터와 리턴 타입을 아는 경우 생략할 수 있습니다. 여기서는 map 의 파라미터로 들어오는 함수의 파라미터와 리턴 타입이 정해져 있는 상태입니다. 이럴 때는 생략이 가능합니다. 리턴 구문도 생략 가능합니다.
1 2 3 4 5
let mappedNumbers = numbers.map({ number in// 클롤저의 파라미터 타입과 리턴 타입을 생략 3* number // 리턴 구문 생략 }) print(mappedNumbers)
여기서 더 줄일 수 있습니다. 매개변수를 받는 괄호 안에 중괄호 ({}) 를 이용해서 클로저를 넣었는데요, 이 때 괄호를 생략하고 중괄호 형태로 수정이 가능합니다. 게다가 매개변수의 이름까지 생략하고 번호를 이용해서 참조할 수도 있습니다. $0 은 첫 번째 파라미터를, ‘$1’ 은 두 번째 파라미터를 말합니다. 따라서 다음과 같이 표현이 가능합니다.
1 2 3
numbers.map { 3*$0 }
너무 생소해져서 스위프트가 싫어질 것 같네요… 하지만 익숙해지면 편할 것 같습니다. 아래는 다른 예입니다.
1 2 3 4
let sortedNumbers = numbers.sort { $0>$1 } print(sortedNumbers)
classEquilateralTriangle: NamedShape { var sideLength: Double=0.0
init(sideLength: Double, name: String) { self.sideLength = sideLength // 하위 클래스의 속성 값 지정 super.init(name: name) // 상위 클래스 init 호출 numberOfSide =3// setter 없이 그냥 접근 가능함 }
var perimeter: Double { get { return3.0* sideLength }
set { // newValue 는 새로운 값을 의미 sideLength = newValue /3.0 } }
overridefuncsimpleDescription() -> String { return"An equilateral triangle with sides of length \(sideLength)." } }
var triangle =EquilateralTriangle(sideLength: 3.1, name: "a triangle") print(triangle.perimeter) // 9.3
클래스를 Optional 로 선언하면 nil 값이 올 수 있습니다. 사용할 때는 물음표 (?)를 붙여서 사용해야 합니다.
1 2
let optionalSqure: Square? =Square(sideLength: 2.5, name: "optional square") let sideLength = optionalSqure?.sideLength
Enumerations and Structures
Enumerations
열거형은 값을 나열해서 표현하는 것으로 값이 중요한 것이 아니라, 순서 상 값을 구분하는 용도로 만든 자료형입니다. 열거형에는 타입을 지정할 수 있고 메서드를 포함할 수 있습니다. 열거형의 값은 자동으로 할당되는데 아래 코드에서는 첫번째 값을 1로 주었기 때문에 차례로 2, 3, 4 순으로 할당됩니다.
funcsimpleDescription() -> String { switchself { case .Spades: return"spades" case .Hearts: return"hearts" case .Diamonds: return"diamonds" case .Clubs: return"clubs" } } }
let hearts =Suit.Hearts let heartDescription = hearts.simpleDescription()
Struct
구조체는 struct 키워드를 이용해 선언합니다. 구조체는 클래스와 거의 비슷하지만 보통 인스턴스가 참조 복사 형태로 전달된다는 점과 달리, 값 복사 형태로 전달됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
structCard { var rank: Rank var suit: Suit
funcsimpleDescription() -> String { return"The \(rank.simpleDescription()) of \(suit.simpleDescription())" } }
let threeOfSpades =Card(rank: .Three, suit: .Spades) let threeOfSpadesDescription = threeOfSpades.simpleDescription()
print(threeOfSpades) // Card(rank: HelloSwift.Rank.Three, suit: HelloSwift.Suit.Spades) print(threeOfSpadesDescription) // The 3 of spades
Associatd Value 는 Enumerations 의 case 에 특정한 값을 할당해서 저장한 후에 나중에 활용할 수 있는 방식입니다. 아래 코드를 보시면 ServerResponse 는 Result(String, String) 또는 Failure(String) 둘 중 하나의 값만을 가질 수 있는 상태입니다. 값을 할당 시에 저장한 두 개의 String 값은 switch 문에서 아래 코드와 같이 사용 가능합니다.
let success =ServerResponse.Result("6:00 am", "8:09 pm") let failure =ServerResponse.Failure("Out of cheese.")
switch success { caselet .Result(sunrise, sunset): // case .Result(let sunrise, let sunset) 과 같은 의미
// Sunrise is at 6:00 am and sunset is at 8:09 pm. print("Sunrise is at \(sunrise) and sunset is at \(sunset).") caselet .Failure(message): print("Failure... \(message)") }
Protocols and Extensions
Protocols
프로토콜은 자바의 인터페이스와 비슷한 개념입니다. 반드시 구현해야 하는 것을 명시할 수 있습니다. 프로토콜은 일종의 타입으로써 다형성을 구현할 수 있습니다.
1 2 3 4
protocolExampleProtocol { var simpleDescription: String { get } mutatingfuncadjust() }
클래스 뿐만 아니라, Enumerations 와 구조체에도 프로토콜을 적용할 수 있습니다.