Swift 기본 문법
상수와 변수
상수(값이 변하지 않는 수): let 상수명: 데이터 타입 = 값
변수(값이 변하는 수): var 변수명: 데이터 타입 = 값
기본 데이터 타입
Int: 64bit 정수형
Uint: 부호가 없는 64bit 정수형
Float: 32bit 부동 소수점
Double: 64bit 부동 소수점
Bool: true, false 값
Character: 문자
Ex) var someCharacter: Character = “가”
String: 문자열
Any: 모든 타입을 지칭하는 키워드
- 타입 추론: 데이터 타입을 명시하지 않아도 알아서 값을 보고 데이터 타입을 추론
ex)var number = 10
컬렉션 타입(collection type)
: 데이터들의 집합 묶음
- Array: 데이터 타입의 값들을 순서대로 저장
Ex)var numbers: Array<Int> = Array<Int>() = var numbers: [Int]() = var numbers: [Int] = []
데이터 추가: append((data)) ex)
numbers.append(1)
중간에 데이터 추가: insert((data), at: (location)) ex)numbers.insert(4, at: 2)
데이터 제거: remove(at: (location)) ex)numbers.remove(at: 0)
- Dictionary: 순서없이 키(key)와 값(value) 한 쌍으로 데이터를 저장
Ex)var dic: Dictionary<String, Int> = Dictionary<String, Int>() = var dic: [String: Int] = [:]
데이터 추가: dic[(key)] = (value)
데이터 제거: removeValue(forKey: (key)) ex)dic.removeValue(forKey: “김경록”)
- Set: 같은 데이터 타입의 값을 순서없이 저장
Ex)var set: Set = Set<Int>()
데이터 추가: insert ex)
set.insert(10)
데이터 제거: remove ex)set.remove(20)
함수 사용법
func 함수명(파라미터 이름: 데이터 타입) -> 반환 타입 {
return 반환 값
}
! 반환타입이 void일 경우 생략가능 !
전달인자 레이블(from, to) 을 사용한 함수
-> 매개변수 역할을 명확히 하기 위해, 코드 가독성 높아진다
func 함수명(전달인자 레이블 매개변수 이름: 매개변수 타입,
전달인자 레이블 매개변수 이름: 매개변수 타입…) -> 반환 타입 {
return 반환 값
}
Ex)
func sendMessage(from myName: String, to name: String){
return “Hello \(name)! I’m \(myName)”
}
sendMessage(from: “Gunter”, to: “Json”)
- 와일드카드 식별자(_)
매개변수 이름을 적어주지 않아도 값으로만 함수의 매개변수 전달 가능
Ex)func sendMessage(_ name: String) -> String { return “Hello \(name)” } sendMessage(“Gunter”)
- 가변 매개변수(…)
매개변수로 몇개의 값이 들어올지 모를때, 0개 이상 값을 받아올 수 있다, 배열처럼 사용 함수마다 1개만 사용가능
Ex)func sendMessage(me: String, friends: String…) -> String { return “Hello \(friends)! I’m \(me)” } sendMessage(me: “Gunter”, friends: “Json”, “Albert”, “Stella”)
! 함수는 1급 객체이기 때문에 변수, 상수로 할당 및 매개변수로 전달 가능 !
조건문
- if:
if 조건식 {
조건식1을 만족할 때 실행할 구문
} else if 조건식2 {
조건식2을 만족할 때 실행할 구문
} else {
아무 조건식도 만족하지 않을 때 실행할 구문
}
- switch:
switch 비교대상 {
case 패턴1:
패턴1 일치할 때 실행되는 구문
case 패턴2, 패턴3:
패턴2, 3이 일치할 때 실행되는 구문
default:
어느 비교 패턴과도 일치하지 않을 때 실행되는 구문
}
- guard
반복문
- for-in: 일정횟수만큼 특정구문을 반복하고자 할 때
for 루프상수 in 순회대상(문자열, 컬렉션 타입, 사잇값) {
실행할 구문
}
- while: 주어진 조건식이 false가 될 때까지 반복
while 조건식 {
실행할 구문
}
- repeat-while: 적어도 한번은 구문을 실행 /c언어의 do-while
repeat {
실행할 구문
} while 조건식
옵셔널(optional)
값이 있을 수도 있고 없을 수(nil)도 있다.
변수에 nil을 지정하기 위해서는 type뒤에 ‘?’를 붙인다.
Ex) var name: String? = “”
(초기화도 가능)
옵셔널이 아닌 변수에 옵셔널 변수를 대입할 수 없다. -> 옵셔널 바인딩
옵셔널바인딩(optional binding)
- 명시적 해제
- 강제 해제 (!)
값이 nil인 변수를 강제 해제하게 될 경우 프로그램이 강제종료될 수 있으므로 위험한 방법
Ex)var number: Int? = 3 print(number!)
- 비강제 해제(옵셔널 바인딩)
- if문
Ex)if let result = number { print(result) } else { // 값 추출 실패시 }
- guard문
Ex)guard let result = number else { return }
- if문
if문 - 추출된 변수를 if문 내에서 사용, guard문 - 함수 전체에서 사용
- 강제 해제 (!)
- 묵시적 해제
-
- 컴파일러에 의한 자동 해제
- 비교 연산자를 사용하면 컴파일러가 자동적으로 옵셔널값 해제
Ex)
if number == 6 { print(“number = 6”) } else { printf(“number != 6”) }
- 컴파일러에 의한 자동 해제
-
- 옵셔널의 묵시적 해제
- 타입 뒤에 “!”가 붙어있는 옵셔널 변수는 사용할 때만 묵시적으로 해제
Ex)
let string = “12” var stringToInt: Int! = Int(string)
- 옵셔널의 묵시적 해제
-
구조체(struct)
- 값 타입
- 구조체 변수를 새로운 변수에 할당할 때마다 새로운 구조체가 할당됩니다.
-
즉 같은 구조체를 여러 개의 변수에 할당한 뒤 값을 변경시키더라도 다른 변수에 영향을 주지 않음
(값 자체를 복사) - 선언:
struct 구조체 이름 {
프로퍼티(멤버변수)와 메소드(멤버함수)
}
- 호출:
var 변수 = 구조체(프로퍼티) // 호출 시 프로퍼티 초기화
- 프로퍼티 접근:
변수.프로퍼티
클래스(class)
- 참조 타입
- ARC로 메모리를 관리
- 상속이 가능
- 타입 캐스팅을 통해 런타임에서 클래스 인스턴스의 타입을 확인할 수 있음
- deinit을 사용하여 클래스 인스턴스의 메모리 할당을 해제할 수 있음
-
같은 클래스 인스턴스를 여러 개의 변수에 할당한 뒤 값을 변경 시키면 모든 변수에 영향을 줌(메모리가 복사 됨)
- 선언:
class 클래스 이름 {
프로퍼티와 메소드
}
- 호출:
var 변수 = 클래스() // **선언시 클래스 내에서 초기화**
클래스와 구조체의 공통점
- 값을 저장할 프로퍼티를 선언할 수 있습니다
- 함수적 기능을 하는 메서드를 선언할 수 있습니다
- 내부 값에 . 을 사용하여 접근할 수 있습니다
- 생성자를 사용해 초기 상태를 설정할 수 있습니다
- extension을 사용하여 기능을 확장할 수 있습니다.
- protocol을 채택하여 기능을 설정할 수 있습니다.
클래스와 구조체의 차이점
struct a { var p1, let p2 } => 값 타입
var instance = a() -> p1 값 변경 가능, p2 값 변경x
let instance = a() - > p1 값 변경x, p2 값 변경x
class b { var p1, let p2 init(){} } => 참조 타입
let instance = a() - > p1 값 변경 가능, p2 값 변경x
Init(initialization, 초기화)
클래스 구조체 또는 열거형의 인스턴스를 사용하기 위한 준비과정
인스턴스가 생성되면 호출
init(매개변수: 타입, …) {
// 프로퍼티 초기화
// 인스턴스 생성시 필요한 설정을 해주는 코드 작성
}
Ex)
Class User {
var nickname: String
var age: Int
init(nickname: String, age: Int) { // 기본 정의 생성자
self.nickname = nickname
self.age = age
}
init(age: Int) { // 사용자 설정 생성자
self.nickname = “Albert”
self.age = age
}
deinit { // 클래스가 더이상 필요하지 않으면(nil 대입) 자동으로 메모리에서 소멸 -> deinit 발생
print(“deinit”)
}
}
프로퍼티(Property)
- 저장 프로퍼티, Stored Property(구조체, 클래스)
인스턴스에 프로퍼티의 값이 저장
Ex)Class User { var nickname: String var age: Int }
- 연산 프로퍼티, Computed Property(구조체, 클래스, 열거형)
getter, setter을 이용해서 값을 연산하고 프로퍼티에 전달
Ex)struct Stock { var averagePrice: Int var quantity: Int var purchasePrice: Int { get { // get만 구현 가능 (읽기전용, 값 변경x) return averagePrice * quantity } set(newValue) { // purchasePrice 값이 변할 때 호출 averagePrice = newValue / quantity } } }
- 프로퍼티 옵저버, Property Observer
(저장 프로퍼티, overriding된 저장프로퍼티, overriding된 계산 프로퍼티)
- 프로퍼티 값의 변화를 관찰하고 반응(set 될때마다)
- willSet: 값이 저장되기 직전에 호출
- didSet: 값이 저장된 직후에 호출
Ex)class Account { var credit: Int = 0 { willSet { print(“잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정”) } didSet { print(“잔액이 \(oldValue)원에서 \(credit)원으로 변경”) } } credit = 1000 // set 되기전 willSet, 된 후 didSet 호출
- 프로퍼티 값의 변화를 관찰하고 반응(set 될때마다)
- 프로퍼티 옵저버, Property Observer
- 타입 프로퍼티, Type Property(저장, 연산 프로퍼티)
인스턴스 생성없이 객체내의 프로퍼티에 접근을 가능하게 함
Ex)Struct S { static var a = “” static var b: Int { return 1 } } S.a S.b
상속
class 클래스 이름: 부모클래스 이름 {
//하위 클래스 정의
}
- base 클래스: 상속받지 않은 클래스
- super 클래스: 부모 클래스, 상위 클래스
- sub 클래스: 자식 클래스, 하위 클래스
-
자식클래스는 부모클래스의 모든 프로퍼티와 메소드를 상속받아 사용가능
-
Overriding
서브클래스는 super클래스의 프로퍼티, 메소드, subscript을 자신만의 기능으로 바꿔 사용
Ex)override func a() {}
-
super 클래스의 메소드를 사용하기 위해서는 ‘super’ 키워드를 사용
Ex)override func a() { super.a() }
-
프로퍼티 override
프로퍼티 자체가 아니라 프로퍼티의 getter, setter, 옵저버를 재정의
Ex)> 계산프로퍼티, 저장프로퍼티를 override한 프로퍼티는 getter, setter를 가질 수 있음override var description: String { return super.description + “ in gear \(gear)” }
> 자식 클래스에서 재정의하려는 프로퍼티는 슈퍼클래스 프로퍼티명과 일치
> super클래스에서 read write -> sub클래스 read only X
> super클래스에서 read only -> sub클래스 read write O
> 상속된 override 프로퍼티에 옵저버도 추가 가능
Ex)
override var currentSpeed: Double { didSet { gear = Int(currentSpeed / 10) + 1 } }
상수 저장 프로퍼티, read only 연산프로퍼티에는 프로퍼티 옵저버 추가x -> 값을 설정할 수 없기 때문
-
Final
메소드, 프로퍼티, type프로퍼티가 override되는 것을 방지
Ex)
final var currentSpeed = 0.0
final class AutomaticCar: Car { }
타입 캐스팅(type casting)
- 인스턴스의 타입을 확인
- 어떠한 클래스의 인스턴스를 해당 클래스 계층 구조의 슈퍼 클래스나 서브 클래스로 취급
- is
Ex)for item in library { if item is Movie { movieCount += 1 } else if item is Song { songCount += 1 } }
- as 형변환(다운캐스팅)
as? : 조건부형식, 다운캐스팅하려는 타입의 옵셔널 값 반환
as! : 강제형식, 강제로 언래핑하여 값 반환, 성공 확신이 있을때만 사용(runtime error)
Ex)for item in library { if let movie = item as? Movie { print(“Movie: \(movie.name), dir. \(movie.director)”) // 다운 캐스팅이 된 타입에 접근이 가능 } else if let song = item as? Song { print(“Song: \(song.name), by \(song.artist)”) } }
assert
- 특정 조건을 체크하고, 조건이 성립되지 않으면 컴파일 에러 메세지를 출력하게 할 수 있는 함수
- assert 함수는 디버깅 모드에서만 동작하고 주로 디버깅 중 조건의 검증을 위하여 사용
Ex)var value = 2 assert(value == 0, “값이 0이 아닙니다”)
guard
- 뭔가를 검사하여 그 다음에 오는 코드를 실행하지 말지 결정 하는 것
- guard 문에 주어진 조건문이 거짓일 때 구문이 실행됨(early exit - 방어코드)
Ex)guard value == 0 else { return }
- guard 문을 이용한 옵셔널 바인딩
Ex)var value: Int? guard let value = value else { return } // 값이 nil일 경우 return print(value)
프로토콜(Protocol)
특정 역할을 하기 위한 Method, Property, 기타 요구사항 등의 청사진
Ex)
protocol SomeProtocol { }
protocol SomeProtocol2 { }
struct SomeStructure: SomeProtocol, SomeProtocol2 { }
class SomeClass: SomeSuperclass, SomeProtocol, SomeProtocol2 {}
// 상속받았다면 super class는 맨앞에 위치
-
Type Property(Static)
Ex)protocol SomeProtocol3 { static var someTypeProperty: Int { get set } }
- function
:매개변수 지정, 구현x
Ex)protocol SomeProtocol4 { func someTypeMethod(int a) }
- initializer
Ex)protocol SomeProtocol5 { init(someParameter: Int) }
- 구조체
struct someStructure: SomeProtocol5 { init(someParameter: Int) { } }
- 클래스
class someClass: SomeProtocol5 { required init(someParameter: Int) { } }
- 상속받을 수 없는 final class일 경우
final class someClass: SomeProtocol5 { init(someParameter: Int) { } }
- 구조체
extension
기존의 클래스, 구조체, 열거형, 프로토콜에 새로운 기능을 추가하는 기능
기존 기능을 override할 수 없다.
- Computed Type Property / Computed Instance Property
- Not Stored Property
- Can’t add a Property Observer to the existing Property
Ex)
extension Int { var isEven: Bool { return self % 2 == 0 } } var number = 3 number.isEven // false
- Not Stored Property
- Type Method / Instance Method
Ex)extension String { func convertToInt() -> Int? { return Int(self) } } var string = “0” string.convertToInt() // 0
- Initaializer
- Subscript
- Nested Type
- Functions for a specific Protocol
열거형(enum)
연관성이 있는 값을 모아 놓은 것
switch구문에서 활용
enum Number { // 하나의 새로운 type처럼 사용 <swift 이름규칙> 이름은 대문자로 시작
case one, two
case three
case four
}
var num = Number.two
num = .one
- 원시값을 가지는 열거형
특정타입으로 지정된 값을 가짐
Ex)enum CompassPoint: String { case north = “북” case south = “남” case east = “동” case west = “서” } var direction = .north
- 원시값 반환
direction.rawValue // “북”
- 열거형 반환
direction = CompassPoint(rawValue: “남”) // south
- 원시값 반환
- 연관값을 가지는 열거형
열거형내 항목이 자신과 연관된 값을 가짐
Ex)enum PhoneError { case unknown case batteryLow(String) } let error = PhoneError.batteryLow(“배터리가 곧 방전됩니다.”)
- 연관값 추출 (if or switch)
switch error { case .batteryLow(let message): print(message) } if error == .batteryLow(let message) { print(message) }
- 연관값 추출 (if or switch)
옵셔널 체이닝(Optional Chaining)
옵셔널에 속해 있는 nil 일지도 모르는 프로퍼티, 메소드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정
옵셔널 바인딩보다 쉽게 접근
Ex)
print(company.developer?.name) // Optional(“name”) -> 옵셔널 바인딩
print(company.developer!.name) // name
try-catch
프로그램 내에서 에러가 발생한 상황에 대해 대응하고 이를 복구하는 과정
swift 런타임 에러 처리
- 발생 (throwing)
- 감지 (catching)
- 전파 (propagating)
- 조작 (manipulating)
swift의 열거형은 오류 원인을 나누고 해당 오류의 특성에 대한 추가정보를 전달하는 모델을 만드는 데 적합
Ex)
enum PhoneError: Error { // Error 프로토콜 채택
case unknown
case batteryLow(batteryLevel: Int)
}
오류 발생: throw PhoneError.batteryLow(batteryLevel: 20)
swift 오류 처리 방법
- throws
함수에서 발생한 오류를 해당 함수를 호출한 코드에 전파
func checkPhoneBatteryStatus(batteryLevel: Int) throws -> String { }
- do-catch
do { try checkPhoneBatteryStatus(batteryLevel: -1) } catch PhoneError.unknown { print(“알 수 없는 에러입니다.”) } catch PhoneError.unknwon(let batteryLabel) { print(“배터리 전원 부족 남은 배터리 : \(batteryLabel)%”) } catch { print(“그 외 오류 발생: \(error)”) }
- try?
에러를 던져주지 않을 경우 옵셔널 값, 에러 발생시 nil 반환
let status = try? checkPhoneBatteryStatus(batteryLevel: 30)
- try!
오류가 발생하지 않을 것이란 확신, 에러 발생시 강제종료
let status = try! checkPhoneBatteryStatus(batteryLevel: 30)
클로저(익명함수 unnamed closure)
코드에서 전달 및 사용할 수 있는 독립 기능 블록이며, 일급 객체의 역할을 할 수 있음
일급객체란 전달 인자로 보낼 수 있고, 변수/상수 등으로 저장하거나 전달할 수 있으며, 함수의 반환 값이 될 수 도 있다.
{ (매개 변수) -> 리턴 타입 //헤드 in
실행 구문 //body
}
Ex)
let hello = { () -> () in
print(“Hello”)
}
hello()
let hello2 = { (name: String) -> String in
return “Hello, \(name)”
}
hello2(“abc”) //전달인자 레이블 사용x
- 함수의 파라미터로 클로저를 전달
func doSomething(closure: () -> ()) { closure() } doSomething(closure: { () -> () in print(“hello”) })
- 함수의 반환타입으로 클로저 사용
func doSomething2() -> () -> () { return { () -> () in print(“hello4”) } } doSomething2()()
- 후행클로저
- 마지막 매개변수로 전달되는 경우
doSomething() { print(“hello2”) }
- 하나의 매개변수만 전달되는 경우
doSomething { print(“hello3”) }
- 마지막 매개변수로 전달되는 경우
- 다중 후행클로저
매개 변수에 클로저가 여러 개있는 경우func doSomething2(success: () -> (), fail: () -> ()) { } —> doSomething2 { //code } fail: { //code }
- 클로저 표현 간소화 문법 최소화
func doSomething3(closure: (Int, Int, Int) -> Int) { closure(1,2,3) } doSomething3(closure: { (a, b, c) in return a+b+c }) doSomething3(closure: { return $0+$1+$2 }) doSomething3(closure: { $0+$1+$2 //단일 리턴문 } doSomething3() { $0+$1+$2 } doSomething3 { $0+$1+$2 }
고차함수
다른 함수를 전달 인자로 받거나 함수 실해의 결과를 함수로 반환하는 함수
map, filter reduce -> collection type
-
- map
- 컨테이너 내부에 기존 데이터를 변형하여 새로운 컨테이너를 생성
- map
let number = [0, 1, 2, 3]
let mapArray = numbers.map { (number) -> Int in
return number * 2
}
-
- filter
- 컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출
- filter
let intArray = [10, 5, 20, 13, 4]
let filterArray = intArray.filter { $0 > 5 } // 간소화
-
- reduce
- 컨테이너 내부의 요소를 하나로 통합
- reduce
let someArray = [1, 2, 3, 4, 5]
let reduceResult = someArray.reduce(0) {
(result: Int, element: Int) -> Int in
return result + element
}