먹고 기도하고 코딩하라

Swift 의 기본 문법 (3) - 옵셔널의 이해, 조건/반복문 본문

앱/Swift

Swift 의 기본 문법 (3) - 옵셔널의 이해, 조건/반복문

2G Dev 2021. 11. 24. 22:12
728x90
728x90

저번에 함수와 다소 이해하기 어려운 클로저 개념에 대해 살펴봤다.

이번에는 옵셔널 변수, 그리고 기본적인 조건/반복문을 살펴보고자 한다.

 

 

(6) 옵셔널 변수

옵셔널 변수란 값이 있을 수도 있고 없을 수도(nil일 수도) 있는 변수를 뜻한다. nil의 가능성을 명시적으로 표현한 것이다. 역으로 설명하자면, 옵셔널 변수가 아니라면 그 변수의 값은 절대 nil일 수 없다. 전달받은 값이 옵셔널이 아니라면 그냥 nil 체크 안 하고 써도 된다. 

옵셔널 변수는 타입 뒤에 '?'을 쓰거나 Optional<type>을 써서 옵셔널 변수라고 선언하게 된다. 

이렇게 쓰면 "이 변수엔 값이 있을 수도 없을 수도 있어~ 참고해~" 이런 뜻이 된다. 

var optionalString: String?;
var optionalString2: Optional<String> = nil;

?을 안 붙이고 초기값도 안 줘도 일단 컴파일은 된다.

하지만 진짜 문제는 초기화되지 않은 변수를 사용하려고 할 때 생긴다.

var varString: String;
print(varString);	//variable 'varString' used before being initialized

이렇게 초기화되기 이전의 변수를 쓰면 에러가 나면서 프로그램이 뻗어버린다.

사실 이건 잘못된 예시이기는 하다. 특별한 경우가 아니라면 사용 전에 초기값을 주는 게 좋고, 더구나 nil이 허용되지 않는다면 선언할 때가 됐든 나중이 됐든 값을 줘야 맞기 때문이다.

어쨌든, 값이 있을 수도 있고 없을 수도 있는 변수라면 옵셔널 변수로 선언해야 한다. ?을 붙여서 선언한다.

 

옵셔널 값을 꺼내오는 방법으로는 강제로 꺼내오는 방법과 안전하게 옵셔널 바인딩으로 가져오는 방법이 있다.

먼저 강제로 꺼내오는 방법부터 살펴보자면, 간단하다.

사용하려는 옵셔널 변수에 !을 붙이면 nil인지 뭔지 확인 안 하고 값이 있을 테니까 가져와라~ 이런 의미가 된다.

print(optionalString!);

하지만 이렇게 사용할 경우, optionalString이 nil일 때 바로 런타임 에러가 일어나면서 프로그램이 죽어 버린다. 간단하지만 위험한 방법이기도 하다.

 

다음으로 옵셔널 변수를 안전하게 사용하는 법이다.

옵셔널 바인딩이란 옵셔널 값을 꺼내오는 방법 중 하나로, 옵셔널 변수의 값이 nil인지 체크하고 안전하게 값을 추출하는 것이다. 

제일 기본적인 방법은 변수를 nil과 비교하는 if-else문을 쓰는 것이다. (이렇게 자연스럽게 if-else 구문을... ㅋㅋㅋ) 두 번째 방법은 if let, if var 등을 써서 새로운 변수에 원래 옵셔널 값을 대입해서 되는지 확인한 다음 그 값을 사용하는 것이다.

암튼, 이제 옵셔널 변수 개념이 확실해졌을 테니 어떻게 쓰는지 알아보자.

var optionalName: String? = "Jane";
var greeting: String;
var optionalName2: String? = "John";

print(optionalName);	// Optional("Jane");
// 이렇게 나오기 때문에 옵셔널 바인딩이 필요한 것이다.

// 방법 1
if optionalName != nil {
  greeting = "Hello, \(optionalName!)";
} else {
  greeting = "Hi";
}
print(greeting);	// Hello, Jane

// 방법 2 ?? 연산자 사용 
print("Hi \(optionalName ?? "")!")	// Hi Jane!

// 방법 3 (if let, if var) (여러 개의 옵셔널 바인딩을 한꺼번에 할 수 있다)
if let name = optionalName, var friend = optionalName2 {
  greeting = "Hello, \(name). This is \(friend).";
} else {
  greeting = "Hi";
}
print(greeting);	// Hello, Jane. This is John.

두 방법 모두 조건문을 사용하는 방식이다.

첫 번째 방식은 직접 옵셔널 변수를 nil과 비교하는 방식이다. 이 때, else 블록은 선택이다. nil일 때 아무것도 안 하고 싶다면 else 구문을 생략할 수도 있다.

두 번째 방식은 if-let, if-var 구문이다. 그냥 if 뒤로는 하나의 조건문이라고 생각하면 편하다. name 상수를 선언해서 optionalName을 대입하고, friend 변수를 선언해서 optionalName2를 대입하는 것이다. 이 때, optionalName이 nil이거나 optionalName2가 nil이면 else 블록으로 넘어가게 된다.

name 에 optionalString을 할당할 수 있다면, 즉 optionalName이 nil이 아니라면 바로 다음으로 주어지는 블록 안의 문장을 실행하게 되고, nil이라면 else 문에 묶인 중괄호 안의 문장을 실행하게 된다. 만약 else 블록이 없다면 그냥 아무것도 안 하고 탈출한다.

if let과 if var는 별 차이가 없다. 단지 상수로 받을 것인지 변수로 받을 것인지의 차이일 뿐.

웃긴 건, greeting의 타입을 String이 아니라 String?으로 주고 나서 출력하면 Optional() 값이 출력된다. Expression implicitly coerced from 'String?' to 'Any' 라는 경고 메시지가 뜬다. 

 

옵셔널 값을 다루는 또다른 방법은 '??' 쓰기이다. 어떤 변수나 print문에 옵셔널 값을 사용해야 할 때, 옵셔널 변수에 값이 있다면 그 값을 그대로 쓰고, 없다면(=nil이라면) 기본값을 지정해 주는 방법이다. 자바스크립트에서 || 를 쓰는 거랑 비슷한 맥락이라고 생각하면 될 것 같다.

let nickname: String? = "Effy";
let fullName: String = "Elisabeth";
let greeting = "This is \(nickname ?? fullName)";

 

여기서 잠깐, let이라고 옵셔널 변수가 안 되는 건 아니다. let과 var은 상수/변수를 구분하는 키워드이고, ?가 붙고 안 붙고는 옵셔널 변수인지(nil을 허용하는지) 아닌지의 차이를 나타내는 것이다. 상수여도 값이 없을 수 있다. 

 

마지막으로 암시적 추출 옵셔널을 살펴보자. 이 방법은 어떤 변수를 선언할 때, nil이 가끔 들어갈 수는 있는데 사용할 때는 확실히 값이 있어서 프로그램에 지장을 주지 않을 것 같은, 즉 늘 nil인지 확인하기 귀찮은 그런 경우에 사용할 수 있다.

사용 방법은 선언 시에 타입 뒤에 !를 붙여주는 것이다. 위에서 살펴본 강제로 옵셔널 추출하는 방법과는 다르다. 그건 변수 이름 뒤에 !를 붙이는 것이었고, 이건 선언할 때 타입 뒤에 !를 붙이는 것이다.

var score: Int! = 100;
score = nil;	// OK. 만약에 그냥 Int로 선언했으면 지금쯤 뒤집어진다
if var s = score {
  print(s);
} else {
  print("something wrong");
}

 

 

(7) 조건/반복문

조건문이야 다른 프로그래밍 언어랑 비슷하다. 그래서 조건문보다는 반복문을 중심적으로 살펴보려 한다.

단, 조건문의 결과로는 Bool 타입만 들어와야 한다.

즉, 0과 1 등 다른 프로그래밍 언어에서 falsy, truthy한 값이라고 여겨지는 값들을 조건문에 사용할 수 없다는 뜻이다.

루프 변수나 조건문에 괄호를 치고 안 치고는 선택이다. 하지만 중괄호는 필수.

if, if-else 등은 다른 프로그래밍 언어에서 쓰는 것과 비슷하기 때문에 생략한다.

삼항 연산자도 다른 프로그래밍 언어와 비슷한 쓰임새이다.

let score = 80;
print(score >= 75 ? "A or B" : "C or fail")	// A or B

 

switch 문을 살펴보자. 다른 프로그래밍 언어에서 switch 문은 단 하나의 값이나 여러 개의 값 등을 나타내지 범위를 나타내는 건 잘 없는데 스위프트에는 범위 연산자가 있다. 그리고 switch문의 case에 break 문을 써줄 필요가 없다. 어딘가의 case에 걸린다면 case문을 실행하고 자동으로 break되기 때문이다. 만약 명시적으로 switch-case 문의 특정 지점 끝까지 실행하길 원한다면 fallthrough 를 쓸 수 있다. 

var score: Int = 97;

// switch 다음에 변수명을 써준다.
switch score {
  case 0, 1..<50: print("F")	// 0부터 50 미만
  case 50..<65: print("D")	// 50부터 65 미만
  case 65..<75: print("C")	// 65부터 75 미만
  case 75..<90: print("B")	// 75부터 90 미만
  case 90...100: print("A")	// 90부터 100까지
  default: print("잘못된 점수")
}

..< 는 오른쪽 수의 미만이라는 의미이고, ...는 오른쪽 수까지 포함하는 이하 개념이다. x..는 x부터 끝까지, ..x는 x까지를 의미한다. 이 경우, 지정한 x는 범위에 포함된다.

또한, switch 문에서 판별할 변수는 꼭 정수 타입만 가능한 게 아니다. 정수 외 대부분의 기본 타입을 사용 가능하다. String이나 character나.

그리고 switch 문은 default 문을 절대 빠뜨리면 안 된다. default를 빠뜨리면 아예 컴파일 자체가 안 된다. switch must be exhaustive 하면서. default 문은 어떤 경우라도 꼭 써줘야 한다.

 

그 다음은 반복문이다. for 문은 for-in 루프를 쓴다. 다른 블로그 글을 보니 일반적인 C 스타일 for문을 안 쓴다고.

배열이나 딕셔너리, 셋 등을 for-in 루프로 돌 수 있다. 문자열 역시 for-in 루프로 돌 수 있다.

// 1~5까지 루프 돌기
for idx in 1...5 {
  if idx == 2:
    continue
  if idx == 4:
    break	// 4가 되면 탈출
  print(idx);
}

// 반복 횟수만 중요하고 굳이 인덱스가 필요하지 않다면 비울 수 있음
for _ in 0..<5 {
  print("OK");	// OK 5번 출력
}

// stride(from:to:by)는 파이썬의 range와도 비슷하다. (start, end, step)
for n in stride(from: 0, to: 100, by: 10) {
  print(n, terminator=" ");	// 0 10 20 30 40 50 60 70 80 90
}

// 배열 돌기
let immutableArray: [Int] = [1, 2, 3];

for i in immutableArray {
    print(i, terminator: " ");	// 1 2 3
    // 개행 대신 terminator 안의 문자를 출력하는 것으로 print를 마무리함
}

// 문자열 돌기
for c in "Hello" {
    print(c)
}

// 딕셔너리 돌기
var initializedDict: [String: String] = ["name": "K", "gender": "F"];

// 딕셔너리의 경우 순서가 정해져있지 않기 때문에 뭐가 먼저 나올지는 모름
for (k, v) in initializedDict {
    print("\(k): \(v)")
}

다른 반복문으로는 while, repeat-while(do-while)이 있다. 

while 문은 익숙하다. 조건문의 결과가 참인 동안 계속 반복하는 것이다.

repeat-while 은 do-while처럼, 일단 최소 1번은 반복문을 돌고 난 다음 조건문을 검사하는 반복문이다. 

var base = 2;
while base < 100 {
    base *= 2	// 6번
}
print(base)	// 128

base = 2;
repeat {
    base *= 2
} while base < 100	// 6번
print(base)	// 128

 

 

어휴 길어졌다. 다음은 구조체와 클래스! 

 

 

References

 

 

728x90
반응형
0 Comments
댓글쓰기 폼