bbiguduk님이 번역해 주신 Swift 공식문서를 보며, 기초부터 탄탄히 기본 문법을 정리합니다.
제가 이미 알거나 정리가 불필요하다 판단되는 내용은 생략합니다.
Swift는 정수 Int
, 부동 소수점 Double
및 Float
, 부울 값에 대한 Bool
및 텍스트 데이터 String
을 포함해 C와 Objective-C의 모든 기초 타입을 제공한다. 또한 Array
, Set
, Dictionary
라는 3개의 Collection Type을 제공한다.
그 외에 Tuple
이라는 고급 타입을 제공하는데, Tuple은 값을 그룹화 하여 생성하거나 전달할 수 있다. 튜플을 사용해 함수의 여러값을 단일 복합 값으로 반환할 수 있다.
또한, 값이 존재하지 않는 상태를 처리하기 위해 Optional
타입을 사용한다. Optional은 ‘값이 있고, x와 같다’ 또는 ‘값이 없다’를 알려준다. 이는 Class뿐 아니라 모든 타입에서 사용할 수 있다. 자세한 것은 나중에 서술!
C와 같이 Swift는 변수를 식별 가능한 이름으로 값을 저장하고 참조한다. 또한 값을 변경할 수 없는 변수르 광범위하게 사용하고 이러한 변수를 상수라고하며 C에서의 상수보다 훨씬 더 강력하다! 상수는 Swift 전체에서 사용되며 변경할 필요가 없는 값으로 작업할 때 코드를 보다 안전하고 명확하게 만들 수 있다.
상수(Constant)는 최초 지정 후에는 변경이 불가능 하지만, 변수(Variable)는 다른 값으로 변경이 가능하다.
상수는 let
키워드로 선언하고 변수는 var
키워드로 선언한다.
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
위와 같이 최대 로그인 시도 횟수(maximumNumberOfLoginAttempts)는 최대값을 절대 변경되지 않아야 하므로 상수(let
)로 선언해야 했고, 현재의 로그인 시도 횟수(currentLoginAttempt)는 로그인 시도 시 값을 증가시켜야 하므로 변수(var
)로 선언해야 한다.
var x = 0.0, y = 0.0, z = 0.0
여러 개의 상수 또는 변수를 선언할 때 콤마로 구분해 한줄로 선언이 가능하다.
상수 또는 변수를 선언할 때 저장할 수 있는 값의 종류를 명확하게 하기 위해 타입 명시(Type Annotation)을 제공한다. 상수 또는 변수 이름 뒤에 콜론과 사용할 타입 이름을 적어 사용한다!
var message: String
위 예저처럼 message는 문자열(String)을 담고 있어야 하므로 위와 같이 타입 명시를 할 수 있다.
그래서 위와 같이 선언한다면 우리는 “message라는 변수를 선언하고, 이것의 타입은 String입니다.”라고 표현할 수 있는 것이다.
var red, green, blue: Double
타입 명시 또한 여러 개의 연관된 변수를 콤마로 변수 이름을 구분하고 마지막 변수 이름 뒤에 하나의 타입 명시를하여 한줄로 선언할 수 있다.
상수와 변수의 이름은 유니코드 (Unicode) 문자를 포함하여 대부분 문자를 포함할 수 있다.
let n = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
공백, 수학적 기호, 화살표, 개인전용 유니코드 스칼라 값 또는 선과 박슬르 그리는 문자는 포함할 수 없다. 또한 숫자로 시작해도 안된다!
그리고 Swift 키워드와 같은 이름을 선언해야 한다면 이름을 백틱(`)으로 묶어야한다. 하지만 웬만해서 사용하지 않는 편이 좋다.
print
함수로 상수 또는 변수의 현재 값을 출력할 수 있습니다.
var message = "Hello!"
print(message)
// 출력 : Hello!
print 함수의 형태는 print(_:separator:terminator:)
로 separator
와 terminator
는 기본 값을 가지고 있어 호출할 때 생략할 수 있다. 기본적으로는 줄바꿈을 하고 출력하지만 만약 줄바꿈 없이 출력하고 싶을 때는 terminator
에 빈 문자열을 넘겨주면 된다.
var message1 = "Hello!"
var message2 = "I'm Hyojun"
print(message1, terminator: "")
print(message2)
// 출력 : Hello! I'm Hyojun
그리고 문자열에 상수 또는 변수를 포함해서 출력하고 싶다면 문자열 삽입(String interpolation)을 사용할 수 있따. 변수를 백슬래시()와 소괄호로 감싸면 된다.
var name = "Hyojun"
print("My name is \(name)")
// 출력 : My name is Hyojun
Swift는 Python과 비슷하게 코드의 각 구문 끝에 세미콜론(;)을 필수적으로 붙이지 않아도 된다. 하지만 여러 구문을 한 줄로 작성하고 싶을 때는 꼭 달아주어야 한다.
let cat = "🐱"; print(cat)
// 출력 : "🐱"
Swift는 8, 16, 32, 64비트 형태의 부호가 있는 정수와 부호가 없는 정수를 지원한다. 이 정수는 8bit의 부호가 없는 정수 UInt8 그리고 32bit의 부호가 있는 정수 Int32와 같이 C와 비슷한 형태를 가진다.
각 정수 타입의 min
과 max
로 최소값과 최대값을 가져올 수 있다.
let minValue = UInt8.min // 0
let maxValue = UInt8.max // 255
Double
은 64-bit 부동 소수점 숫자를 표기하고 Float
는 32-bit 부동 소수점 숫자를 표기한다.
Double
은 최소 15자리의 소수점을 표현할 수 있고, Float
는 더 적은 6자리의 소수점을 표현할 수 있다. 보통 두 타입 중에는 Double
이 선호됩니다.
Swift는 타입 세이프(Type-Safe) 언어로, 코드를 작성할 때 사용할 수 있는 값의 타입을 명확하게 알 수 있다. 코드를 컴파일 할 때 타입 검사를 수행하고 일치하지 않는 타입을 오류로 표시한다. 이를 통해 개발 단계에서 빠르게 오류를 검출할 수 있다.
하지만 선언하는 모든 변수와 상수에 타입을 지정하지 않아도 된다. Swift가 알아서 적절한 타입으로 타입 유추(Type Inference)를 해주기 때문이다.
let meaningOfLife = 42 // Int로 유추
let pi = 3.14159 // Double로 유추
let anotherPi = 3 + 0.14159 // Double로 유추
Swift에서 정수 리터럴은 다음과 같다.
0b
접두사로 2진수0o
접두사로 8진수0x
접두사로 16진수let decimalInteger = 17
let binaryInteger = 0b10001
let octalInteger = 0o21
let hexadecimalInteger = 0x11
모두 10진수로 17로 같은 값이다.
부동 소수점 리터럴은 10진수 또는 16진수(0x
) 일 수 있다. 소수점 양쪽에 항상 숫자가 있어야 한다. 10진수는 대문자 또는 소문자 e로 표시되는 지수를 가질 수 있다. 16진수는 대문자 또는 소문자 p로 표시되는 지수를 가질 수 있다.
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
모두 10진수로 12.1875로 같은 값이다.
또한, Swift에서는 읽기 쉽게 추가 포맷을 포함할 수 있다. 정수와 부동 소수점 모두 추가적으로 0을 채울 수 있으며, 가독성을 높이기 위해 밑줄을 포함할 수 있다.
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
let cannotBeNegative: UInt8 = -1 // 오류!
let tooBig: Int8 = Int8.max + 1 // 오류!
UInt8
은 0 ~ 255의 범위를 갖고 있기 때문에 위와 같이 컴파일 할 시 오류가 발생한다. 그래서 특정 타입으로 변환하려면 기존 값으로 원하는 타입의 새 숫자를 초기화해야 한다.
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one) // 2001
위와 같이 UInt16(one)
을 호출 해 one
값을 새로운 UInt16
으로 초기화 해서 새로운 값을 사용한다!
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine // 3.14159
위와 같이 Int
형의 three
를 Double
형로 호출해 초기화 해준다.
let integerPi = Int(pi)
또한 이렇게 Int
형으로 초기화해줄 수도 있다.
Swift에서는 타입 별칭(Type Aliases)를 사용해 이미 존재하는 타입을 새로운 이름으로 정의할 수 있다. 타입 별칭은 typealias
키워드로 정의할 수 있다.
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min // 0
튜플(Tuples)은 여러 값을 단일 복합 값으로 그룹화 한다. 튜플 안에 값은 어떠한 타입도 가능하다.
let http404Error = (404, "Not Found")
위 예제 코드는 HTTP 상태 코드를 나타내는 튜플인데, 값을 컴퓨터가 인식할 수 있는 숫자(Int
)와 사람이 읽을 수 있는 설명인 문자열(String
)을 그룹화해서 제공한다. 그래서 이 튜플의 타입은 (Int, String)
로 설명할 수 있다.
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 출력 : The status code is 404
print("The status message is \(statusMessage)")
// 출력 : The status message is Not Found
또한 튜플은 위 코드와 같이 별도의 상수 또는 변수로 분해해서 접근할 수 있다! (매우 편리할 듯)
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 출력 : The status code is 404
튜플의 값 중 일부만 필요한 경우에는 튜플을 분해할 때 밑줄(_)로 튜플의 일부를 무시할 수 있다.
print("The status code is \(http404Error.0)")
// 출력 : The status code is 404
print("The status message is \(http404Error.1)")
// 출력 : The status message is Not Found
그리고 튜플 값의 index에 따라 개별 요소에 접근할 수 있다.
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")
// 출력 : The status code is 200
print("The status message is \(http200Status.description)")
// 출력 : The status message is OK
튜플을 정의할 때 튜플의 요소에 이름을 정의할 수 있다.
이로써 Swift에서의 튜플은 함수의 반환 값으로 유용하게 사용할 수 있다!
Swift에서 값이 없는 경우 옵셔널(Optionals)를 사용한다. 옵셔널은 ‘값이 있고, x와 같다’ 또는 ‘값이 없다’라는 2가지 가능성을 가지고 있다.
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// 출력 : Optional(123)
위 예제에서는 String
값을 Int
형으로 초기화했다. 하지만 문자열에 숫자가 포함되어 있지 않을 수도 있다. (예를 들어 “123”과 달리 “abc”라는 문자열은 숫자로 변환할 수 없는 것)
그래서 강제로 Int
형으로 초기화 할 때 초기화가 실패할 수 있으므로, 위 예제에서는 Int
형이 아닌 Optional(Int)
형을 반환하는 것 이다. 이는 Int?
형으로 표현할 수 있다. 물음표(?)는 값의 포함 여부가 Optional
이라는 것을 나타내고 어떠한 Int
값이 있거나 없을 수 있다는 의미이다.
Optinal 변수에 nil
로 값을 지정해 값이 없는 상태를 나타낼 수 있다.
var serverResponseCode: Int? = 404 // Optional(404)
serverResponseCode = nil // nil
또한, 기본값 없이 Optional 변수를 정의하면 자동적으로 nil
로 설정한다.
if
구문은 Optional
과 nil
을 비교해 Optional
에 값이 포함되어 있는지 확인할 수 있다. Optional
에 값이 있다면 nil
이 아님으로 간주할 수 있다.
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
옵셔널에 값이 포함되어 있다고 확신하면 옵셔널 이름 끝에 느낌표(!)를 추가해 값에 접근할 수 있다. 이때 느낌표는 “이 옵셔널 값은 확실하게 값을 가지고 있다"라는 의미이다. 이것을 강제 언래핑(Forced Unwrapping)이라고 한다.
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
옵셔널 바인딩(Optional Binding)은 옵셔널이 값을 포함하고 있는지 확인하고 값이 있는 경우 해당 값을 임시 상수 또는 변수로 사용할 수 있도록 한다. 옵셔널 바인딩은 if
와 while
구문에서 옵셔널에 값이 있는지 체크하고 단일 동작의 일부로 상수 또는 변수를 추출할 수 잇다.
if
구문에서 옵셔널 바인딩은 다음과 같이 사용한다!
이로써 강제 언래핑(forced unwrapping)보다 옵셔널 바인딩을 이용할 수 있다.
if let actualNumber = Int(possibleNumber) {
print("\"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
print("\"\(possibleNumber)\" could not be converted to an integer")
}
위 코드는 Int(possibleNumber)
이 Optional(Int)
값이라면, actualNumber
이라는 새로운 상수에 옵셔널을 벗긴 값을 설정한다.
그래서 변환에 성공한다면 actualNumber
상수는 if
구문에 첫번째 중괄호 내에서 사용할 수 있다.
let myNumber = Int(possibleNumber) // Optinal(Int)
if let myNumber = myNumber { // Int
print("My number is \(myNumber)")
}
이 코드는 이전 예제에서 코드와 마찬가지로 myNumber
가 값이 있는지 없는지 먼저 확인한다. 값이 있다면 해당 값이 새로운 myNumber
라는 상수에 설정된다. if
구문 안에 myNumber
는 새로운 옵셔널이 아닌 상수를 참조한다. 이 상수는 if
문 내에서만 사용 가능하다! 그 외에는 옵셔널 값이다.
if let myNumber {
print("My number is \(myNumber)")
}
위와 같이 더 짧게 사용도 가능하다. 게다가 상수(let
)뿐 아니라 변수(var
)도 사용이 가능하다.
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"
위 코드와 같이 if
문에 쉼표로 구분해 옵셔널 바인딩 뿐 아니라 다양한 조건문들 포함할 수 있다. 옵셔널 바인딩 값 중 하나가 nil
이거나 조건문이 false
로 판단되면, if
문은 false
로 간주된다.
옵셔널은 상수 또는 변수가 값이 없다는 것을 허락하고 알려준다. 때때로 프로그램 구조에서 옵셔널이 값을 처음 설정한 후에 항상 값을 갖는 것은 분명하다. 이 경우 항상 값이 있다고 가정할 수 있으므로, 접근할 때 마다 옵셔널의 값을 확인하고 언래핑할 필요가 없다.
이러한 옵셔널은 암시적으로 언래핑된 옵셔널(implicitly unwrapped optionals)로 정의된다. 타입뒤에 물음표(?)가 아닌 느낌표(!)로 암시적으로 언래핑된 옵셔널을 작성한다. 변수명 뒤가 아닌 옵셔널 타입 뒤에 느낌표를 위치시키는 것이 더욱 좋다!
암시적으로 언래핑된 옵셔널은 내부적으로 옵셔널이지만 옵셔널에 접근할 때마다 옵셔널 값을 풀 필요없이 옵셔널이 아닌 값처럼 사용할 수 있다.
let possibleString: String? = "An optional string."
let forcedString: String = possibleString!
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString
암시적으로 언래핑된 옵셔널은 강제 언래핑을 허락하는 것으로 생각할 수 있다. 암시적으로 언래핑된 옵셔널을 사용할 때 Swift는 처음에 기존의 옵셔널 값으로 사용하려고 하고 사용이 불가능할 경우 Swift는 값을 강제로 언래핑한다.
위 예제에서 옵셔널 값 assumedString
은 implicitString
이 명시적으로 옵셔널이 아닌 String
타입이기 때문에 implicitString
에 값을 할당하기 전에 강제로 언래핑 된다.
let optionalString = assumedString // Optional(String)
위 코드에서 optionalString
은 명시적 타입이 없으므로 기본적으로 옵셔널이다.
Swift에서 프로그램이 실행되는 동안 에러가 발생할 때 처리하기위햐 에러 처리(Error Handling)을 사용한다.
값의 존재 유무를 사용해 함수의 성공 또는 실패를 전달할 수 있는 옵셔널과 달리 에러 처리를 사용하면 에러 원인을 판별하고 필요한 경우 에러를 프로그램의 다른 부분으로 전파 할 수 있다.
func canThrowAnError() throws {
// error가 발생할 수도 있음
}
함수를 선언할 때 throws
키워드를 포함시켜 에러가 발생할 수 있음을 나타낸다. 에러를 발생할 수 있는 함수를 호출할 때 표현식 앞에 try
키워드를 붙여야한다.
do {
try canThrowAnError()
// 에러가 나지 않으면 계속 실행
} catch {
// 에러가 나면 실행
}
do
구문은 에러를 하나 이상의 catch
절로 전파할 수 있는 새로운 범위를 만든다. 그리고 catch
절에 의해 처리될 때 까지 현재 범위에서 에러를 자동으로 전파한다.
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
위 예제에서 makeASandwich()
함수는 깨끗한 그릇을 사용할 수 없거나, 재료가 없는 경우 에러를 발생한다. 그래서 makeASandwich()
함수는 에러를 발생할 수 있으므로 try
키워드를 붙여야 한다. 그리고 do
구문 안에 넣어주면 에러가 발생했을 때 상황에 맞게 catch
절로 전파된다. 만약 에러가 발생하지 않으면 계속 진행해 eatASandwich()
함수가 호출된다.
주장(Assertions
)과 전제조건(Preconditions
)은 런타임 시 발생하는 조건이다. 추가 코드를 실행하기 전에 이를 사용해 필수조건이 충족되는지 확인할 수 있다. Assertions
또는 Preconditions
이 true
이면 코드는 평소와 같이 진행된다. 조건이 false
로 판단되면 프로그램의 현재 상태는 유효하지 않아 코드 실행은 종료되고 앱은 종료된다.
Error Handling과 달리 Assertions
과 Preconditions
은 복구 가능하거나 예상되는 에러에 사용되지 않는다. 이는 유효하지 않은 프로그램 상태를 나타내기 때문에 실패한 상태를 잡을 방법은 없다.
Assertions
은 오직 디버그 빌드에서 체크되고 Preconditions
는 디버그와 프로덕션 빌드에서 모두 체크된다.
Swift 표준 라이브러리에 assert(_:_:file:line:)
함수로 Assertions을 작성할 수 있다. 이 함수에 true
또는 false
로 판단될 표현식과 조건이 false
일 경우 출력될 메세지를 전달한다.
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
위 코드에서는 age
가 0보다 작으므로, false
이기 때문에 assertion
은 실패가 되어 프로그램이 종료된다.
반대로 assertionFailure(_:file:line:)
함수를 사용할 수 있다.
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel.")
} else if age >= 0 {
print("You can ride the ferris wheel.")
} else {
assertionFailure("A person's age can't be less than zero.")
}
precondition(_:_:file:line:)
함수로 Preconditions을 작성할 수 있다. 이 함수에 true
또는 false
로 판단될 표현식과 조건이 false
일 경우 출력될 메세지를 전달한다.
precondition(index > 0, "Index must be greater than zero.")
Assert와 마찬가지로 반대로 preconditionFailure(_:file:line:)
함수를 호출하여 실패가 발생했음을 알릴 수 있다.