Lower Camel Case: function, method, variable, constant
Upper Camel Case: class, struct, enum, extension
/// for logging
- dump: description property 까지 출력.
/// String Interpolation
import Swift
let age: Int = 10
print("Hello! I am \(age) years old")
print("Hello! I am \(age + 1) years old")
/// constant: let, variable: var
let name: type = value
var name: type = value
type 이 명확하다면 type 은 생략 가능. (type inference)
let sum: Int
sum = inputA + inputB
처럼 상수도 미리 name 과 type 선언 후 나중에 할당할 수 있다.
/// tuple
var topTitle = ("메인화면", "mainIcon.png")
topTitle.0
topTitle.1
var httpError = (404, "not found")
var httpError = (statudCode: 404, description: "not found")
httpError.statusCode
httpError.description
/// Basic data type: Bool, Int, UInt, Float, Double, Character, String
var someBool: Bool = true
var someInt: Int = -100
var someUInt: UInt = 100
var someFloat: Float = 3.14
var someDouble: Double = 3.14
var someCharacter: Character = "a"
var someCharacter: Character = "aaa" // error
var someString: String = "aaa" + "bbb"
someString = someCharacter // error
//MARK: - Any
var someAny: Any = 100
someAny = "can be any type"
someAny = 123.45
//MARK: - nil (see optional together)
someAny = nil
/// Collection type: Array, Dictionary, Set
//MARK: - 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] // error, because now this array is empty
var doubles: Array<Double> = [Double]()
var strings: [String] = [String]()
var characters: [Character] = []
let immutableArray = [1, 2, 3]
// immutableArray.append(4) // error, because this array is constant so immutable
// immutableArray.removeAll() // error, because this array is constant so immutable
//MARK: - Dictionary
// Key 가 String 타입이고 Value 가 Any 인 빈 Dictionary 생성
var anyDictionary: Dictionary<String, Any> = [String: Any]()
anyDictionary["someKey"] = "value"
anyDictionary["anotherKey"] = 100
anyDictionary["someKey"] = "dictionary"
anyDictionary.removeValue(forKey: "anotherKey") // remove value
anyDictionary["someKey"] = nil // remove value
let emptyDictionary: [String: String] = [:]
// emptyDictionary["key"] = "value" // error, because this dictionary is constant so immutable
let initailizedDictionary: [String: String] = ["name": "yagom", "gender": "male"]
// let someValue: String = initializedDictionary["name"] // error, initializedDictionary["name"] could be nil
//MARK: - Set
// 빈 Int Set 생성
var integerSet: Set<Int> = Set<Int>()
integerSet.insert(1)
integerSet.insert(100)
integerSet.insert(99)
integerSet.insert(99)
integerSet.contains(1)
integerSet.contains(2)
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.subtracting(setB)
/// Function
//MARK: - declare function
//MARK: basic form
func function_name(param1_name: param1_type, param2_name: param2_type, ...) -> return_type {
// implementation
// return return_value
}
func sum(a: Int, b: Int) -> Int {
return a + b
}
//MARK: no return value
func function_name(param1_name: param1_type, param2_name: param2_type, ...) -> Void {
// implementation
// return
}
func printMyName(name: String) -> Void {
print(name)
}
func function_name(param1_name: param1_type, param2_name: param2_type, ...) {
// implementation
// return
}
func printMyName(name: String) {
print(name)
}
//MARK: no params
func function_name() -> return_type {
// implementation
// return return_value
}
func maximumIntegerValue() -> Int {
return Int.max
}
//MARK: no params, no return_value
func function_name() -> Void {
// implementation
// return
}
func hello() -> Void { print("hello") }
func function_name() {
// implementation
// return
}
func bye() { print("bye") }
//MARK: - call function
sum(a: 3, b: 5)
printMyName(name: "Wave")
maximumIntegerValue()
hello()
bye()
//MARK: - params default value
// 기본값을 갖는 매개변수는 매개변수 목록 중에 뒤쪽에 위치하는 것이 좋다.
func greeting(friend: String, me: String = "Wave") {
print("Hello \(friend)! I'm \(me)")
}
// 매개변수 기본값을 가지는 매개변수는 생략할 수 있습니다.
greeting(friend: "Jack") // Hello Jack! I'm Wave
greeting(friend: "Jack", me: "Hue") // Hello Jack! I'm Hugh
//MARK: - 전달인자 레이블
// 함수를 호출할 때 매개변수의 역할을 좀 더 명확하게 하거나 함수 사용자 입장에서 표현하고자 할 때 사용
// 함수 내부에서 전달인자를 사용할 때에는 매개변수 이름을 사용한다.
func greeting(to friend: String, from me: String) {
print("Hello \(friend)! I'm \(me)")
}
// 함수를 호출할 때에는 전달인자 레이블을 사용해야 한다.
greeting(to: "Jack", from: "Wave") // Hello Jack! I'm Wave
// 전달인자 레이블을 사용한 함수와 사용하지 않은 함수는 다른 함수로 취급한다.
//MARK: - 가변 매개변수
// 전달 받을 값의 개수를 알기 어려울 때 사용할 수 있다.
// 가변 매개변수는 함수당 하나만 가질 수 있다.
func sayHelloToFriends(me: String, friends: String...) -> String {
print("Hello \(friend)! I'm \(me)")
}
print(sayHelloToFriends(me: "Wave", friends: "Jack", "Hue", "Grantt"))
// Hello ["Jack", "Hugh", "Grantt"]! I'm Wave
print(sayHelloToFriends(me: "Wave") // 가변 매개변수에 값을 주고 싶지 않다면 생략하라.
// Hello []! I'm Wave
// print(sayHelloToFriends(me: "Wave", friends: )) // error
// print(sayHelloToFriends(me: "Wave", friends: nil)) // error
//MARK: - 데이터 타입으로서의 함수
// 스위프트는 함수형 프로그래밍 패러다임을 포함하는 다중 패러다임 언어이다.
// 스위프트의 함수는 일급객체이므로 변수, 상수 등에 저장이 가능하고 매개변수를 통해 전달도 가능하다.
//MARK: - 함수의 타입표현
// 반환타입을 생략할 수 없다.
// (param1_type: param2_type, ...) -> return_type
var someFunction: (String, String) -> Void = greeting(to:from:) // see the greeting function above
someFunction("Wave", "Jack")
someFunction = greeting(friend:me:)
someFunction = ("Wave", "Jack")
// 타입이 다른 함수는 할당할 수 없다.
// someFunction = sayHelloToFriends(me: friends:) // error
func runAnother(function: (String, String) -> Void) {
function("Wave", "Jack")
}
runAnother(function: greeting(friend:me:))
runAnother(function: someFunction)
/// 조건문, break 없어도 break 걸린다. fallthrough 사용하면 break 안걸림.
if (someInteger < 100) {
print("under 100")
} else if someInteger > 100 {
print("above 100")
} else {
print("100")
}
// 범위 연산자 사용
switch someInteger {
case 0, 1:
print("zero one")
// fallthrough
case 2..< 100:
print("1-99")
case 101...Int.max:
print("over 100")
default:
print("unknown")
}
/// 반복문
for integer in integers {
print(integer)
}
// Dictionary 의 item 은 key 와 value 로 구성된 튜블 타입이다.
for (name, age) in people {
print("\(name): \(age)")
}
while integers.count > 1 {
integers.removeLast()
}
// do while 과 동일. do 는 이미 예약어이기 때문에 repeat 을 사용.
repeat {
integers.removeLast()
} while integers.count > 0
/// Optional
let optionalValue: Optional<Int> = nil
let optionalValue: Int? = nil
// 암시적 추출 옵셔널 (Implicitly Unwrapped Optional) (느낌표!)
var optionalValue: Int! = 100
switch optionalValue {
case .none:
print("This Optional variable is nil")
case .some(let value):
print("Value is \(value)")
}
// 기존 변수 처럼 사용 가능
optionalValue = optionalValue + 1
// nil 할당 가능
optionalValue = nil
// 잘못된 접근으로 인한 런타임 오류 발생
optionalValue = optionalValue + 1
// 옵셔널
var optionalValue: Int? = 100
switch optionalValue {
case .none:
print("This Optional variable is nil")
case .some(let value):
print("Value is \(value)")
}
// nil 할당 가능
optionalValue = nil
// 기존 변수처럼 사용 불가 - 옵셔널과 일반 값은 다른 타입이므로 연산 불가
optionalValue = optionalValue + 1
/// 옵셔널 값 추출
// Optional Binding, 옵셔널 값을 꺼내오는 방법 중 하나 (nil 체크 + 안전한값 추출)
func printName(_ name: String) {
print(name)
}
var myName: String? = nil
printName(myName) // 전달되는 값의 타입이 다르기 때문에 컴파일 오류 발생
// if - let
if let name: String = myName {
printName(name) // name 상수는 if-let 구문 내에서만 사용 가능.
} else {
print("myName == nil")
}
// 상수의 사용 범위를 벗어났기 때문에 컴파일 오류 발생.
printName(name)
// 여러 옵셔널 변수들을 한꺼번에 바인딩을 할 수도 있다.
var myName: String? = "Wave"
var yourName: String? = nil
// yourName 이 nil 이리 때문에 실행되지 않습니다.
if let name = myName, let friend = yourName {
print("\(name) and \(friend)")
}
yourName = "Jack"
if let name = myName, let friend = yourName {
print("\(name) and \(friend)")
} // Wave and Jack
// Force Unwrapping
var myName: String? = "Wave"
printName(myName!) // Wave
myName = nil
print(myName!) // error
var yourName: String! = nil // 암시적 추출 옵셔널, 따라서 이 방식은 좋지 않다.
printName(yourName) // nil 값 전달로 인해 런타임 오류 발생
/// Unicode
let inputValue = "7"
if inputValue >- "\u{30}" && inputValue <= "\u{39}" {
print("It is number")
} else {
print("It is not number")
}
"\u{41}" ~ "\u{7a}" = alphabet
/// 구조체
// 값 타입, 상속 불가. Swifit 의 대부분의 타입이 구조체로 정의되어 있다.
// 구조체는 초기값을 넣어주지 않아도 알아서 타입에 따라 넣어주지만 클래스는 초기값이 있어야 한다.
// Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성되어 있다.
// Apple 프레임 워크의 대부분의 큰 뼈대는 모두 클래스로 구성되어 있다.
// 연관된 몇몇의 값들을 모아서 하나의 데이터타입으로 표현하고 싶을때 사용.
// 다른 객체 또는 함수 등으로 전달될 때 참조가 아닌 복사를 원할 때 사용.
// 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속받을 필요가 없을 때 사용.
// Apple 프레임워크에서 프로그래밍할 경우 주로 클래스를 많이 사용한다.
struct name {
// implementation
}
struct Sample {
var mutableProperty: Int = 100 // 가변 프로퍼티
let immutableProperty: Int = 100 // 불변 프로퍼티
static var typeProperty: Int = 100 // 타입 프로퍼티
// 인스턴스 메서드
func instanceMethod() {
print("instance method")
}
// 타입 메서드
static func typeMethod() {
print("type method")
}
}
//MARK: 구조체 사용
var mutable: Sample = Sample()
mutable.mutableProperty = 200
mutable.immutableProperty = 200 // error
let immutable: Sample = Sample()
immutable.mutableProperty = 200 // error
immutable.immutableProperty = 200 // error
Sample.typeProperty = 300
Sample.typeMethod()
mutable = 400 // error
mutable.typeMethod() // error
struct Student {
var name: String = "unknown"
var `class`: String = "Swift"
static func selfIntroduce() {
print("학생 타입 입니다")
}
func selfIntroduce() {
print("저는 \(self.class)반 \(name)입니다")
}
}
Student.selfIntroduce() // 학생 타입입니다
var jack: Student = Student()
jack.name = "jack"
jack.class = "Swift"
jack.selfIntroduce() // 저는 Swift반 jack입니다
/// 클래스
// 구조체는 값 타입이지만 클래스는 참조 타입이다. 다중 상속이 되지 않는다.
// 클래스는 초기값이 있어야 한다. 아니면 옵셔널로 해주어야 한다.
// Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성되어 있다.
// Apple 프레임 워크의 대부분의 큰 뼈대는 모두 클래스로 구성되어 있다.
// Apple 프레임워크에서 프로그래밍할 경우 주로 클래스를 많이 사용한다.
class Sample {
var mutableProperty: Int = 100 // 가변 프로퍼티
let immutableProperty: Int = 100 // 불변 프로퍼티
static var typeProperty: Int = 100 // 타입 프로퍼티
// 인스턴스 메서드
func instanceMethod() {
print("instance method")
}
// 재정의 불가 타입 메서드 - static
static func typeMethod() {
print("type method - static")
}
// 재정의 가능 타입 메서드 - class
class func classMethod() {
print("type method - class")
}
}
// 클래서는 구조체와 다르게 var 나 let 모두로 선언된 경우 mutableProperty 변경이 가능하다.
var mutableReference: Sample = Sample()
mutableReference.mutableProperty = 200
let immutableReference: Sample = Sample()
immutableReference.mutableProperty = 200 // no error
/// enum, 열거형, Swift 의 열거형은 매우 강력한 기능이 있다.
// 상속 불가. 값 타입.
// 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의. (요일, 상태값, 월 등)
// 열거형 자체가 하나의 데이터 타입. 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급
// Swift 의 열거형은 자동으로 값이 할당되거나 하지 않는다.
enum 이름 {
case 이름1
case 이름2
case 이름3, 이름4, 이름5
...
}
//MARK: 열거형 사용
enum Weekday {
case mon
case tue
case wed
case thu, fri, sat, sun
}
var day: Weekday = Weekday.mon
day = .tue
print(day)
switch day {
case .mon, .tue, .wed, .thu:
print("평일입니다")
case Weekday.fri:
print("불금입니다!")
case .sat, .sun:
print("신나는 주말!!")
// 한개라도 빠져있다면 default 해주어야 한다.
}
//MARK: - 원시값, c 언어처럼 정수값을 가질 수도 있다. rawValue를 사용하면 된다. case 별로 다른 값을 가져야 한다.
enum Fruit: Int {
case apple = 0
case grape = 1 // c 언어처럼 자동으로 증가하기 때문에 1 지워도 1이 들어가진다.
case peach
}
print("Fruit.peach.rawValue == \(Fruit.peach.rawValue)")
// Fruit.peach.rawValue == 2
// 정수 타입 뿐만 아니라
// Hashable 프로토콜을 따르는 모든 타입이 원시값의 타입으로 지정될 수 있다.
enum School: String {
case elementary = "초등"
case middle = "중등"
case high = "고등"
case university
}
print("School.university.rawValue == \(School.university.rawValue)")
// School.middle.rawValue == university
//MARK: 원시값을 통한 초기화
// rawValue 를 통해 초기화 할 수 있습니다
// rawValue 가 case 에 해당하지 않을 수 있으므로 rawValue 를 통해 초기화 한 인스턴스는 옵셔널 타입이다.
let apple: Fruit? = Fruit(rawValue: 0)
if let orange: Fruit = Fruit(rawValue: 5) {
print("rawValue 5에 해당하는 케이스는 \(orange)입니다")
} else {
print("rawValue 5에 해당하는 케이스가 없습니다")
}
//MARK: Swift enum 에는 메서드도 추가가 가능하다
enum Month {
case dec, jan, feb
case mar, apr, may
case jun, jul, aug
case sep, oct, nov
func printMessage() {
switch self {
case .mar, .apr, .may:
print("따스한 봄~")
case .jun, .jul, .aug:
print("여름 더워요~")
case .sep, .oct, .nov:
print("가을은 독서의 계절!")
case .dec, .jan, .feb:
print("추운 겨울입니다")
}
}
Month.mar.printMessage()
//MARK: 복잡한 예시
enum BookType {
case fiction(title: String, price: Int, year: Int)
case comics(title: String, price: Int, year: Int)
case workbook(title: String, price: Int, year: Int)
}
extension BookType {
var typeName: String {
switch self {
case .comics:
return "comics"
case .fiction:
return "fiction"
case .workbook:
return "workbook"
}
}
}
var bookStyle: BookType?
var books = [BookType]()
func save Book(book: BookType) {
books.append(book)
}
saveBook(book: .comics(title: "aaa", price: 5000, year: 2020)
saveBook(book: .comics(title: "bbb", price: 6000, year: 2021)
saveBook(book: .comics(title: "ccc", price: 7000, year: 2010)
saveBook(book: .workbook(title: "ddd", price: 7000, year: 2010)
saveBook(book: .fiction(title: "eee", price: 4000, year: 2020)
saveBook(book: .fiction(title: "fff", price: 8000, year: 2015)
for book in books {
if case let BookType.comics(title, _, _) = book {
print(title, book.typeName)
}
if case let BookType.fiction(title, _, _) = book {
print(title, book.typeName)
}
switch book {
case let .comics(_, price, _):
print(price)
case let .fiction(title, _, _):
print(title)
default:
break
}
}
/// 클로저
// 변수, 상수 등으로 저장, 전달 인자로 전달이 가능
// 함수: 이름이 있는 클로저
//MARK: - 정의
{ (매개변수 목록) -> 반환타입 in
// 실행코드
}
// 함수를 사용한다면
func sumFunction(a: Int, b: Int) -> Int {
return a + b
}
var sumResult: Int = sumFunction(a: 1, b: 2)
// 클로저의 사용
var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
return a + b
}
sumResult = sum(1, 2)
print(sumResult)
// 함수는 클로저의 일종이므로 sum 변수에는 함수도 할당할 수 있다.
sum = sumFunction(a:b:)
sumResult = sum(1, 2)
print(sumResult) // 3
//MARK: - 함수의 전달인자로서의 클로저
let add: (Int, Int) -> Int
add = { (a: Int, b: Int) -> Int in
return a + b
}
let subtract: (Int, Int) -> Int
subtract = { (a: Int, b: Int) -> Int in
return a - b
}
let divide: (Int, Int) -> Int
divide = { (a: Int, b: Int) -> Int in
return a / b
}
func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
return method(a, b)
}
var calculated: Int
calculated = calculated(a: 50, b: 10, method: add)
print(calculated) // 60
calculated = calculate(a: 50, b: 10, method: { (left: Int, right: Int) -> Int in
return left * right
})
/// 클로저 고급
// 후행 클로저
// 반환타입 생략
// 단축 인자이름
// 암시적 반환 표현
func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
return method(a, b)
}
var result: Int
//MARK: - 후행 클로저
// 클로저가 함수의 마지막 전달인자라면 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로저를 구현할 수 있다.
result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
return left + right
}
print(result)
//MARK: - 반환타입 생략
// calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 때문에
// 굳이 클로저에서 반환타입을 명시해 주지 않아도 된다. 대신 in 키워드는 생략할 수 없다.
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
return left + right
})
print(result)
// 후행클로저와 함께 사용할 수도 있다.
result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
return left + right
}
//MARK: - 단축 인자이름
// 클로저의 매개변수 이름이 굳이 불필요하다면 단축 인자이름을 활용할 수 있다.
// 단축 인자이름은 콜러저의 매개변수의 순서대로 $0, $1, ... 처럼 표현한다.
result = calculate(a: 10, b: 10, method: {
return $0 + $1
}
//MARK: - 암시적 반환 표현
// 클로저가 반환하는 값이 있다면
// 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급한다.
result = calculate(a: 10, b: 10) {
$0 + $1
}
// 한 줄로 써줄 수도 있다.
result = calculate(a: 10, b: 10) { $0 + $1 }
// 축약하지 않은 클러조 문법과 축약 후의 클로저 문법 비교
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
return left + right
})
result = calculate(a: 10, b: 10) { $0 + $1 }
/// 프로퍼티
// 저장 프로퍼티(stored property)
// 연산 프로퍼티(computed property - 읽기쓰기 / 읽기전용, .. 쓰기전용은 없다.)
// 인스턴스 프로퍼티(instance property)
// 타입 프로퍼티(type property)
// 프로퍼티는 구조체, 클래스, 열거형 내부에서 구현할 수 있다.
// 다만 열거형 내부에는 연산 프로퍼티만 구현할 수 있다.
// 연산 프로퍼티는 var 로만 선언할 수 있다.
//MARK: - 정의
struct Student {
// 인스턴스 저장 프로퍼티
var name: String = ""
var `class`: String = "Swift"
var koreanAge: Int = 0
// 인스턴스 연산 프로퍼티
var westernAge: Int {
get {
return koreanAge - 1
}
set(inputValue) {
koreanAge = inputValue + 1
}
}
// 타입 저장 프로퍼티
static var typeDescription: String = "학생"
// 인스턴스 메서드
// func selfIntroduce() {
// print("저는 \(self.class)반 \(name)입니다")
// }
// 읽기전용 인스턴스 연산 프로퍼티
var selfIntroduction: String {
get {
return "저는 \(self.class)반 \(name)입니다"
}
}
// 읽기전용 타입 연산 프로퍼티, 읽기전용에서는 get 을 생략할 수 있다.
static var selfIntroduction: String {
return "학생타입입니다"
}
}
struct Money {
var currencyRate: Double = 1100
var dollar: Double = 0
var won: Double {
get {
return dollar * currencyRate
}
set {
dollar = newValue / currencyRate
}
}
}
//MARK: - 저장 프로퍼티와 연산 프로퍼티 기능은 지역변수, 전역변수 에서도 사용가능
var a: Int = 100
var b: Int = 200
var sum: Int {
return a + b
}
print(sum)
/// 프로퍼티 감시자
struct Money {
var currencyRate: Double = 1100 {
willSet(newRate) {
print("currencyRate will be changed \(currencyRate) to \(newRate)")
}
didSet(oldRate) {
print("currencyRate has changed \(oldRate) to \(currencyRate)")
}
}
}
// 프로퍼티 감사자 사용, 연산 프로퍼티 안에서는 사용할 수 없다.
var dollar: Double = 0 {
// willSet 의 암시적 매개변수 이름 newValue
willSet {
print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
}
// didSet 의 암시적 매개변수 이름 oldValue
didSet {
print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
}
}
// 프로퍼티 감사자는 지역변수, 전역변수 에서도 사용가능
/// 상속
class 이름: 상속받을 클래스 이름 {
final 키워드 재정의 방지
static 키워드 재정의 불가 타입 메서드
class 키워드 재정의 가능 타입 메서드, final 붙이면 재정의 불가
저장 프로퍼티는 재정의 불가
재정의는
override func selfIntroduce() {
print(...)
super.selfIntroduce()
}
override class func classMethod() {}
}
/// 인스턴스의 생성과 소멸
//MARK: - 프로퍼티 기본값
// 스위프트의 모든 인스턴스는 초기화와 동시에 모든 프로퍼티에 유효한 값이 할당되어 있어야 한다.
// 프로퍼티에 미리 기본값을 할당해두면 인스턴스가 생성됨과 동시에 초기값을 지니게 된다.
//MARK: - 이니셜라이저
// 프로퍼티 기본값을 지정하기 어려운 경우에는 이니셜라이저를 통해 인스턴스가 가져야 할 초기값을 전달할 수 있다.
class PersonB {
var name: String
var age: Int
var nickName: String
init(name: String, age: Int, nickName: String) {
self.name = name
self.age = age
self.nickName = nickName
}
}
let jack: PersonB = PersonB(name: "jack", age: 29, nickName: "잭")
//MARK: 프로퍼티의 초기값이 꼭 필요 없을 때
// 옵셔널을 사용
class PersonC {
var name: String
var age: Int
var nickName: String?
// convenience - 필수조건 사용시 다른 init 을 반드시 실행해야 한다.
convenience init(name: String, age: Int, nickName: String) {
// self.name = name
// self.age = age
self.init(name: name, age: age) // 자신의 이니셜라이저 사용시 convenience 붙여야 함.
self.nickName = nickName
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 암시적 추출 옵셔널은 인스턴스 사용에 꼴 필요하지만 초기값을 할당하지 않고자 할 때 사용
class Puppy {
var name: String
var owner: PersonC!
init(name: String) {
self.name = name
}
func goOUt() {
print("\(name)가 주인 \(owner.name)와 산책을 합니다")
}
}
//MARK: 실패가능한 이니셜라이저
class PersonD {
var name: String
var age: Int
var nickName: String?
init?(name: String, age: Int) {
if (0...120).contains(age) == false {
return nil
}
if name.characters.count == 0 {
return nil
}
self.name = name
self.age = age
}
}
// let jack: PersonD = PersonD(name: "jack", age: 23) // error
let jack: PersonD? = PersonD(name: "jack", age: 23)
let ghost: PersonD? = PersonD(name: "ghost", age: 123)
let buster: PersonD? = PersonD(name: "", age: 10)
print(ghost) // error
print(buster) // error
//MARK: - 디이니셜라이저
// deinit 은 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출된다.
// 인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있다.
var a: Int? = 10
a = nil
/// 옵셔널 체이닝 (optional chaining) 과 nil 병합 연산자 (nil-coalescing operator)
var guardJob: String
guardJob = jack?.home?.guard?.job ?? "배트맨"
/// Type Annotations
var str = "Hello, playground"
var myAge = 0 // 타입 어노테이션을 지정하지 않으면 추론에 의해 Int 형이 된다.
var screenHeight: Float = 570 // 이렇게 우측에 타입을 명확히 지정할 수 있다.
var sum = myAge + Int(screenHeight) // 엄격한 타입. 타입 다르면 + 안된다.
let guestName = "Jack"
/// 타입캐스팅
// 스위프트의 타입캐스팅은 인스턴스의 타입을 확인하는 용도
// 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용
// is, as 를 사용
let someInt: Int = 100
let someDouble: Double = Double(someInt) // 이건 Double 타입의 인스턴스를 하나 더 생성해주는 것이다.
Dictionary, Any, AnyObject 에서 많이 사용.
switch jack {
case jack is Person:
print("jack is Person")
case jack is UniversityStudent:
print("jack is UniversityStudent")
default:
print("nothing")
}
// 업 캐스팅, as 사용 (이건 잘 사용 안한다.)
var mike: Person = UniversityStudent() as Person
var jina: Any = Person() // as Any 생략 가능
//MARK: - 다운캐스팅 (이건 중요)
// as? 또는 as! 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입정보를 전환해준다.
// MARK: 조건부 다운캐스팅
// as?
var optionalCasted: Student?
optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent // nil, if can not be casted
...
// MARK: 강제 다운 캐스팅
// as!
var forcedCated: Student
optionalCasted = mike as! UniversityStudent
optionalCated = jenny as! UniversityStudent // runtime error, if can not be casted
// 활용
func doSomethingWithSwitch(someone: Person) {
switch someone {
case is UniversityStudent:
(someone as! UniversityStudent).goToMT()
case is Student:
(someone as! Student).goToSchool()
case is Person:
(someone as! Person).breath()
}
}
doSomethingWithSwitch(someone: mike as Person)
doSomethingWithSwitch(someone: mike)
doSomethingWithSwitch(someone: jenny)
func doSomethingWithSwitch(someone: Person) {
if let universityStudent = someone as? UniversityStudent {
universityStudent.goToMT()
} else if let student = someone as? Student {
student.goToSchool()
} else if let person = someone as? Person {
person.breath()
}
}
/// assert 와 guard
//MARK: - Assertion
// assert(_:_:file:line:) 함수를 사용합니다
// assert 함수는 디버깅 모드에서만 동작합니다
// 배포하는 애플리케이션에서는 제외됩니다
// 주로 디버깅 중 조건의 검증을 위하여 사용합니다
var someInt: Int = 0
assert(someInt == 0, "someInt != 0")
someInt = 1
// assert(someInt == 0) // 동작 중지, 검증 실패
// assert(someInt == 0, "someInt != 0") // 동작 중지, 검증 실패
// assertion failed: someInt != 0: file guard_assesrt.swift, line 22
//MARK: - Early Exit
// guard 를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료.
// 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작한다.
// guard 의 else 블럭 내부에는 특정 코드블럭을 종료하는 지시어(return, break 등) 가 꼭 있어야 한다.
// 타입 캐스팅, 옵셔널과도 자주 사용된다.
// 그 외 단순 조건 판단 후 빠르게 종료할 때도 용이
func functionWithGuard(age: Int?) {
guard let unwrappedAge = age, unwrappedAge < 130, unwrappedAge >= 0 else {
print("wrong age")
return
}
guard unwrappedAge < 100 else {
return
}
if unwrappedAge < 100 {
} else {
return
}
// if-let 은 블럭 안에서만 사용 가능했지만 guard-let 은 이후 로직에서 계속 적용되서 사용할 수 있다.
print("Your age is \(unwrappedAge)")
}
var count = 1
while true {
guard count < 3 else {
break
}
print(count)
count += 1
}
func someFunction(info: [String: Any]) {
// 딕셔너리에서 받은 값은 대부분 옵셔널이다. 그래서 guard-let 구문을 굉장히 많이 사용한다.
guard let name = info["name"] as? String else {
return
}
guard let age = info["age"] as? Int, age >= 0 else {
return
}
print("\(name): \(age)")
}
someFunction(info: ["name": "jenny", "age": "10"])
someFunction(info: ["name": "mike"])
someFunction(info: ["name": "jack", "age": 10]) // jack: 10
/// 프로토콜
// 프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.
// 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 프로토콜의 요구사항을 실제로 구현할 수 있다.
// 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 '프로토콜을 준수한다(Confirm)'고 표현한다.
// 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 한다.
//MARK: - 정의 문법
protocol Talkable {
// 프로퍼티 요구
// 프로퍼티 요구는 항상 var 키워드를 사용
// get 은 읽기만 가능해도 상관 없다는 뜻이며 get 과 set 을 모두 명시하면
// 읽기 쓰기 모두 가능한 프로퍼티이어야 한다.
var topic: String { get set }
var language: String { get }
// 메서드 요구
func talk()
// 이니셜라이저 요구
init(topic: String, language: String)
}
//MARK: - 프로토콜 채택 및 준수
// Person 구조체는 Talkable 프로토콜을 채택했습니다.
struct Person: Talkable {
var topic: String
var language: String
// 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능하다.
var language: String { return "한국어" }
// 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있다.
func talk() {
print("\(topic)에 대해 \(language)로 말합니다")
}
init(topic: String, language: String) {
self.topic = topic
self.language = language
}
}
//MARK: - 프로토콜 상속
// 프로토콜은 클래스와 다르게 다중상속이 가능하다.
protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
}
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
struct SomeType: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
class SubClass: SuperClass, Writeable, ReadSpeakable {
...
}
//MARK: - 프로토콜 준수 확인
// is, as 연산자 사용해서 확인가능
var someAnyu: Any = sup
someAny is Readable
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
}
/// 익스텐션
// 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능이다.
// 기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다해도
// 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있다.
// 익스텐션으로 추가할 수 있는 기능
// 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
// 타입 메서드 / 인스턴스 메서드
// 이니셜라이저
// 서브스크립트
// 중첩 타입
// 특정 프로토콜을 준수할 수 있도록 기능 추가
// 기존에 존재하는 기능을 재정의할 수는 없다.
//MARK: - 정의 문법
extension 확장할 타입 이름 {
}
extension Int {
var isEven: Bool {
return self % 2 == 0
}
var isOdd: Bool {
return self % 2 == 1
}
}
print(1.isEven) // false
var number: Int = 3
print(number.isEven)
//MARK: 메서드 추가
extension Int {
func multiply(by n: Int) -> Int {
return self * n
}
}
number = 3
print(number.multiply(by: 2))
//MARK: 이니셜라이저 추가
extension String {
init(intTypeNumber: Int) {
self = "\(intTypeNumber)"
}
init(doubleTypeNumber: Double) {
self = "\(doubleTypeNumber)"
}
}
let stringFromInt: String = String(intTypeNumber: 100)
let stringFromDouble: String = String(doubleTypeNumber: 100.0)
/// 오류처리
//MARK: - 오류표현
//Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현합니다.
enum 오류 종류 이름: Error {
case 종류1
case 종류2
case 종류3
// ...
}
// 자판기 동작 오류의 종류를 표현한 VendingMachineError 열거형
enum VendingMachineError: Error {
case invalidInput
case insufficentFunds(moneyNeeded: Int)
case outOfStock
}
//MARK: - 함수에서 발생한 오류 던지기
// 자판기 동작 도중 발생한 오류 던지기
// 오류 발생의 여지가 있는 메서드는 throws 를 사용하여 오류를 내포하는 함수임을 표시한다.
class VendingMachine {
let itemPrice: Int = 100
var itemCount: Int = 5
var deposited: Int = 0
// 돈 받기 메서드
func receiveMoney(_ money: Int) throws {
// 입력한 돈이 0이하면 오류를 던진다.
guard money > 0 else {
throw VendingMachineError.invalidInput
}
// 오류가 없으면 정상처리를 한다.
self.deposited += money
print("\(money)원 받음")
}
// 물건 팔기 메서드
func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
// 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던진다.
guard numberOfItemsToVend > 0 else {
throw VendingMachineError.invalidInput
}
// 구매하려는 수량보다 미리 넣어둔 돈이 적으면 오류를 던진다.
guard numberOfItemsToVend * itemPrice <= deposited else {
let moneyNeeded: Int
moneyNeeded = numberOfItemsToVend * itemPrice - deposited
throw VendingMachineError.insufficientFunds(moneyNeeded: moneyNeeded)
}
// 구매하려는 수량보다 요구하는 수량이 많으면 오류를 던진다.
guard itemCount >= numberOfItemsToVend else {
throw VendingMachineError.outOfStock
}
// 오류가 없으면 정상 처리
let totalPrice = numberOfItemsToVend * itemPrice
self.deposited -= totalPrice
self.itemCount -= numberOfItemsToVend
return "\(numberOfItemsToVend)개 제공함"
}
}
// 자판기 인스턴스
let machine: VendingMachine = VendingMachine()
// 판매결과를 전달받을 변수
var result: String?
//MARK: - 오류처리
// 오류발생의 여지가 있는 throws 함수(메서드)는 try를 사용하여 호출해야 한다.
// try, try?, try!
//MARK: do-catch
// 오류발생의 여지가 있는 throws 함수(메서드)는 do-catch 구문을 활용하여 오류발생에 대비한다.
do {
try machine.receiveMoney(0)
} catch VendingMachinError.invalidInput {
print("입력이 잘못되었습니다")
} catch VendingMachinError.insufficientFunds(let moneyNeeded) {
print("\(moneyNeeded)원이 부족합니다")
} catch VendingMachinError.outOfStock {
print("수량이 부족합니다")
}
do {
try machine.receiveMoney(300)
} catch /*(let error)*/ {
// let error 를 주석처리하면 암시적으로 error 를 사용하면 된다.
switch error {
case VendingMachinError.invalidInput:
print("입력이 잘못되었습니다")
case VendingMachinError.insufficientFunds(let moneyNeeded):
print("\(moneyNeeded)원이 부족합니다")
case VendingMachinError.outOfStock:
print("수량이 부족합니다")
default:
print("알수없는 오류 \(error)")
}
}
do {
result = try machine.vend(numberOfItems: 4)
} catch {
print(error)
}
do {
try result = try machine.vend(numberOfItems: 4)
}
//MARK: try? 와 try!
// try?
// 별도의 오류처리 결과를 통보받지 않고 오류가 발생했으면 결과값을 nil 로 돌려받을 수 있다.
// 정상동작 후에는 옵셔널 타입으로 정상 반환값을 돌려 받는다.
result = try? machine.vend(numberOfItems: 2)
result // Optional("2개 제공함")
result = try? machine.vend(numberOfItems: 2)
result // nil
// try!
// 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때
// try! 를 사용하면 정상동작 후에 바로 결과값을 돌려받는다.
// 오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.
/// 고차함수 high order function
// 전달인자로 함수를 전달받거나 함수실행의 결과를 함수로 반환하는 함수
// map, filter, reduce
//MARK: - map
// 컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너 생성
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]
//MARK: for 구문 사용
doubledNumbers = [Int]()
strings = [String]()
for number in numbers {
doubledNumbers.append(number * 2)
strings.append("\(number)")
}
print(doubledNumbers)
print(strings)
//MARK: map 메서드 사용
doubledNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
doubledNumbers = numbers.map { $0 * 2 }
//MARK: filter 메서드 사용
let evenNumbers: [Int] = numbers.filter {
(number: Int) -> Bool in
return number % 2 == 0
}
print(evenNumbers)
// 매개변수, 반환 타입, 반환 키워드 생략, 후행 클로저
let oddNumbers: [Int] = numbers.filter {
$0 * 2 != 0
}
print(oddNumbers)
//MARK: reduce 메서드 사용
let sum: Int = someNumbers.reduce(0, {
(first: Int, second: Int) -> Int in
return first + second
})
print(sum)
let sumFromThree = someNumbers.reduce(3) { $0 + $1 }
/// Generic
// generic <Type 내가 정한 임의의 타입>
// 로직 반복, 타입 여러가지
// stack
struct IntStack {
var items = [Int]()
// struct 에서는 기본적으로 값이기에 내부의 변수 변경이 불가하다. 하려면 mutating 을 붙여주어야 한다.
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int? {
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
var myStack = IntStack()
myStack.push(item: 4)
myStack.push(item: 5)
myStack.push(item: 6)
myStack.pop()
myStack.pop()
myStack.pop()
myStack.pop()
// where 쓰면 타입 후보군에 제한을 줄 수 있다. Numeric, StringProtocol, Equatable, ...
struct MyStack<MyType> where MyType: Numeric {
var items = [MyType]()
// struct 에서는 기본적으로 값이기에 내부의 변수 변경이 불가하다. 하려면 mutating 을 붙여주어야 한다.
mutating func push(item: MyType) {
items.append(item)
}
mutating func pop() -> MyType? {
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
var myStack1 = MyStack<String>()
myStack1.push(item: "a")
myStack1.pop()
var myStack2 = MyStack<Int>()
myStack2.push(item: 1)
myStack2.pop()
var myStack3 = MyStack<[String: Any]>() // Dictionary Type, where Equatable 일 경우 Any 일 경우 오류를 낸다.
myStack2.push(item: 1)
myStack2.pop()
딕셔너리를 요즘 잘 사용 안한다. find, filter 등 값을 찾아내는 경우 어려울 수 있기 때문이다.
struct 나 class 같은 것으로 타입을 만들어 사용하는게 나을 수 있다. 이때 Equatable 을 지정해주어야 제너릭 타입에 넣어줄 때 에러가 안난다.
'MAC & iOS' 카테고리의 다른 글
LifeCycle (0) | 2022.04.01 |
---|---|
ios simulator sqlite path (0) | 2022.02.16 |
swift 창시자 구글 브레인팀 합류 (0) | 2021.10.17 |
iOS UI 갱신 관련 메소드 및 DispatchQueue, GCD Main Thread (0) | 2021.09.11 |
iOS 개발을 하며 React 의 함수형개발방식이 그냥 옵션일 뿐임을 상기시켜주었다. (0) | 2021.09.08 |