[swift] BASIC_01. UIKit


UIKit

Cocoa Touch FrameWork

  • IOS 개발 환경을 구축하기 위한 최상위 프레임워크
  • IOS Application (UIkit, Foundation등)을 포함한다.



Foundation

  • 가장 기본적인 데이터 타입부터, 자료구조, 각종 구조체, 타이머, 네트워크 통신 파일 관리 등 기본적인 프로그램의 중심을 담당한다.



UIKit

  • 사용자의 인터페이스를 관리한다.
  • 이벤트를 처리하는 것이 주 목적이다.

uikit




Code Structure of a UIKit App

UI 킷의 구조는 기본적으로 MVC 디자인 패턴을 사용한다.


현실 MVC

뷰와 컨트롤러가 강하게 연결되어 있어 뷰컨트롤러가 거의 모든 일을 담당한다.
뷰와 컨트롤러는 분리하기 어려우며, 프로젝트 규모가 커질 수록 컨트롤러의 크기가 거대해지고 유지보수가 힘들어진다.
이를 해결하기 위해 MVVM, 바이퍼 패턴 등으로 해결할 수 있다.

mvc

[swift] 22. High order function (고차 함수)


고차함수

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

swift에서 제공하는 고차 함수 (모두 컬렉션 타입에 정의됨)

  • map
  • filter
  • reduce




예제


map

let numbers = [0, 1, 2, 3]
let mapArray = numbers.map { (number) -> Int in
    return number * 2
}
print("map \(mapArray)")

출력 결과

map [0, 2, 4, 6]


filter

필터는 컨테이너 내부의 값을 걸러서 새로운 컨테이너를 생성한다.

let intArray = [10, 5, 20, 13, 4]
let filterArray = intArray.filter { $0 > 5 }    // 클로저 간소화
print("filter \(filterArray)")

출력 결과

filter [10, 20, 13]


reduce

reduce는 컨테이너 내부의 값들을 하나로 통합시켜준다.

let someArray = [1, 2, 3, 4, 5]
let reduceResult = someArray.reduce(0) {    // 초기 값 0
    (result: Int, element: Int) -> Int in   // 매개변수 result(누적 값), element(배열의 요소 값)
    print("\(result) + \(element)")
    return result + element
}
print("reduce \(reduceResult)")

출력 결과

0 + 1
1 + 2
3 + 3
6 + 4
10 + 5
reduce 15

[swift] 21. Closure (클로저)


클로저

  • 코드에서 전달 및 사용할 수 있는 독립 기능 블록이며, 일급 객체의 역할을 할 수 있다.

일급 객체

  • 전달 인자로 보낼 수 있고, 변수/상수 등으로 저장하거나 전달할 수 있으며, 함수의 반환 값이 될 수도 있다.

Named Closure

  • 이름이 있는 함수

    Unnamed Closure

  • 이름이 없는 함수(보통 클로저를 지칭할 때 이를 말함)



클로저 표현 식

{ (매개 변수) -> 리턴 타입 in)
    실행 구문
}




예제

let hello = { () -> () in
    print("hello")
}

hello()

출력 결과

hello


파라미터와 리턴 타입이 있는 클로저

let hello2 = { (name: String) -> String in 
    return "Hello, \(name)" // 함수에서 배운대로 파라미터의 네임은 단독으로 쓰였으니, 전달인자 레이블이자 파라미터 네임으로 생각할 수도 있지만, 클로저에서는 전달인자 레이블을 사용용하지 않기 때문에, 파라미터 네임으로만 사용한다.
}

// hello2(name: "Junhyeok") // 즉, 전달인자를 적고 파라미터 값을 넘겨주면 에러가 나게 된다.
hello2("Junhyeok")

출력 결과

Junhyeok


클로저는 함수의 파라미터 타입으로 클로저를 전달할 수 있다.

클로저가 1급 객체이기 때문에 가능하다.

func doSomething(closure: () -> ()) { // 파라미터 이름은 closure
    closure
}

doSomething(closure: { () -> () in
    print("hello")
})

출력 결과

hello


함수의 반환 타입으로 클로저를 사용할 수 있다.

func doSomething2() -> () -> () {
    return { () -> () in
        print("hello4")
    }
}

doSomething2()()

출력 결과

hello4


클로저가 길어지거나, 가독성이 떨어진다면 후엔클로저 기능을 사용한다. (맨 마지막 매개변수에만 사용할 수 있다.)

후엔클로저

func doSomething(closure: () -> ()) {
    closure()
}

doSomething() {
    print("hello2")
}

doSomething {
    print("hello3")
}

출력 결과

hello2
hello3


다중 후엔 클로저 문법을 이용해 여러 개의 클로저를 사용한다.

func doSomething2(success: () -> (), fail: () -> ()) {

}

doSomething2 {
    print("success")
} fail: {
    print("fail")
}


클로저 간소화

파라미터 형식과 리턴 형식의 타입을 생략할 수 있다. 약식인수 이름를 사용하여 매개변수 이름 또한 생략이 가능하다.
약식인수 이름 : 매개변수를 대신하여 사용

단일 구문(return이 하나라면) 타입 유추를 통해 return 타입도 생략 가능하며,

// 모두 같은 의미를 가진 클로저

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 // 단일 구문이므로 return 생략 가능
})

doSomething3() { // 후엔클로저 방식으로 작성
    $0 + $1 + $2
}

doSomething3 {  // 단 하나의 클로저만 매개변수로 전달하는 경우에는 소괄호까지 생략 가능
    $0 + $1 + $2
}

[swift] 20. try - catch 문


에러 처리란 프로그램 내에서 에러가 발생한 상황에 대해 대응하고 이를 복구하는 과정을 말한다.
런타임 오류와 함께 프로그램이 종료되지 않는다.

swift는 RunTime 에러를 처리하기 위한 다음과 같은 1급 클래스를 제공한다.

  • 발생(throwing)
  • 감지(catching)
  • 전파(propagating)
  • 조작(manipulating)


swift에서는 오류를 처리하는 네 가지 방법이 존재한다.

  1. 함수에서 발생한 오류를 해당 함수를 호출한 코드에 전파하는 방법
  2. do - catch 구문을 이용해서 오류를 처리 하는 방법
  3. optional 값으로 오류를 처리하는 방법
  4. 오류가 발생하지 않을 것이라고 확신하는 방법




예제


스위프트에서 에러는 에러 프로토콜을 따르는 타입의 값으로 표현된다.
에러 프로토콜은 요구사항이 없는 빈 프로토콜이지만, 오류를 표현하기 위해서는 에러 프로토콜을 채택해야 한다.


에러 프로토콜을 채택한 열거형

enum PhoneError: Error { // 오류 처리를 위해 Error 프로토콜을 채택한 PhoneError 열거형
    case unknown
    case batteryLow(batteryLevel: Int) // 배터리 잔량을 알려주기 위한 Int값
}

throw PhoneError.batteryLow(batteryLevel: 20) // throw 구문을 이요해서 에러가 발생할 것 같은 지점에 에러를 던져준다.

출력 결과

Playground execution terminated: An error was thrown and was not caught:
   PhoneError
      batteryLow : 1 element
      - batteryLevel : 20



1. do - catch 문으로 오류를 처리하는 방법

do {
    try 오류 발생 가능한 코드
} catch 오류 패턴 {
    처리 코드
}

예제

enum PhoneError: Error {
    case unknown
    case batteryLow(batteryLevel: Int)
}

func checkPhoneBatteryStatus(batteryLevel: Int) throws -> String {
    guard batteryLevel != -1 else { throw PhoneError.unknown } // betteryLevel이 1이 아니면 else 구문 실행 (guard문은 false 일 때 실행)

    guard batteryLevel >= 20 else { throw PhoneError.batteryLow(batteryLevel: 20)}

    return "배터리 상태가 정상입니다."
}

do {
    try checkPhoneBatteryStatus(batteryLevel: -1)
} catch PhoneError.unknown {
    print("알 수 없는 에러입니다.")
} catch PhoneError.batteryLow(let batteryLevel) {
    print("배터리 전원 부족, 남은 배터리 : \(batteryLevel)%")
} catch { // 위 catch 문이 모두 해당하지 않는 경우
    print("그 외 오류 발생 : \(error)")
}

do {
    try checkPhoneBatteryStatus(batteryLevel: 20)
} catch PhoneError.unknown {
    print("알 수 없는 에러입니다.")
} catch PhoneError.batteryLow(let batteryLevel) {
    print("배터리 전원 부족, 남은 배터리 : \(batteryLevel)%")
} catch { // 위 catch 문이 모두 해당하지 않는 경우
    print("그 외 오류 발생 : \(error)")
}

출력 결과

알 수 없는 에러입니다.
배터리 전원 부족, 남은 배터리 : 20%




2. try - ? 를 사용 (옵셔널 값으로 변환하여 오류를 처리)

(코드의 반환값은 nil이 된다.)

enum PhoneError: Error {
    case unknown
    case batteryLow(batteryLevel: Int)
}

func checkPhoneBatteryStatus(batteryLevel: Int) throws -> String {
    guard batteryLevel != -1 else { throw PhoneError.unknown } // betteryLevel이 1이 아니면 else 구문 실행 (guard문은 false 일 때 실행)

    guard batteryLevel >= 20 else { throw PhoneError.batteryLow(batteryLevel: 20)}

    return "배터리 상태가 정상입니다."
}

let status1 = try? checkPhoneBatteryStatus(batteryLevel: -1)
print(status1)  // 오류일 경우 nil 반환

let status2 = try? checkPhoneBatteryStatus(batteryLevel: 30)
print(status2)  // 오류가 아닐 경우 Optional 값으로 반환

출력 결과

nil
Optional("배터리 상태가 정상입니다.")




3. try - ! 를 사용 (오류가 발생하지 않을 것으로 확신)

enum PhoneError: Error {
    case unknown
    case batteryLow(batteryLevel: Int)
}

func checkPhoneBatteryStatus(batteryLevel: Int) throws -> String {
    guard batteryLevel != -1 else { throw PhoneError.unknown } // betteryLevel이 1이 아니면 else 구문 실행 (guard문은 false 일 때 실행)

    guard batteryLevel >= 20 else { throw PhoneError.batteryLow(batteryLevel: 20)}

    return "배터리 상태가 정상입니다."
}

/*
오류일 경우 프로그램 강제 종료
즉, 조심해서 사용해야 함.
let status1 = try! checkPhoneBatteryStatus(batteryLevel: -1)
print(status1)  
*/
let status2 = try! checkPhoneBatteryStatus(batteryLevel: 30)
print(status2)

출력 결과

배터리 상태가 정상입니다.

[swift] 19. Optional chaining (옵셔널 체이닝)


옵셔널 체이닝이란 옵셔널에 속해 있는 nil 일지도 모르는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정




예제


struct Developer {
    let name: String
}

struct Company {
    let name: String
    var developer: Developer?
}

var company: Company(name: "Gunter", developer: nil)
print(company.developer)

var developer = Developer(name: "jun")
var company2: Company(name: "CompanyJun", developer: developer)
print(company2.developer)
// print(company.developer.name) // ERROR company.developer.name에 접근하기 전에 developer의 옵셔널을 벗겨내라는 에러
print(company2.developer?.name) // ?로 Optional 체니잉을 하면 프로퍼티의 값은 항상 Optional로 감싸 있다. (값이 nil일 수도 있기 때문)
print(company2.developer!.name) // !로 Optional 체이닝을 하면 옵셔널 프로퍼티를 강제 언래핑하기 때문에 값이 옵셔널로 감싸져 있지 않다.

출력 결과

nil
Optional(__lldb_expr_595.Developer(name: "jun"))
Optional("jun")
jun