[부스트코스] iOS 프로그래밍을 위한 스위프트 기초 #1

백승호·2020년 8월 12일
0
post-thumbnail

링크: https://www.edwith.org/boostcamp_ios/

네이버 커넥트 재단에서 xcode를 활용해 ios프로그래밍을 하기 전에 기초적인 스위프트 문법을 알려주는 강의가 있어서 수강해보았다.

수강에 걸린 총 시간은 하루 날 잡고 공부해서 4일정도 소요된 것 같다. 영상 하나 하나의 길이는 짧지만 새로운 내용들의 연속이였기 때문에 듣다가 멈춰서 정리하고, 또 듣고 멈추고를 반복했다.

화면을 반으로 갈라서 한쪽에는 노션, 한쪽에는 강의를 틀어놓고 공부했는데 듀얼모니터의 필요성을 뼈저리게 느낀 시간이였다.

아무튼, 노션에 정리한 내용을 여기에도 기록해 두고자 글을 작성한다.


1. 콘솔로그

프린트랑 덤프라는 함수 제공

  • print 는 단순 문자열 출력
  • dump는 인스턴스의 자세한 설명까지 출력

streaming으로 해서 파일에 남길수도있긴함

2. 문자열 보간법

  • string interpolation
  • 프로그램 실행 중 문자열 내에 변수 또는 상수의 실질적인 값을 표현하기 위해 사용
  • () 사이에 변수를 넣으면 실질적인 값이 나타나게됨
import Swift

let age: Int = 10

"안녕하세요! 저는 \(age)살입니다"
"안녕하세요! 저는 \(age + 5)살입니다" 수식계산도 가능

이걸 콘솔로그로 보고싶으면 print, dump사용하면됨
print("안녕하세요! 저는 \(age)살입니다")
class Person {
	var name: String = "sebaek"
	var age: Int = 10
}

let sebaek: Person = Person()
print(sebaek)
print("\n------\n")
dump(sebaek)

-----------------------------------------------------
__lldb_expr_256.Person #0

// 단순히 Person의 인스턴스를 보여줌.
-------
__lldb_expr_256.Person #0
- name: "sebaek"
- age: 10

// dump를 사용하면 인스턴스의 자세한 설명(description 프로퍼티)도 볼수있음

3. 상수와 변수 let, var

스위프트는 함수형 패러다임..불변객체를 중요시 생각함

  • 상수

    let 이름: 타입 = 값

let constant: String = "변경 불가능한 상수"

  • 변수

var 이름: 타입 = 값

var variable: String = "변경 가능한 변수"

variable = "다른 값으로 변경"

주의할점.

띄어 쓰기와 콜론 중요하다.

let은 상수로 선언한 것이기 때문에 값을 변경할 수 없다.

"fix it 선택을 해주면 선언시 let이 var로 바뀐다"

나중에 값을 할당하고 싶다면 이름과 타입을 명시해줘야한다.

let 같은 경우는 처음 한번만 할당해줄 수 있다.

값이 초기화 되지 않은 상태로 함수에 전달하면 컴파일러가 에러를 뱉어낸다.

4. 기본 데이터 타입

불린, 인티져, 캐릭터, 플롯 등

Bool

var someBool: Bool = true
someBool = false
//someBool = 0
//someBool = 1
//Bool타입변수에 왜 Int형 값을 넣었냐 하며 컴파일에러

Int

var someInt: Int = -100
someInt = 100
//someInt = 100.1
//오류 발생. 왜 Int타입에 Double타입을 넣어주려하느냐!

UInt

var someUInt: UInt = -100
//someUInt = -100
//오류 발생. 왜 UInt타입에 음수를 넣으려 하느냐

Float(32bit)

var someFloat: Float = 3.14
someFloat = 3
//정수를 넣어줘도 실수는 무리없이 받아들인다.

Double(64bit)

var someDouble: Double = 3.14
//someDouble = someFloat
//더블타입에 왜 플롯을 넣으려하느냐. 안된다

Character

var someCharacter: Character = "🧑"
// 유니코드로 표현할수있는 모든 문자를 넣어줄수있어
// 문자열과 똑같이 큰따옴표로 묶어줌
// 그런데!
// someCharacter = "하하하"
// 캐릭터가아닌 문자열 타입이다! 안된다!

String

var someString: String = "하하하 😂 "
someString = semeString + "웃으면 복이와요"
print(someString)
//문자열이 합해질수 있음! 단!!
//someString = someCharacter
//문자열은 문자를 수용할 수 없음!!!!

스위프트는 자료형의 형변환에 엄격한 기준을 가지고있음

각각 어떤 타입이 될까?

let integer = 100 // Int

let floatingPoint = 12.34 // Double

let apple = "A" // String

  • 힌트 type(of: )

5. Any, AnyObject, nill

Any : swift의 모든 타입을 지칭하는 키워드

AnyObject: 모든 클래스 타입을 지칭하는 프로토콜

nil: 없음을 의미하는 키워드

Any

var someAny: Any = 100
someAny = "어떤 타입도 수용가능"
someAny = 12.14

let someDouble: Double = someAny
//마지막에 할당한 타입대로 할당해주려하는데 오류 발생

AnyObject

클래스의 인스턴스를 쥐고있을수있음!
class SomeClass {}
var someAnyObject: AnyObject = SomeClass()

semeAnyObject = 123.13
// 클래스 인스턴스가 아닌 기본 데이터타입을 주려 하니 오류 발생

nil

someAny = nil
// 모든 타입을 넣어줄 수 있다그랬는데 nil을 넣어주려하면 오류 발생
// 빈값은 들어올수 없다라는것
someAnyObject = nil
// AnyObject역시 불가능

왜 필요할까?
옵셔널이라는 부분에서 필요해진다

6. 컬렉션 타입

Array, Dictionary, Set

Array는 순서있는 리스트 컬렉션

Dictionary는 키와 값의 쌍으로 이루어진 컬렉션

Set 순서 없고 중복 허용하지 않는 컬렉션

Array

var integers: Array<Int> = Array<Int>()
integers.append(1) 
integers.append(100)

integers.contains(100)
integers.contains(99)

integers.remove(at: 0)
integers.removeLast()
integers.removeAll()

integers.count

integers[0]
// 주의. 비어있는 상태에 0번째 인덱스로 접근하려하면 프로그램이 종료된다

.append() - 배열의 맨뒤에 ()안의 요소 추가

.contains() - 배열에 ()안에 있는 값이 있는지 확인해서 true나 false리턴

.remove(at: 인덱스) - 배열의 인덱스에 해당하는 값을 삭제해준다

.removeLast() - 맨 마지막 멤버를 삭제해준다

.removeAll() - 배열의 값 초기화 해준다

.count - 몇개가 들어있는지 세준다

Array의 다양한 표현방법

var integers: Array<Int> = Array<Int>()
var doubles: Array<Double> = [Double]()
var strings: [String] = [String]()
var characters: [Character] = []
let 을 사용하여 Array를 선언하면 불변 Array가 된다
미리 요소들을 선언할수도 있음

let constantArray = [1, 2, 3]

이 경우,
constantArray.append(4)
constantArray.removeAll() 
불가능

Dictionary

Key가 String 타입이고 Value가 Any인 빈 Dictionary 생성

var anyDictionary: Dictionary<String, Any> = [String: Any]()
anyDictionary["someKey"] = "value"
anyDictionary["anotherKey"] = 10

anyDictionary.removeValue(forKey: "anotherKey")
anyDictionary["someKey"] = nil

let emptyDictionary: [String: String] = [:]
// emptyDictionary["key"] = "value" 불가능. let으로 선언했기 때문에 그냥 비어있는 딕셔너리임

let initializeDictionary: [String: String] = ["name":"sebaek", "gender":male]
// let someValue: String = initializedDictionary["name"]
// 밸류값 알아내서 담아내고 싶은데 그게 안돼네?
// 이유가 뭘까..딕셔너리의 키에 해당하는 값이 있을수도 있고 없을수도 있기 때문
// 값이 없다면 String타입의 상수에 값을 넣어줄수 없는 경우가 생기므로 컴파일 불가!

var 이름: Dictionary<자료형, 자료형> = Dictionary<자료형, 자료형>()
var 이름: Dictionary<자료형, 자료형> = [:]
var 이름: [자료형: 자료형] = Dictionary<자료형, 자료형>()
var 이름: [자료형: 자료형] = [자료형: 자료형]()
var 이름: [자료형: 자료형] = [:]
var 이름 = [자료형: 자료형]()

딕셔너리 콜렉션 선언후
이름[] = 값

혹은

선언과 동시에 초기화
var 이름: [자료형: 자료형] = [:,:]

딕셔너리[키] = 밸류

.removeValue(forKey: "anotherKey") - 키에 할당된 값 제거

딕셔너리[키] = nil - 키에 할당된 값 제거

Set

var integerSet: Set<Int> = Set<Int>()
integerSet.insert(1)
integerSet.insert(100)
integerSet.insert(99)
integerSet.insert(99)

integerSet.contains(99)

integerSet.remove(100)
integerSet.removeFirst()
integerSet.count

let setA: Set<Int> = [1,2,3,4,5]
let setB: Set<Int> = [3,4,5,6,7]

let union: Set<Int> = setA.union(setB)
let sortedUnion: [Int] = union.sorted()
let intersection: Set<Int> = setA.intersection(setB)
let subtracting: Set<Int> = setA.subtraction(setB)

.insert(값) - 같은 값을 insert해도 값은 하나만 존재함. 집합이기때문

.contains(값) - 집합에 값이있는지 없는지

.remove(값) - 요소 제거

.count - 몇개인지 셀수있다

.unions(집합) - 해당 집합과 인자로 받은 집합의 합집합

.intersection(집합) - 해당 집합과 인자로 받은 집합의 교집합

.subtracting(집합) - 해당 집합과 인자로 받은 집합의 차집합

.sorted() - 집합의 요소를 정렬

7. 함수 기본

함수 작성

func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 ...) -> 반환타입
{
	함수 구현부
	return 반환값
}

example

func sum(a: Int, b:Int) → Int

{

return a+b

}

반환값이 없는 함수라면 → Void를 사용하거나 아예 생략도 가능

매개변수가 없는 함수라면 괄호 안을 비워줘도 된다.

함수 호출

sum(a: 3, b: 5) 함수의 이름과 매개변수의 이름에 값 대입

8. 함수 고급

기본값(?)을 갖는 매개변수

func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 = 기본값 ...) -> 반환 타입
{
	함수 구현부
	return 반환값
}

왜 기본값이 필요한거지? 기본값을 줄거면 C는 무조건 호출하는쪽에서 저장해두고 넘겨줄텐데
아래 같은 경우?

func greeting(friend: String, me: String = "Sebaek") -> void
{
	print("Hello \(friend)! I'm \(me)")
}
근데 이럴거면 그냥 sebaek이라고 쓰면 되지않나. 뭔가 나중에 기본값을 바꿔줄수 있어지나?

기본값을 갖는 매개변수 호출

greeting(friend: "hana")
위와 같이 기본값을 갖는 매개변수는 생략가능
greeting(friend: "john", me: "eric")
이렇게 호출하면 기본값을 바꿔줄수도 있넹

흠..그럼 기본값 갖는게 두개면 어케 지정하지? -> 스위프트은 함수쓸때 값을 매개변수이름: 값 이렇게 지정하잖아!
일단 이렇게 쓸려고 기본값을 갖는 매개변수를 목록중 뒤쪽에
위치시키는게 좋다는거군..

전달인자 레이블

함수를 호출할때 매개변수의 역할을 좀 더 명확하게 하거나
함수 사용자의 입장에서 표현하고자 할 때 사용한다

func 함수이름(전달인자 레이블  매개변수1이름: 매개변수1타입, 전달인자 레이블  매개변수2이름: 매개변수2ㅌ타입 ...) -> 반환타입
{
	함수 구현부
	return
}

func greeting(to friend: String, from me: String)
{
	print("Hello \(friend)! I'm (me)")
}

호출 시
greeting(to: "hana", from: "sebaek")
이렇게 매개변수 이름 앞에 '레이블'을 지정해서 호출시 좀더 명확한 표현이 가능한가보다

전달인자 레이블의 함수를 정의할땐 함수 내부에서 매개변수의 이름을 사용하는 반면
전달인자 레이블이 담긴 함수를 호출할땐 매개변수의 이름이 아닌 레이블을 사용해야한다
 
추가로, 이렇게 레이블을 지정한 함수는 더 위에서 정의한
greeting함수와 다른 이름인 것으로 적용된다. 오오..
이러면 쓸만할거같다..같은 이름인것 같지만, 다른함수로 취급되는것

가변 매개변수(이거 알아! C에서 va_arg로 쓰자너 ...으로 표현하고. 스위프트는?

func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입...) -> 반환타입
{
	함수 구현
	return
}
저렇게 맨뒤에 ...을 띄어쓰기 없이 붙여줘서 사용한다
또, 가변매개변수는 함수당 하나만 사용할수 있다.

친구가 몇명이 들어오든 상관없게..
func sayHelloToFriends(me: String, friends: String...) -> String
{
	return "Hello \(friends)! I'm \(me)!"
}
print(sayHelloToFriends(me: "sebaek", friends: "hana", "eric", "wing"))
//Hello ["hana", "eric", "wing"]! I'm yagom!
print(sayHelloToFriends(me: "sebaek")
//Hello []! I'm sebaek!
가변인자를 사용하고싶지 않다면 아예 생략하면된다.

스위프트는 함수형 프로그래밍 패러다임을 갖고있기 때문에 스위프트의 함수는 1급 객체이다.

따라서 변수나 상수에 저장될수있고 매개변수를 통해 전달될수있다.

하나의 데이터 타입으로서 표현될 수 있다.

함수의 타입표현

var 이름: (매개변수1타입, 매개변수2타입 ...) -> 반환타입
// 이 땐 반환타입 생략 불가능

var someFunction: (String, String) -> Void = greeting(to:from:)
매개변수 타입이 둘다 String이고, 반환값이 Void인 함수만 받아줄 수 있다.
= 으로 위에서 정의한, to from 레이블을 사용하는 greeting함수를 받아준다.

someFunction("eric", "sebaek") //레이블

someFunction = greeting(friend:me:)
someFunction("eric", "sebaek") //매개변수명

타입이 다른 함수는 할당할 수 없다.
아까 가변인자 매개변수를 가지는 함수를 생각해보면
// someFunction = sayHelloToFriends(me:friends:)
// 오류 발생!

함수의 타입을 매개변수타입으로 넣어줄수도있다! String, Int가 아닌 
함수타입으로!
func runAnother(function: (String, String) -> Void)
{
	function("jenny", "mike")
	//함수 안에서, 매개변수로 받아온 함수를 호출해줄 수도 있다!
}
runAnother(function: greeting(friend:me:))
//함수를 직접넘겨줄수도, 함수를 담는 변수를 넘겨줄수도있다 
runAnother(function: someFunction)

9. 조건문

if (condition) {
<} else if (condition2) {
} else {
} 
여기서 소괄호는 생략 가능., 중괄호는 생략 불가능. 한 줄이라도 중괄호로 무조건 감싸줘야한다.

비교값은 무조건 bool 타입으로 나와야하므로, 반환형이 Int
인 함수로 0 1 값에 따라 비교문을 작성하면 오류발생한다.
switch value {
case val1:
		code
case val2:
		code
case val3:
		code
default:
		code
}
switch case에 범위 연산자를 유용하게 사용할 수 있다.

switch someInteger {
case 0:
		print("zero")
case 1..<100:
		print("1~99")
case 100:
		print("100")
case 101...Int.max:
		print("over 100")
default:
		print("unknown")
}

범위연산자 ...

  • 1이상 100미만 ⇒ 1..<100
  • 101이상 ~ 최대값 이하 ⇒ 101...Int.max
switch "sebaek" {
case "jake":
	print("jake")
case "mina":
	print("mina")
case "sebaek":
	print("sebaek")
default:
	print("unknown")
}

switch 구문의 경우 명확히 케이스가 다 명시되지 않는한 default가 필요함!

그리고 케이스구문 다음에 break 가 없어도 무조건 브레이크가 걸린다!!!

case "jake", "mina":

print("jake")

대신 이런식으로 콤마로 묶어줄수 있다.

break를 쓰지 않은것처럼 동작하게 하려면 명시적으로 fallthrough를 밑에 넣어주면 된다..

반복문

collection타입과 많이 사용됨!

var integers = [1, 2, 3]
let people = ["sebaek": 10, "eric": 15, "mike": 12]
선언해두고 시작해보자

for-in 구문

for item in 콜렉션 {
	code
}

for i in integers {
	print(i)
}
이터레이션을 돌면서 안의 구문을 반복실행
(integers는 [1, 2, 3]배열임!)

for (name, age) in people {
	print("\(name): \(age)")
}
딕셔너리의 경우 이터레이션이 튜플 타입으로 들어감., 밸류가 들어가게됨

while

while (조건) {
	code
}

repeat-while

기존의 
do {
	코드
} while (조건)
처럼 작동!

repeat {
	코드
} while (조건)

do 라는 이름을 안쓰는 이유는 do가 오류처리 구문에 사용되는
키워드이기 때문이다. do try catch

11. 옵셔널

스위프트 핵심개념!
옵셔널: 값이 있을수도 없을수도!

let x: Int = nil
옵셔널(?)을 안주면 nil을 받을 수 없음

let y: Int? = nil

왜 필요한거야?
nil의 가능성을 명시적으로 표현하는것이다.

nil의 가능성을 문서화하지 않아도 코드만으로 충분히 표현가능해진다
-> this value must not be nil! 이런 글 써주지않아도댐

전달받은 값이 옵셔널이 아니라면 nil체크를 하지 않더라도 안심하고 사용할 수 있다.
-> 효율적인 코딩, 예외 상황을 최소화 하는 안전한 코딩!
옵셔널이 아닌 Int타입
func f(x: Int) {
}
옵셔널이 명시된 Int타입
func f(y: Int?) {
}
옵셔널이 아닌타입에는 nil을 보낼 수 없어
컴파일시 에러를 뱉기 때문에 안전하게 코딩 가능!
옵셔널은 열거형 enum과 general의 합작품이다

enum Optional<Wrapped> : ExpressibleByNilLiteral {
	case none
	case some(Wrapped)
}
옵셔널에 값이 있다, 값이 없다 두가지 열거형으로 구성이 되어있다.

실제로 옵셔널을 선언할때 타입은 Optional<Int>하는게 완전한 문법이지만
?로 대체 가능

let optionalValue:Optional<Int> = nil
let optionalValue: Int? = nil

?와 !

! 로 표현하는것은 암시적 추출 옵셔널
var optionalValue: Int! = 100 
이렇게 !를 붙여준것이 암시적 추출 옵셔널 형식이다

옵셔널의 경우 열거형이기 때문에 switch case로 구분해줄수있다
값이 없으면 .none: 있으면 .some(let value): 케이스로 구분해줄수 있다.

var optionalValue: Int! = 100

switch optionalValue {
case .none:
	print("This optional variable is nil")
case .some(let value):
	print("Value is \(value)")
}
이런식으로

암시적 추출 옵셔널 형식은
기존 변수처럼 사용할 수 있다
nil을 할당할수도 있되 nil을 할당하고나면 변수처럼 연산을 해주진못한다
optionalValue = optionalValue + 1
optionalValue = nil
// 불가능 optionalValue = optionalValue + 1
 
? 를 쓴 옵셔널은 일반적인 옵셔널
타입뒤에 ?를 써서 값이 있을수도 있고 없을수도 있는 변수다~
마찬가지로 switch문을 써서 값을 구분해줄 수 있다

var optional: Int? = 100

switch optional {
case .none:
	print("This variable is nil")
case .some(let value)
	print("This value is \(value)")
}
마찬가지로 nil을 할당할 수 있다
그런데 기존 변수처럼 사용할 수는 없다
// 불가능 optional = optional + 1
옵셔널과 일반 값은 다른 타입이므로 연산불가능하다
그럼 어떻게 값을 꺼내서 쓰고 활용할 수 있지?

12. 옵셔널 추출

옵셔널을 어떻게 활용해 볼 수 있을까?
옵셔널의 값을 꺼내는 방법은 옵셔널 바인딩과 강제 추출 방식이있다
- optional binding은 nil 체크와 동시에 안전하게 값을 추출할 수 있는 방법이다
- force unwrapping은 옵셔널의 값을 강제로 추출하는 방식이다
optional binding
Int에 42 라는 값이 있다고 생각했을때
옵셔널로 ?가 붙은 자료형에 들어간 값은 보호막이 하나씩 있다고 생각하면 된다
Int?, Int! 마찬가지. 변수 두개가 있고42라는 값이 있는 상태가 하나 있고 nil인 상태가
하나 있다고 생각해보자
optional binding의 경우 값이 있습니까 똑똑 두드려보고 없으면 지나치고
있으면 그 값을 꺼내오는방식

func printName(name: String) -> void {
	print(name)
}
var myName: String? = nil
printName(myName)
//넣어주는 변수가 옵셔널 타입의 스트링이기때문에 타입이 서로 달라 컴파일 오류 발생

우리는 if - let 방식으로 옵셔널 바인딩을 해줄 수 있다

func printName(name: String) -> void {
	print(name)
}
var myName: String! = nil
if let name: String = myName {
	printName(name)
} else {
	print("myName is nil")
}

name 이라는 상수는 if let 구문 안에서만 사용 가능!
여러 변수들을 한번에 바인딩 할 수도 있음

var myName: String? = "sebaek"
var yourName: String? = nil
if let name = myName, let friend = yourName {
	print("\(name) and \(friend)")
}
// yourName이 nil이기 때문에 실행되지 않는다

yourName = "hana"
if let name = myName, let friend = yourName {
	print("\(name) and \(friend)")
}
// sebaek and hana

이렇게 쉼표를 사용해서 여러 옵셔널을 엮을 수 있음
Force unwrapping 옵셔널 강제추출
노크해서 정중하게 값을 꺼내오는게 아니라 옵셔널의 보호막을 강제로 깨부수고
값을 가지고 나오는거라고 생각하면된다

func printName(name: String) {
	print(name)
}

var myName: String? = "sebaek"
printName(myName!)

이렇게 !를 붙여서 강제로 값을 꺼내온다. 그래서 옵셔널 타입이 아닌 스트링타입으로 값을 전달해줄 수 있게 된다.
그런데 그렇게 꺼내온게 만약 nil값이라면 런타임 오류가 발생한다

myName = nil
print(myName!)
// 강제추출시 값이 없으므로 런타임 오류 발생

var yourName: String! = nil
암시적 추출 옵셔널(!)는 처음에 선언할때부터 저런 상황을 가정하고 선언하는것이다
강제추출할거라는걸 생각해두고 선언한거지
그래서
printName(yourName)
이렇게 !를 붙이지 않아도 강제추출이 적용된다. 전달하는 상황에 !가 자동으로 붙는식.
그래서 변수처럼 연산이 가능했던 거구나
그러나 안에 들어있는 값이 nil이였기 때문에 런타임 오류가 발생한다
profile
삽질하는 개발자 지망생

0개의 댓글