iOS

[iOS] Swift 기본 문법 (YouTube - yagom)

빨간체리반지 2020. 10. 26. 16:30

# 이름짓기

function, method, variable, constant : Lower Camel Case 사용

class, struct, enum, extension: Upper Camel Case 사용

 

# 콘솔로그 printdump

print : 단순 문자열 출력

dump : 인스턴스의 자세한 설명(description 프로퍼티)까지 출력

(streaming 을 이용해 파일로 로그 기록할 수 있다)

 

# 문자열 보간법  \() 

"안녕하세요 제 이름은 \(name)입니다."


# 변수(var)와 상수(let)

var : 차후 값이 변경되는 변수 선언시 사용

let : 차후 값이 변경되지 않는 변수 선언시 사용

 

 

(TIP)

// ` 사용하면 사용불가 한 변수명 사용가능해짐
var `class`: String = "Sample"

 


# 기본 데이터 타입

Bool, Int, UInt, Float, Double, String, Character

* Character 타입은 " 로 값 초기화

* String, Int, Double은 구조체로 선언되어 있다

 

# 컬렉션 타입

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

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

Set : 순서가 없고, 멤버가 유일한 컬렉션

 

# Any, AnyObject

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

AnyObject : 모든 클래스 타입을 지칭하는 키워드


# 함수 고급 (기본 함수형태는 생략)

 

* 매개변수 기본값

func funcName(param1: String, param2: String = "hi") { /* 함수 구현부 */ }

 

* 전달인자 레이블

func funcName(a param1: String, b param2: String) /* 함수 구현부 */ }

 

* 가변 매개변수

func funcName(param1: String, param2: String...) /* 함수 구현부 */ }

 

* 함수 변수

함수 매개변수로 사용될 수도 있음

var sampleFunc: (String, String) -> Void = funcName(a:b:)


# 조건문

if-else

if 조건 {

    /* 구현 */

} else if 조건 {

    /* 구현 */

} else {

    /* 구현 */

}

 

switch

switch 변수 {

case 값/범위:

    /* 구현 */

case 값/범위:

    /* 구현 */

default:

    /* 구현 */

}

 

# 반복문

for-in

for (key, value) in people { // people은 Dictionary

    /* 구현 */

}

 

while

while 조건 {

    /* 구현 */

}

 

repeat-while

repeat {

    /* 구현 */

} while 조건


# 옵셔널

Non-Optional : 변수값에 nil 비허용

Optional : 변수값에 nil 허용

let someInt: Int = 0 // non-optional

let someInt: Int? = nil // optional

let someInt: Int! = nil // optional (암시적 추출)

 

# 옵셔널 값 추출

 

옵셔널 바인딩

if-let

var myName: String? = "hob"

var yourName: String? = nil

 

if let name = myName, let friend = yourName {

    /* yourName 값이 nil이기 때문에 실행되지 않음 */

}

 

옵셔널 강제 추출

var name1: String! = nil

print(name1) /* 런타임 오류 발생 */

 

var name2: String? = nil

print(name2!) /* 런타임 오류 발생 */


# 구조체

struct Sample {

    var mutableProperty: Int = 100 // 인스턴스 프로퍼티 (가변)

    let immutableProperty: Int = 100 // 인스턴스 프로퍼티 (불변)

 

    static var typeProperty: Int = 100 // 타입 프로퍼티

 

    //  인스턴스 메서드

    func instanceMethod() {

       /* 구현 */

    }

 

    // 타입 메서드

    static func typeMethod() {

       /* 구현 */

    }

}

 

// 타입 프로퍼티 및 메서드

Sample.typeProperty = 200

Sample.typeMethod()

 

// 인스턴스 프로퍼티 및 메서드

var mutable: Sample = Sample() // 가변 인스턴스

mutable.mutableProperty = 200 // 가능

mutable.immutableProperty = 200 // 불가능

 

let immutable: Sample = Sample() // 불변 인스턴스

immutable.mutableProperty = 200 // 불가능

immutable.immutableProperty = 200 // 불가능

 


# 클래스

class Sample {

    var mutableProperty: Int = 100

    let immutableProperty: Int = 100

 

    static var typeProperty: Int = 100

 

    func instanceMethod() {

       /* 구현 */

    }

 

    // 타입 메서드 (재정의 가능)

    static func typeMethod() {

       /* 구현 */

    }

 

    // 타입 메서드 (재정의 불가능)

    class func classMethod() {

       /* 구현 */

    }

}

 

 

// 인스턴스 프로퍼티 및 메서드

var mutable: Sample = Sample() // 가변 인스턴스

mutable.mutableProperty = 200 // 가능

mutable.immutableProperty = 200 // 불가능

 

let immutable: Sample = Sample() // 불변 인스턴스

immutable.mutableProperty = 200 // 가능

immutable.immutableProperty = 200 // 불가능


# 열거형

- enum은 타입이므로 대문자 카멜케이스를 사용하여 이름을 정의

- 각 case는 소문자 카멜케이스로 정의

- 각 case는 그 자체가 고유의 값

 

enum Weekday {

    case mon

    case tue

    case wed, thu, fri, sat, sun  // 연속해서 쓸 수도 있다

 

    func printMsg() { // 메서드를 구현할 수도 있다!!

        switch self {

        case .mon, .tue, .wed, .thu, .fri:

            print("주중")

        case .sat, sum:

            print("주말")

        }

    }

}

 

// C 언어의 enum처럼 원시값(정수값/문자열값)을 가질 수 있음 (rawValue 사용)

// case 별로 각각 다른 값을 가져야 함 (중복 불가)

enum Fruit: Int {

    case apple = 0

    case grape = 1

    case peach      // 값을 안적으면 자동으로 1씩 증가함 (문자열값 생략시 case 이름 그대로를 원시값으로 가짐)

 

// 변수 생성

var day1: Weekday = .mon

var day2 = Weekday.tue

 

// switch & enum

// 모든 case에 enum값이 명시되면 default 쓰지 않아도 됨

switch day {

case .mon, .tue, .wed, .thu:

    print("평일")

case Weekday.fri:

    print("불금")

case .sat, .sun:

    print("주말")

}

 

 

let myValue: Int = Fruit.apple.rawValue // 원시값

let myFruit: Fruit? = Fruit(rawValue: 0) // 원시값으로 가져오는 enum값은 반드시 optional 이다

 


# 값 타입과 참조 타입

 

Class

- 전통적인 OOP 관점에서의 클래스

- 단일상속

- (인스턴스/타입) 메서드

- (인스턴스/타입) 프로퍼티

- 참조 타입

- Apple 프레임워크의 대부분의 큰 뼈대는 모두 클래스로 구성

 

Struct

- C 언어 등의 구조체보다 다양한 기능

- 상속 불가

- (인스턴스/타입) 메서드

- (인스턴스/타입) 프로퍼티

- 값 타입

- Swift의 대부분의 큰 뼈대는 모두 구조체로 구성

 

Enum

- 다른 언어의 열거형과는 많이 다른 존재

- 상속 불가

- (인스턴스/타입) 메서드

- (인스턴스/타입) 연산 프로퍼티

- 값 타입

- Enumeration

- 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의 (ex_ 요일, 상태값, 월 등)

- 열거형 자체가 하나의 데이터 타입, 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급

- 선언 키워드 enum

 

  Class Struct Enum
Type Reference Value Value
Subclassing O X X
Extension O O O

 

 

* 구조체를 사용하는 경우

- 연관된 몇몇의 값들을 모아서 하나의 데이터 타입으로 표현하고 싶을 때

- 다른 객체 또는 함수 등으로 전달될 때 (참조가 아닌 복사를 원할 때)

- 자신을 상속할 필요가 없거나, 자신이 다른 타입을 상속받을 필요가 없을 때

 

 

(참고)

Value

: 데이터를 전달할 때 값을 복사하여 전달

Reference

: 데이터를 전달할 때 값의 메모리 위치를 전달

 

 

* Swift는 구조체, 열거형 사용을 선호

public struct Int
public struct Double
public struct String
public struct Dictionary<Key: Hashable, Value>
public struct Array<Element>
public struct Set<Element: Hashable>

 

# 클로저

// 기본 형태
{ (매게변수 목록) -> 반환타입 in
    실행 코드
}

// 매게변수 / 반환타입이 없는 경우
{ () -> Void in
    실행 코드
}

 

// 클로저의 사용
var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a+b
}

print(sum(1, 2)) // 3

 

 

* 후행 클로저

func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
    return a+b
}


// 후행 클로저
result = caculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
    return left + right
}

// 반환타입 생략
result = caculate(a: 10, b: 10) { (left: Int, right: Int) in
    return left + right
}

// 단축 인자이름
result = caculate(a: 10, b: 10) {
    return $0 + $1
}

// 암시적 반환 표현
result = caculate(a: 10, b: 10) { $0 + $1 }

# 프로퍼티

 

1. stored property (저장 프로퍼티)

2. computed property (연산 프로퍼티)

3. instance property (인스턴스 프로퍼티)

4. type property (타입 프로퍼티) 

 

// 인스턴스 저장 프로퍼티
var koreanAge: Int = 20

// 인스턴스 연산 프로퍼티
var westernAge: Int {
    get {
       return koreanAge - 1
    }
    set {
       koreanAge = newValue + 1
    }

}

 

// 타입 저장 프로퍼티
static var typeDesc: String = "학생"

 

// 읽기전용 인스턴스 연산 프로퍼티
val selfIntroduction: String {
    get {
       return "저는 \(name)입니다."
    }
}

val selfIntroduction: String {
    return "저는 \(name)입니다." // get 생략 가능
}

 

 

# 프로퍼티 감시자

struct Money {
    var currencyRate: Double = 1100 {
       willSet {
           print("willSet : \(currencyRate) -> \(newValue)")
       }
       didSet {
           print("didSet: \(oldValue) -> \(currencyRate)")
        }
    }
}

var money: Money = Money()
// (print) willSet : 1100.0 -> 1150.0
money.currencyRate = 1150
// (print) didSet : 1100.0 -> 1150.0



# 상속

- 스위프트의 상속은 클래스, 프로토콜에서 가능

- 열거형, 구조체는 상속 불가

- 스위프트는 다중상속을 지원하지 않음

 

class Person {
    var name: String = ""

    func selfIntroduce() {
       print("저는 \(name) 입니다")
    }

    // final 키워드 - 재정의 방지
    final func sayHello() {
       print("hello")
    }

    // 타입 메서드
    // 재정의 불가 타입 메서드 - static

    static func typeMethod() {
        print("type method - static")
    }
    // 재정의 불가 타입 메서드 - class

    class func classMethod() {
        print("type method - class")
    }

    // 재정의 가능한 class 메서드 라도 final 키워드를 사용하면 재정의 불가
    // 메서드 앞의 `static`과 `final class`는 똑같은 역할을 합니다
    final class func finalClassMethod() {
        print("type method - final class")
    }

}

 

class Student: Person {
    var major: String = ""

    override func selfIntroduce() {
       print("저는 \(name)이고, 전공은 \(major)입니다.")
       // super.selfIntroduce() // 부모 클래스의 함수 호출 가능
    }

    override class func classMethod() {
       print("overriden type method - class")
    }

    // override final func sayHello() {} // final 메소드 재정의 불가
    // override static func typeMethod() {} // static 메소드 재정의 불가

    // override final class func finalClassMethod() {} // final class 메소드 재정의 불가

}

# 인스턴스의 생성과 소멸 (init, deinit)

class Person {
    var name: String
    var age: Int
    var nickName: String?

    // 이니셜라이저 - designated init
    init(name: String, age: Int) {
        self.name = name
        self.age = age

    }


    // 이니셜라이저 - convenience init(보조 이니셜라이저)
    // self.init을 호출하는 init 앞에는 convenience를 붙여준다
    convenience init(name: String, age: Int, nickName: String) {
       self.init(name: name, age: age)
       self.nickName = nickName

    }

}

 

class Person {
    var name: String
    var age: Int
    var nickName: String?

    // init?
    // 원하는 범주의 값이 아니라면 nil을 반환하게 된다
    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

    }

    deinit {
       // Person 객체가 nil 할당을 받으면(메모리에서 해제되면) deinit이 실행됨
    }
}

# 옵셔널 체이닝

: 연속적인 nil 체크 가능

if let guardJob = owner?.home?.guard?.job {
    print("우리집 경비원의 직업은 \(guardJob)입니다.")
} else {
    print("우리집 경비원은 직업이 없어요.")
}

 

# nil 병합 연산자

: 값이 nil인 경우, default 값 지정 가능

var guardJob: String = owner?.home?.guard?.job ?? "무직"

# 타입 캐스팅 (is, as)

1) 인스턴스의 타입을 확인하는 용도

2) 인스턴스를 부모/자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도

 

var isPerson: Bool = person is Person // true or false

// switch 문으로 type 확인할 수도 있음
switch person {
case is Student:
    //
case is Person:
    //
default:
    //
}

 

/*
업 캐스팅
자식 클래스 -> 부모 클래스
as 사용
non-optional
as Any는 생략가능
*/
var mike: Person = Student() as Person
var mike: Person = Student() as Person
var jenny: Person = Person()

/*
다운 캐스팅
부모클래스 -> 자식 클래스
as? 또는 as! 사용
optional
*/
// 조건부 다운 캐스팅 as?
var studentA = mike as? Student
var studentA = jenny as? Student // nil

// 강제 다운 캐스팅 as!
var studentA = mike as! Student
var studentA = jenny as! Student // 런타임 오류

 

// 이런식으로 함수내에서 클래스 타입확인해서 사용
func doSomething(someone: Person) {
    if let student = someone as? Student {
       student.studentFunc()
    } else if let person = someone as? Person {
       person.personFunc()
    }

}

# Assertion

- assert(_:_:file:line:) 함수를 사용

- assert 함수는 debug 모드에서만 동작, release 모드에서는 동작하지 않음

- 주로 디버깅 중 조건의 검증을 위해 사용

var someInt: Int = 0
assert(someInt == 0, "someInt != 0")

 

 # Early Exit

- guard를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료

- debug, release 모두에서 동작

- guard의 else 블럭 내부에는 종류 지시어(return/break..) 가 꼭 있어야 함

- 타입 캐스팅, 옵셔널과도 자주 사용, 단순 조건 판단 후 빠르게 종료할 때에도 용이

func someFunc(age: Int?) {
    guard let unwrappedAge = age, unwrappedAge < 130, unwrappedAge >= 0 else { return }
    print("나이 : \(unwrappedAge)" // guard문에서 선언한 변수명을 아래코드에서도 사용 가능!
}
// while 문에서 쓰기도 함
var count =1

while true {
    guard count < 3 else { break }
    print(count)
    count += 1

}
// Dictionoary를 사용할 때에도 용이함
func someFunc(info: [String: Any]) {
    guard let name = info["name"] as? String else { return }
    guard let age = info["age"] as? Int, age >= 0 else { return }
    print("\(name) : \(age)")
}

# Protocol(프로토콜)

- 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의

- 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 프로토콜의 요구사항을 실제로 구현

- 어떤 프로토콜의 요구사항을 모두 따르는 타입은 '프로토콜을 준수한다(Conform)'고 표현

protocol Talkable {
    // 프로퍼티 요구
    // var 키워드만 사용할 수 있음
    var topic: String { get set } // { get set } :  읽기 쓰기 가능
    var language: String { get } // { get } : 읽기만 가능

    // 메서드 요구
    func talk()

    // 이니셜라이저 요구
    init(topic: String, language: String)

}

// ---------------------------------------

struct Person: Talkable { // <- Person 구조체는 Talkable 프로토콜을 채택
    // 프로퍼티 구현 (1)
    var topic: String // { get set }으로 정의된 property이기 떄문에 무조건 var로 선언
    let language: String // { get }으로 정의된 property이기 때문에 var/let 모두 가능

    // 프로퍼티 구현 (2) - 연산 프로퍼티로 대체
    // 읽기 전용 프로퍼티
    var language: String { return "한국어" }
    // 일기쓰기 전용 프로퍼티
    var subject: String = ""
    var topic: String {
       set {
           self.subject = newValue
       }
       get {
           return self.subject
       }

    }

    // 메서드
    func talk() { print("\(topic), \(language)")

    // 이니셜라이저
    init(topic: String, language: String) {
       self.topic = topic
       self.language = language

    }
}

}

 

* 프로토콜의 상속

- 프로토콜은 클래스와 다르게 다중상속이 가능

- 클래스에서 상속과 프로토콜 채택을 동시에 하려면, 상속받으려는 클래스를 먼저 명시하고 그뒤에 채택할 프로토콜 목록을 작성해야함

 

* 프로토콜 준수 확인 (is, as)

// is
someAny is Talkable // true or false

// as
if let someTalkable: Talkable = someAny as? Talkable {
    someTalkable.talk()
}

# Extension

- 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능

 

* 익스텐션으로 추가할 수 있는 기능

1. 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티

2. 타입 메서드 / 인스턴스 메서드

3. 이니셜라이저

4. 서브스크립트

5. 중첩 타입

6. 특정 프로토콜을 준수할 수 있도록 기능 추가

(기존에 존재하는 기능을 재정의할 수는 없음)

 

extension Int {
    var isEven: Bool {
       return self % 2 == 0
    }
    var isOdd: Bool {
       return self % 2 == 1
    }

    func multiplay(by n: Int) -> Int {
        return self * n
    }
}
print(1.isEven)



extension String {
    init(intTypNumber: Int) {
        self = "\(intTypeNumber)"
    }

}

print(String(intTypNumber: 100))

# 오류처리

- Error 프로토콜과 (주로) 열거형을 통해 오류를 표현

enum VendingMachineError: Error {
    case invalidInput
    case insufficientFunds(moneyNeeded: Int)
    case outOfStock

}


class VendingMachine {
    let itemPrice: Int = 100
    var itemCount: Int = 5
    var deposited: Int = 0

    // 돈 받기
    func receiveMoney(_ money: Int) throws { // '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 }

        // 
    }
}

 


# 고차함수

- 전달인자로 함수를 전달받거나 함수 실행의 결과를 함수로 반환하는 함수

(예시) map, filter, reduce

let numbers: [Int] = [1, 2, 3, 4, 5]

// map

// 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너 생성
let strings: [String] = numbers.map { "\($0)" } // ["1", "2", "3", "4", "5"]

// filter
// 컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출
let oddNumbers: [Int] = numbers.filter { $0 % 2 == 1 } // [1, 3, 5]

// reduce
// 컨텐츠 내부의 콘턴츠를 하나로 통합
let sum: Int = numbers.reduce { $0 + $1 } // 15

# Subscript

stuct Classroom {
    var students = [
       ["Eric", "Peter", "Randy"],
       ["Messi", "Nicki", "Alicia", "Bill", "Jeff"]

    ]

    subscript(row: Int, col: Int) -> String {
       get {
           return students[row][col]
       }
       set {
           students[row][col] = newValue
       }

    }

}

let class = Classroom
class[1, 2] // Alicia
class[0, 0] = "newStudent"
class[0, 0] // newStudent

 


# 추가로 더 공부할 것들

- 제네릭(Generics)

- 접근수준(Access Control)

- 중첩타입(Nested Types)

- 사용자정의 연산자(Custom Operators)


 

구름EDU - 야곰의 스위프트 프로그래밍 (무료)

https://edu.goorm.io/lecture/1141/%25EC%2595%25BC%25EA%25B3%25B0%25EC%259D%2598-%25EC%258A%25A4%25EC%259C%2584%25ED%2594%2584%25ED%258A%25B8-%25ED%2594%2584%25EB%25A1%259C%25EA%25B7%25B8%25EB%259E%2598%25EB%25B0%258D

 

YouTube - yagom

youtu.be/2n-fSlW-jts