[iOS] Swift 기본 (1)
1. 변수와 상수
변수는(variable)은 값을 수정할 수 있고, 상수(constant)는 그렇지 않습니다. 변수는 var로 선언하고, 상수는 let으로 선언합니다.
var name = "simple"
let birthyear = 1234
변수일 경우엔 다음과 같이 변경이 가능하지만
name = "han"
상수일 경우에는 컴파일 에러가 발생합니다.
birthyear=2000//컴파일에러
Swift는 javascript와는 다르게 정적 타이핑 언어(java / C)입니다. 변수 또는 상수를 선언시 그 자료형(type)을 명시해주어야 하는 언어를 말합니다.
var name: String = "simple"
let birthyear: Int = 1234
var height: Float = 100.1
이런식으로 변수 또는 상수이름 뒤에 콜론(:)을 붙이고 자료형을 명시할 수 있습니다. 이때 사용하는 :String, :Int 등을 가지고 타입 어노테이션(Type Annotation) 이라고 합니다. Swift에서는 타입을 굉장히 엄격히 다루기 때문에 다른 자료형 끼리는 기본연산조차 되지않습니다. 예를 들어 현재 Int로 선언한 birthyear와 Float 타입의 money를 더하려고 할 경우 에러가 발생합니다.
birthyear + height // 컴파일 에러!
이런 경우엔 정확히 어떤 타입으로 표현할건지 명시후 사용하면 됩니다.
Float(birthyear) + height // 1334.1
해당 방식을 조금 응용하면,
String(birthyear) + "의 " + name + "임"// 1234의 simple임
좀더 쓰기 편하게 바꿀 경우 아래와 같이 작성할 수도 있습니다.
"\(birthyear)의 \(name)임"// 1234의 simple임
위의 예제에서는 자료형을 명시하지 않았음에도 에러가 발생하지 않는 것을 알 수있는데, Swift 컴파일러도 큰 따옴표(”)로 감싸진 텍스트는 String 타입인 것을 알고, 숫자는 Int 타입인 것을 인식할 수 있기 때문입니다. 이처럼 직접 명시하지않고도 값을 가지고 정적 타이핑을 할 수 있게 해주는 것을 타입 추론(Type Inference)이라고 합니다.
2. 배열과 딕셔너리
배열과 딕셔너리는 모두 대활호([]) 를 이용해서 정의할 수 있습니다.
var languages = ["Swift", "Objective-C", "Python"]
var capitals = [
"한국": "서울",
"일본": "도쿄",
"중국": "베이징"
]
접근하거나 값을 변경시에도 대괄호를 사용합니다.
languages[0] // Swift
languages[1] = "Ruby"
capitals["한국"] // 서울
capitals["프랑스"] = "파리"
마찬가지로 상수로 정의하면 값을 수정할 수 없으므로 주의하셔야합니다. 또한 타입 명시시에는 대괄호안에 어떤 타입을 받을것인지 명시 해야합니다.
var languages: [String] = ["Swift", "Objective-C", "Python"]
var capitals: [String: String] = [
"한국": "서울",
"일본": "도쿄",
"중국": "베이징",
]
빈 배열이나 빈 딕셔너리를 정의하고싶을경우엔 빈 대괄호를 쓰면 됩니다.
var languages: [String] = []
var capitals: [String: String] = [:]
var languages = [String]()
var capitals = [String: String]()
3. 조건문과 반복문
조건을 검사할 때에는 if, switch를 씁니다. 아래 코드는 if를 사용한 예시입니다.
var age = 19
var student = ""
if age >= 8 && age < 14 {
student = "초등학생"
} else if age < 17 {
student = "중학생"
} else if age < 20 {
student = "고등학생"
} else {
student = "기타"
}
student // 고등학생
if문의 조건절에는 값이 정확하게 참 혹은 거짓으로 나오는 Bool 타입을 사용해야 합니다. 위에서 언급한 것과 같이 Swift에서는 타입 검사를굉장히 엄격하게 하기 때문에, 다른 언어에서 사용 가능한 아래와 같은 코드를 사용하지 못합니다.
var number = 0
if !number { /* 컴파일 에러!*/ ...}
Unary operator ‘!’ cannot be applied to an operand of type ‘Int’
대신 이렇게 써야합니다.
if number == 0 { // ... }
빈 문자열이나 배열 등을 검사할 때에도 명확하게 길이가 0인지를 검사해야 합니다.
if name.isEmpty { ... } if languages.isEmpty { ... }
만약 C나 Java와 같은 프로그래밍 언어를 사용해봤다면 switch는 단순히 값이 ‘같은지’만을 검사하는 것으로 알고 있겠지만, Swift의 switch구문은 조금 특별합니다. 패턴 매칭이 가능하기 때문입니다. 아래 코드는 위에서 작성한 if문을 switch문으로 옮겨본 것입니다.
switch age {
case8..<14: student = "초등학생"
case14..<17: student = "중학생"
case17..<20: student = "고등학생"
default: student = "기타"
}
8..<14와 같이 범위Range 안에 age가 포함되었는지 여부를 검사할 수 있습니다. 반복되는 연산을 할 때에는 for, while을 씁니다. for 구문을 사용해서 배열과 딕셔너리를 차례로 순환할 때에는 아래와 같이 씁니다.
for language in languages {
print("저는 \(language) 언어를 다룰 수 있습니다.")
} for (country, capital) in capitals { print("\(country)의 수도는 \(capital)입니다.") }
단순한 반복문을 만들고 싶다면 범위를 만들어서 반복시킬 수도 있습니다.
for i in0..<100 { i }
만약 i를 사용하지 않는데 단순한 반복을 하고 싶다면, i 대신 _를 사용해서 무시할 수도 있습니다.
for_in0..<10 { print("Hello!") }
-키워드는 어디서나 변수 이름 대신에 사용할 수 있으며, 알아두면 유용하게 사용할 수 있습니다. while은 조건문의 값이 true일 때 계속 반복됩니다.
var i = 0
while i < 100 { i += 1 }
4. 옵셔널
Swift가 가지고 있는 가장 큰 특징 중 하나가 바로 옵셔널Optional입니다. 직역하면 ‘선택적인’ 이라는 뜻이 되는데요. 값이 있을 수도 있고 없을 수도 있는 것을 나타냅니다.
예를 들어 문자열의 값이 있으면 “가나다”가 될 것입니다. 그럼, 값이 없다면 ““으로 출력 될것인가? 아닙니다. ““도 엄연히 값이 있는 문자열입니다. 정확히는 ‘값이 없다’가 아니고 ‘빈 값’입니다. 값이 없는 문자열은 바로 nil입니다.
또 다른 예를 들어보겠습니다. 정수형의 값이 있으면 123과 같은 값이 있을 것입니다. 값이 없다면 0인가? 마찬가지로 0은 0이라는 숫자 ‘값’입니다. 이 경우에도 값이 없는 정수는 nil입니다. 마찬가지로, 빈 배열이나 빈 딕셔너리라고 해서 ‘값이 없는’것이 아닙니다. 다만 ‘비어 있을’ 뿐입니다.. 배열과 딕셔너리의 경우에도 ‘없는 값’은 nil입니다.
이렇게, 값이 없는 경우를 나타낼 때에는 nil을 사용합니다. 그렇다고 해서 모든 변수에 nil을 넣을 수 있는 것은 아닙니다. 예로, 우리가 위에서 정의한 name이라는 변수에 nil을 넣으려 하면 에러가 발생합니다.
var name: String = "simple"
name = nil// 컴파일 에러!
Nil cannot be assigned to type ‘String’
값이 있을 수도 있고 없을 수도 있는 변수를 정의할 때에는 타입 어노테이션에 ?를 붙여야 합니다. 이렇게 정의한 변수를 바로 옵셔널Optional이라고 합니다. 옵셔널에 초깃값을 지정하지 않으면 기본값은 nil입니다.
var email: String? print(email) // nil
email = "ac85kr@gmail.com"print(email) // Optional("ac85kr@gmail.com")
옵셔널로 정의한 변수는 옵셔널이 아닌 변수와는 다릅니다. 예를 들어, 아래와 같은 코드는 사용할 수 없습니다.
let optionalEmail: String? = "ac85kr@gmail.com"
let requiredEmail: String = optionalEmail // 컴파일 에러!
Value of optional type ‘String?’ not unwrapped; did you mean to use ‘!’ or ‘?’?
requiredEmail 변수는 옵셔널이 아닌 String이기 때문에 항상 값을 가지고 있어야 합니다. 반면에, optionalEmail은 옵셔널로 선언된 변수이기 때문에 실제 코드가 실행되기 전까지는 값이 있는지 없는지 알 수 없습니다. 따라서 Swift 컴파일러는 안전을 위해 requiredEmail에는 옵셔널로 선언된 변수를 대입할 수 없게 만들었습니다. 옵셔널은 개념적으로 이렇게 표현할 수 있습니다. 어떤 값 또는 nil을 가지고 있습니다.
,-- 어떤 값 (String, Int, ...)
Optional
`-- nil 옵셔널 바인딩 (Optional Binding)
그럼 옵셔널의 값을 가져오고 싶은 경우에는 어떻게 해야합니까? 이 때 사용하는 것이 바로 옵셔널 바인딩Optional Binding입니다. 옵셔널 바인딩은 옵셔널의 값이 존재하는지를 검사한 뒤, 존재한다면 그 값을 다른 변수에 대입시켜줍니다. if let 또는 if var를 사용하는데ㅡ 옵셔널의 값을 벗겨서 값이 있다면 if문 안으로 들어가고, 값이 nil이라면 그냥 통과하게 됩니다. 예를 들어, 아래의 코드에서 optionalEmail에 값이 존재한다면 email이라는 변수 안에 실제 값이 저장되고, if문 내에서 그 값을 사용할 수 있습니다. 만약 optionalEmail이 nil이라면 if문이 실행되지 않고 넘어갑니다.
if let email = optionalEmail {
print(email) // optionalEmail의 값이 존재한다면 해당 값이 출력됩니다.
} // optionalEmail의 값이 존재하지 않는다면 if문을 그냥 지나칩니다.
하나의 if문에서 콤마(,)로 구분하여 여러 옵셔널을 바인딩할 수 있습니다. 이곳에 사용된 모든 옵셔널의 값이 존재해야 if문 안으로 진입합니다.
var optionalName: String? = "한태준"
var optionalEmail: String? = "ac85kr@gmail.com"
if let name = optionalName, email = optionalEmail { /* name과 email 값이 존재*/ }
//Tip: 코드가 너무 길 경우에는, 이렇게 여러 줄에 걸쳐서 사용하는 것이 바람직합니다.
If let name = optionalName, let email = optionalEmail { /* name과 email 값이 존재*/ }
참고로, 두 번째 let 부터는 생략이 가능합니다. 위 코드는 아래 코드와 동일합니다.
if let name = optionalName { if let email = optionalEmail { /* name과 email 값이 존재*/ } }
/*
Tip: 한 번의 if문에서 여러 옵셔널을 바인딩할 수 있게 된 것은 Swift 1.2 버전부터입니다.
이전 버전까지는 바로 위와 같이 여러 번으로 감싸진 옵셔널 바인딩을 사용했습니다.
옵셔널을 바인딩할 때 ,를 사용해서 조건도 함께 지정할 수 있습니다.
이후의 조건절은 옵셔널 바인딩이 일어난 후에 실행됩니다. 즉, 옵셔널이 벗겨진 값을 가지고 조건을 검사하게 됩니다.
*/
var optionalAge: Int? = 22
if let age = optionalAge, age >= 20 { /* age의 값이 존재하고, 20 이상입니다.*/ }
위 코드는 아래 코드와 동일합니다.
if let age = optionalAge { if age >= 20 { /* age의 값이 존재하고, 20 이상입니다.*/ } }
- 옵셔널 체이닝 (Optional Chaining) : Swift 코드를 간결하게 만들어주는 많은 요소들이 있는데, 옵셔널 체이닝Optional Chaining을 알게되면 다른 프로그래밍 언어가 조금 불편하게 느껴지는 경우가 생깁니다. 옵셔널 체이닝을 이해하는 데에는 설명보다 코드를 보는 편이 훨씬 좋습니다. 예컨대, 옵셔널로 선언된 어떤 배열을 떠올려봅시다. 이 배열이 ‘빈 배열’인지를 검사하려면 어떻게 해야 하겠습니까? nil이 아니면서 빈 배열인지를 확인해보면 될 것입니다.
let array: [String]? = []
var isEmptyArray = false
if let array = array, array.isEmpty {
isEmptyArray = true
} else {
isEmptyArray = false
} isEmptyArray
/*옵셔널 체이닝을 사용하면 이 코드를 아래와 같이 더 간결하게 쓸 수 있습니다.*/
let isEmptyArray = array?.isEmpty == true
옵셔널 체이닝은 옵셔널의 속성에 접근할 때, 옵셔널 바인딩 과정을 ? 키워드로 줄여주는 역할을 합니다. 다음과 같이 3가지 경우의 수를 생각해봅시다.
/*array가 nil인 경우*/
array?.isEmpty ~~~~~~ 여기까지 실행되고 `nil`을 반환합니다.
/*array가 빈 배열인 경우*/
array?.isEmpty ~~~~~~~~~~~~~~ 여기까지 실행되고 `true`를 반환합니다.
/*array에 요소가 있는 경우*/
array?.isEmpty ~~~~~~~~~~~~~~ 여기까지 실행되고 `false`를 반환합니다.
array?.isEmpty의 결과로 나올 수 있는 값은 nil, true, false가 됩니다. isEmpty의 반환값은 Bool인데, 옵셔널 체이닝으로 인해 Bool?을 반환하도록 바뀐 것입니다. 따라서 값이 실제로 true인지를 확인하려면, == true를 해주어야 합니다.
- 옵셔널 벗기기 : 옵셔널을 사용할 때마다 옵셔널 바인딩을 하는 것이 가장 바람직합니다. 하지만, 개발을 하다보면 분명히 값이 존재할 것임에도 불구하고 옵셔널로 사용해야 하는 경우가 종종 있는데, 이럴 때에는 옵셔널에 값이 있다고 가정하고 값에 바로 접근할 수 있도록 도와주는 키워드인 !를 붙여서 사용하면 됩니다.
print(optionalEmail) /* Optional("ac85kr@gmail.com") */
print(optionalEmail!) /* ac85kr@gmail.com*/
!를 사용할 때에는 주의할 점이 있는데, 옵셔널의 값이 nil인 경우에는 런타임 에러가 발생한다는 것입니다. Java의 NullPointerException과 비슷하다고 생각하시면 될 듯 합니다.
var optionalEmail: String? print(optionalEmail!) // 런타임 에러!
fatal error: unexpectedly found nil while unwrapping an Optional value
런타임 에러가 발생하면 iOS 앱은 강제로 종료(크래시)됩니다. 그러므로 조심히 사용할 필요가 있습니다.
- 암묵적으로 벗겨진 옵셔널 (Implicitly Unwrapped Optional) : 만약, 옵셔널을 정의할 때 ? 대신 !를 붙이면 ImplicitlyUnwrappedOptional이라는 옵셔널로 정의됩니다. 직역하면 ‘암묵적으로 벗겨진 옵셔널’입니다.
var email: String! = "ac85kr@gmail.com"
print(email) // ac85kr@gmail.com
이렇게 정의된 옵셔널은 nil을 포함할 수 있는 옵셔널이긴 한데, 접근할 때 옵셔널 바인딩이나 옵셔널을 벗기는 과정을 거치지 않고도 바로 값에 접근할 수 있다는 점에서 일반적인 옵셔널과 조금 다릅니다. 옵셔널 벗기기와 마찬가지로, 값이 없는데 접근을 시도하면 런타임 에러가 발생합니다.
var email: String!
print(email) // 런타임 에러!
fatal error: unexpectedly found nil while unwrapping an Optional value
가급적이면 일반적인 옵셔널을 사용해서 정의하고, 옵셔널 바인딩 또는 옵셔널 체이닝을 통해 값에 접근하는 것이 더 바람직합니다. Tip: Swift 2 버전에서는 “(email)”과 같이 문자열을 포맷팅하면 ac85kr@gmail.com이 나왔으나, Swift 3 버전부터는 ImplicitlyUnwrappedOptional을 문자열 포맷팅 할 경우 Optional(“ac85kr@gmail.com”)로 포맷팅되니 주의해서 사용해야 합니다. Swift 3 버전부터 ImplicitlyUnwrappedOptional을 일반 Optional과 거의 동일하게 취급했기 때문인데, 자세한 이유는 SE-0054 문서에 잘 나와있습니다.