'yagom'에 해당되는 글 78건

  1. 2017.07.20 익스텐션
  2. 2017.07.17 프로토콜
  3. 2017.07.13 assert와 guard
  4. 2017.07.11 사진 게시판 API 만들기 [2]
  5. 2017.07.10 타입캐스팅 (2)
  6. 2017.07.06 옵셔널 체이닝
  7. 2017.06.29 상속
  8. 2017.06.27 사진 게시판 API 만들기 [1] (2)
  9. 2017.06.26 프로퍼티 - Property (2)
  10. 2017.06.23 Swift 4 변경사항 (8)
  11. 2017.06.22 클로저 - Closure
  12. 2017.06.19 클래스, 구조체, 열거형 비교
  13. 2017.06.15 열거형 - Enumerations
  14. 2017.06.12 클래스
  15. 2017.06.08 구조체 (2)
  16. 2017.06.05 옵셔널
  17. 2017.06.01 반복문
  18. 2017.05.29 조건문
  19. 2017.05.25 함수 (2)
  20. 2017.05.24 Perfect 라우팅

익스텐션

Swift/기본문법 2017.07.20 15:59

익스텐션

익스텐션(Extension) 은 스위프트의 강력한 기능 중 하나입니다. 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가 할 수 있는 기능입니다. 

기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있습니다.


스위프트의 익스텐션이 타입에 추가할 수 있는 기능

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티 
  • 타입 메서드 / 인스턴스 메서드
  • 이니셜라이저
  • 서브스크립트 
  • 중첩 타입
  • 특정 프로토콜을 준수할 수 있도록 기능 추가 


익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의 할 수는 없습니다.


클래스의 상속과 익스텐션을 비교해보겠습니다. 이 둘은 비슷해보이지만 실제 성격은 많이 다릅니다. 

클래스의 상속은 클래스 타입에서만 가능하지만 익스텐션은 구조체, 클래스, 프로토콜 등에 적용이 가능합니다. 또 클래스의 상속은 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가 기능을 구현하는 수직 확장이지만, 익스텐션은 기존의 타입에 기능을 추가하는 수평 확장입니다. 또, 상속을 받으면 기존 기능을 재정의할 수 있지만, 익스텐션은 재정의할 수 없다는 것도 큰 차이 중 하나입니다. 상황과 용도에 맞게 상속과 익스텐션을 선택하여 사용하면 됩니다. 

 

 상속

익스텐션 

확장 

수직확장 

수평확장 

사용 

클래스 타입 

클래스, 구조체, 프로토콜, 제네릭 등 모든 타입 

재정의 

가능 

불가능 


익스텐션을 사용하는 대신 원래 타입을 정의한 소스에 기능을 추가하는 방법도 있겠지만, 외부 라이브러리나 프레임워크를 가져다 썼다면 원본 소스를 수정하지 못합니다. 이처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다. 따로 상속을 받지 않아도 되며, 구조체와 열거형에도 기능을 추가할 수 있으므로 익스텐션은 매우 편리한 기능입니다. 

익스텐션은 모든 타입에 적용할 수 있습니다. 모든 타입이라 함은 구조체, 열거형, 클래스, 프로토콜, 제네릭 타입 등을 뜻합니다. 즉, 익스텐션을 통해 모든 타입에 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트, 중첩 데이터 타입 등을 추가할 수 있습니다.

더불어 익스텐션은 프로토콜과 함께 사용하면 굉장히 강력한 기능을 선사합니다. 이 부분은 프로토콜 중심 프로그래밍(Protocol Oriented Programming)에 대해 더 알아보면 좋습니다.


소스코드


정의 문법

extension 키워드를 사용하여 정의합니다.

extension 확장할 타입 이름 {
    /* 타입에 추가될 새로운 기능 구현 */
}


익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다. 이런 경우에는 클래스나 구조체에서 사용하던 것과 똑같은 방법으로 프로토콜 이름을 나열해줍니다.

extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3... {
    /* 프로토콜 요구사항 구현 */
}


스위프트 라이브러리를 살펴보면 실제로 익스텐션이 굉장히 많이 사용되고 있음을 알 수 있습니다. Double 타입에는 수많은 프로퍼티와 메서드, 이니셜라이저가 정의되어 있으며 수많은 프로토콜을 채택하고 있을 것이라고 예상되지만, 실제로 Double 타입의 정의를 살펴보면 그 모든것이 다 정의되어 있지는 않습니다. 

그러면 Double 타입이 채택하고 준수해야 하는 수많은 프로토콜은 어디로 갔을까요? 어디에서 채택하고 어디에서 준수하도록 정의되어 있을까요? 당연히 답은 익스텐션입니다. 이처럼 스위프트 표준 라이브러리 타입의 기능은 대부분 익스텐션으로 구현되어 있습니다. Double 외에도 다른 타입들의 정의와 익스텐션을 찾아보면 더 많은 예를 보실 수 있습니다. 꼭 한 번 찾아보세요! 


익스텐션 구현

연산 프로퍼티 추가

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

print(1.isEven) // false
print(2.isEven) // true
print(1.isOdd)  // true
print(2.isOdd)  // false

var number: Int = 3
print(number.isEven) // false
print(number.isOdd) // true

number = 2
print(number.isEven) // true
print(number.isOdd) // false

위 코드의 익스텐션은 Int 타입에 두 개의 연산 프로퍼티를 추가한 것입니다. Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool 타입으로 알려주는 연산 프로퍼티입니다. 익스텐션으로 Int 타입에 추가해준 연산 프로퍼티는 Int 타입의 어떤 인스턴스에도 사용이 가능합니다. 위의 코드처럼 인스턴스 연산 프로퍼티를 추가할 수도 있으며, static 키워드를 사용하여 타입 연산 프로퍼티도 추가할 수 있습니다. 


메서드 추가

extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}
print(3.multiply(by: 2))  // 6
print(4.multiply(by: 5))  // 20

number = 3
print(number.multiply(by: 2))   // 6
print(number.multiply(by: 3))   // 9

위 코드의 익스텐션을 통해 Int 타입에 인스턴스 메서드multiply(by:) 메서드를 추가했습니다. 여러 기능을 여러 익스텐션 블록으로 나눠서 구현해도 전혀 문제가 없습니다. 관련된 기능별로 하나의 익스텐션 블록에 묶어주는 것도 좋습니다. 


이니셜라이저 추가

extension String {
    init(int: Int) {
        self = "\(int)"
    }
    
    init(double: Double) {
        self = "\(double)"
    }
}

let stringFromInt: String = String(int: 100) 
// "100"

let stringFromDouble: String = String(double: 100.0)    
// "100.0"

인스턴스를 초기화(이니셜라이즈)할 때 인스턴스 초기화에 필요한 다양한 데이터를 전달받을 수 있도록 여러 종류의 이니셜라이저를 만들 수 있습니다. 타입의 정의부에 이니셜라이저를 추가하지 않더라도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다. 

하지만 익스텐션으로 클래스 타입에 편의 이니셜라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없습니다. 지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 합니다(값 타입은 상관없습니다).



관련문서

The Swift Programming Language - Extensions





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

익스텐션  (0) 2017.07.20
프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
Posted by yagom

프로토콜

Swift/기본문법 2017.07.17 11:00

프로토콜

프로토콜(Protocol)은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다. 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다. 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다(Conform)고 표현합니다. 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 합니다. 

즉, 프로토콜은 기능을 정의하고 제시 할 뿐이지 스스로 기능을 구현하지는 않습니다.


소스코드


정의 문법

protocol 키워드를 사용하여 정의합니다.

protocol 프로토콜 이름 {
    /* 정의부 */
}


프로토콜 구현

protocol Talkable {
    
    // 프로퍼티 요구
    // 프로퍼티 요구는 항상 var 키워드를 사용합니다
    // get은 읽기만 가능해도 상관 없다는 뜻이며
    // get과 set을 모두 명시하면 
    // 읽기 쓰기 모두 가능한 프로퍼티여야 합니다
    var topic: String { get set }
    var language: String { get }
    
    // 메서드 요구
    func talk()
    
    // 이니셜라이저 요구
    init(topic: String, language: String)
}


프로토콜 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택했습니다
struct Person: Talkable {
    // 프로퍼티 요구 준수
    var topic: String
    let language: String
    
    // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
//    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
    }
}


> 프로퍼티 요구는 다양한 방법으로 해석, 구현할 수 있습니다.

struct Person: Talkable {
    var subject: String = ""

    // 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
    var topic: String {
        set {
            self.subject = newValue
        }
        get {
            return self.subject
        }
    }
    
    var language: String { return "한국어" }
    
    func talk() {
        print("\(topic)에 대해 \(language)로 말합니다")
    }
    
    init(topic: String, language: String) {
        self.topic = topic
    }
}



프로토콜 상속

프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다. 프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 프로토콜은 클래스와 다르게 다중상속이 가능합니다.

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 SuperClass: Readable { func read() { } } class SubClass: SuperClass, Writeable, ReadSpeakable { func write() { } func speak() { } }


프로토콜 준수 확인

is, as 연산자를 사용해서 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있습니다.

let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()

var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false

someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true

someAny = sup

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read

if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
    someReadSpeakable.speak()
} // 동작하지 않음

someAny = sub

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read



관련문서

The Swift Programming Language - Protocols





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

익스텐션  (0) 2017.07.20
프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
Posted by yagom

assertguard

애플리케이션이 동작 도중에 생성하는 다양한 연산 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 확인하고 빠르게 처리할 수 있습니다.


소스코드


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_assert.swift, line 26


func functionWithAssert(age: Int?) {
    
    assert(age != nil, "age == nil")
    
    assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
    print("당신의 나이는 \(age!)세입니다")
}

functionWithAssert(age: 50)
//functionWithAssert(age: -1) // 동작 중지, 검증 실패
//functionWithAssert(age: nil) // 동작 중지, 검증 실패


assert(_:_:file:line:)와 같은 역할을 하지만 실제 배포 환경에서도 동작하는 precondition(_:_:file:line:) 함수도 있습니다. 함께 살펴보세요.



빠른 종료 - Early Exit

guard를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료합니다. 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작합니다. guardelse 블럭 내부에는 특정 코드블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 합니다. 타입 캐스팅, 옵셔널과도 자주 사용됩니다. 그 외에도 단순 조건 판단 후 빠르게 종료할 때도 용이합니다.

func functionWithGuard(age: Int?) {
    
    guard let unwrappedAge = age,
        unwrappedAge < 130,
        unwrappedAge >= 0 else {
        print("나이값 입력이 잘못되었습니다")
        return
    }
    
    print("당신의 나이는 \(unwrappedAge)세입니다")
}

var count = 1

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


func someFunction(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)")
    
}

someFunction(info: ["name": "jenny", "age": "10"])
someFunction(info: ["name": "mike"])
someFunction(info: ["name": "yagom", "age": 10]) // yagom: 10


관련문서

* The Swift Programming Language - Control Flow - Early Exit

* The Swift Programming Language - The Basics - Assertion





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

익스텐션  (0) 2017.07.20
프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
Posted by yagom


Perfect 미니 프로젝트 [2]


1. 서버 객체 생성 및 라우팅 
2. mongoDB 연결하기 




본격적으로 API 서버 만들기에 돌입합니다!!


참고

2017년 7월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인(https://github.com/apple/swift/releases)

* Perfect 최신 버전 확인(https://github.com/PerfectlySoft/Perfect/releases)


서버 객체 생성 및 라우팅

서버 객체와 라우팅에 대한 설명은 지난 글에서 했으므로, 작성한 코드만 설명합니다.

2017/05/24 - [Swift/Perfect] - Perfect 라우팅


우선, 프로젝트 폴더의 Sources폴더의 handlers.swift 파일을 생성합니다.

> vi Sources/handlers.swift


혹시 Xcode로 프로젝트를 진행하고 있다면 파일 생성시 타깃을 ImageBoard로 설정해주세요.


handlers.swift 파일에 아래 코드를 입력합니다.

import PerfectHTTP

struct ContentsType {
    static let json = "application/json; charset=utf-8"
    static let formData = "multipart/form-data"
}

// 사진 게시물 목록
func articleListHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    
    // 첫 번째 테스트를 위해 임시로 response body 생성
    do {
        try response.setBody(json: ["hello":"test"])
        response.completed()
    } catch {
        response.completed(status: .internalServerError)
    }
}

// 사진 게시물 등록
func postArticleHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    response.completed()
}

// 사진 게시물 수정
func modifyArticleHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    response.completed()
}

// 사진 게시물 삭제
func deleteArticleHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    response.completed()
}

// 사진 게시물 정보
func articleInfoHandler(request: HTTPRequest, response: HTTPResponse) {
    response.setHeader(.contentType, value: ContentsType.json)
    response.completed()
}


라우팅을 위한 핸들러 생성을 미리 해둔 것입니다.

핸들러 생성을 모두 마쳤으면 이제 서버 객체를 생성하고 구동할 차례입니다. main.swift 파일로 이동합니다.

> vi Sources/main.swift


이전 포스팅에서 모두 설명한 내용이므로 따로 코드설명은 하지 않아도 아실 것이라 생각합니다.

2017/05/17 - [Swift/Perfect] - Perfect 시작하기


import PerfectLib
import PerfectHTTP
import PerfectHTTPServer


let articleURI = "/article"
let subArticleURI = articleURI + "/*"

var routes = Routes()

routes.add(method: .post, uri: articleURI, handler: postArticleHandler(request:response:))

routes.add(method: .get, uri: articleURI, handler: articleListHandler(request:response:))

routes.add(method: .post, uri: subArticleURI, handler: modifyArticleHandler(request:response:))

routes.add(method: .delete, uri: subArticleURI, handler: deleteArticleHandler(request:response:))

routes.add(method: .get, uri: subArticleURI, handler: articleInfoHandler(request:response:))

let server = HTTPServer()

server.addRoutes(routes)
server.serverPort = 8080

do {
    try server.start()
} catch PerfectError.networkError(let error, let message) {
    Log.error(message: "Error: \(error), \(message)")
} catch {
    Log.error(message: error.localizedDescription)
}


자 이렇게 코드 작성을 마쳤으면, 동작 테스트를 해봐야죠! ImageBoard 애플리케이션을 실행 한 후 접속하여 제대로 동작하는지 확인합니다.


이렇게 응답이 온다면 제대로 동작하고 있다고 볼 수 있지요 :)

제대로 응답이 온다면 다음 스텝으로!


mongoDB 연결하기

이번엔 몽고디비를 연결해보려고 합니다. 사실 이 미니 프로젝트는 여러 데이터베이스를 사용할 필요도, 여러 콜렉션을 사용할 필요도 없습니다. 그래서 단일 데이터베이스의 단일 콜렉션을 사용하려고 합니다. 몽고  데이터베이스 이름은 "image_board", 콜렉션 이름은 "articles"라고 사용하겠습니다.


몽고디비 관련한 내용을 작성할 소스코드 파일 mongo.swift를 생성합니다.

> vi Sources/mongo.swift


mongo.swift 파일에 아래 코드를 작성합니다.

import Foundation
import MongoDB
import PerfectLib

struct DB {
    static var client: MongoClient?
    static var database: MongoDatabase?
    static var collection: MongoCollection?
    
    static func connect(uri: String, database: String, collection: String) throws {
        
        do {
            self.client = try MongoClient(uri: uri)
        } catch {
            throw error
        }
        
        self.database = DB.client?.getDatabase(name: database)
        self.collection = DB.database?.getCollection(name: collection)
    }
    
    static func disconnect() {
        self.collection?.close()
        self.database?.close()
        self.client?.close()
    }
}


그리고 main.swift 파일로 이동해서 아래 코드로 업데이트 해줍니다.

> vi Sources/main.swift


/// 더 위의 코드는 생략 do { try DB.connect(uri: "mongodb://localhost", database: "image_board", collection: "articles") try server.start() } catch PerfectError.networkError(let error, let message) { Log.error(message: "Error: \(error), \(message)") } catch { Log.error(message: "\(error)") } DB.disconnect()


이제 데이터베이스 연결까지 준비를 마쳤습니다. 


다음 번에는 실제로 데이터베이스에 쓰고 읽는 작업까지 해보려 합니다~

다음에 또 만나요! :D



참고문서



by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (2) 2017.06.27
Perfect 라우팅  (0) 2017.05.24
Perfect 시작하기  (0) 2017.05.17
우분투(Ubuntu)에 스위프트 설치하기  (0) 2017.04.03
Posted by yagom

타입캐스팅

스위프트의 타입캐스팅은 인스턴스의 타입을 확인하는 용도 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용합니다. is, as를 사용합니다.


소스코드


타입 캐스팅 예제를 위한 클래스 정의

class Person {
    var name: String = ""
    func breath() {
        print("숨을 쉽니다")
    }
}

class Student: Person {
    var school: String = ""
    func goToSchool() {
        print("등교를 합니다")
    }
}

class UniversityStudent: Student {
    var major: String = ""
    func goToMT() {
        print("멤버쉽 트레이닝을 갑니다 신남!")
    }
}


예제를 위한 인스턴스 생성

var yagom: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversityStudent()


타입 확인

is를 사용하여 타입을 확인합니다.

var result: Bool

result = yagom is Person // true
result = yagom is Student // false
result = yagom is UniversityStudent // false

result = hana is Person // true
result = hana is Student // true
result = hana is UniversityStudent // false

result = jason is Person // true
result = jason is Student // true
result = jason is UniversityStudent // true

if yagom is UniversityStudent {
    print("yagom은 대학생입니다")
} else if yagom is Student {
    print("yagom은 학생입니다")
} else if yagom is Person {
    print("yagom은 사람입니다")
} // yagom은 사람입니다

switch jason {
case is Person:
    print("jason은 사람입니다")
case is Student:
    print("jason은 학생입니다")
case is UniversityStudent:
    print("jason은 대학생입니다")
default:
    print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 사람입니다

switch jason {
case is UniversityStudent:
    print("jason은 대학생입니다")
case is Student:
    print("jason은 학생입니다")
case is Person:
    print("jason은 사람입니다")
default:
    print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 대학생입니다


업 캐스팅

as를 사용하여 부모클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입정보를 전환해줍니다. Any 혹은 AnyObject로도 타입정보를 변환할 수 있습니다. 암시적으로 처리되므로 꼭 필요한 경우가 아니라면 생략해도 무방합니다.

// UniversityStudent 인스턴스를 생성하여 Person 행세를 할 수 있도록 업 캐스팅
var mike: Person = UniversityStudent() as Person

var jenny: Student = Student()
//var jina: UniversityStudent = Person() as UniversityStudent // 컴파일 오류

// UniversityStudent 인스턴스를 생성하여 Any 행세를 할 수 있도록 업 캐스팅
var jina: Any = Person() // as Any 생략가능


다운 캐스팅

as? 또는 as!를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입정보를 전환해줍니다.


조건부 다운 캐스팅

as?를 사용합니다. 캐스팅에 실패하면, 즉 캐스팅하려는 타입에 부합하지 않는 인스턴스라면 nil을 반환하기 때문에 결과의 타입은 옵셔널 타입입니다.

var optionalCasted: Student?

optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent // nil
optionalCasted = jina as? UniversityStudent // nil
optionalCasted = jina as? Student // nil


강제 다운 캐스팅

as!를 사용합니다. 캐스팅에 실패하면, 즉 캐스팅하려는 타입에 부합하지 않는 인스턴스라면 런타임 오류가 발생합니다. 캐스팅에 성공하면 옵셔널이 아닌 일반 타입을 반환합니다.

var forcedCasted: Student

optionalCasted = mike as! UniversityStudent
//optionalCasted = jenny as! UniversityStudent // 런타임 오류
//optionalCasted = jina as! UniversityStudent // 런타임 오류
//optionalCasted = jina as! Student // 런타임 오류


활용

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) // 등교를 합니다
doSomethingWithSwitch(someone: yagom) // 숨을 쉽니다


func doSomething(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()
    }
}

doSomething(someone: mike as Person) // 멤버쉽 트레이닝을 갑니다 신남!
doSomething(someone: mike) // 멤버쉽 트레이닝을 갑니다 신남!
doSomething(someone: jenny) // 등교를 합니다
doSomething(someone: yagom) // 숨을 쉽니다


관련문서

The Swift Programming Language - Type Casting





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
Posted by yagom

옵셔널 체이닝

옵셔널 체이닝은 옵셔널의 내부의 내부의 내부로 옵셔널이 연결되어 있을 때 유용하게 활용할 수 있습니다. 매 번 nil 확인을 하지 않고 최종적으로 원하는 값이 있는지 없는지 확인할 수 있습니다. 


소스코드


예제 클래스

class Person {
    var name: String
    var job: String?
    var home: Apartment?
    
    init(name: String) {
        self.name = name
    }
}

class Apartment {
    var buildingNumber: String
    var roomNumber: String
    var `guard`: Person?
    var owner: Person?
    
    init(dong: String, ho: String) {
        buildingNumber = dong
        roomNumber = ho
    }
}


옵셔널 체이닝 사용

let yagom: Person? = Person(name: "yagom")
let apart: Apartment? = Apartment(dong: "101", ho: "202")
let superman: Person? = Person(name: "superman")


// 옵셔널 체이닝이 실행 후 결과값이 nil일 수 있으므로
// 결과 타입도 옵셔널입니다

// 만약 우리집의 경비원의 직업이 궁금하다면..?

// 옵셔널 체이닝을 사용하지 않는다면...
func guardJob(owner: Person?) {
    if let owner = owner {
        if let home = owner.home {
            if let `guard` = home.guard {
                if let guardJob = `guard`.job {
                    print("우리집 경비원의 직업은 \(guardJob)입니다")
                } else {
                    print("우리집 경비원은 직업이 없어요")
                }
            }
        }
    }
}

guardJob(owner: yagom)

// 옵셔널 체이닝을 사용한다면
func guardJobWithOptionalChaining(owner: Person?) {
    if let guardJob = owner?.home?.guard?.job {
        print("우리집 경비원의 직업은 \(guardJob)입니다")
    } else {
        print("우리집 경비원은 직업이 없어요")
    }
}

guardJobWithOptionalChaining(owner: yagom)
// 우리집 경비원은 직업이 없어요



yagom?.home?.guard?.job // nil

yagom?.home = apart

yagom?.home // Optional(Apartment)
yagom?.home?.guard // nil

yagom?.home?.guard = superman

yagom?.home?.guard // Optional(Person)

yagom?.home?.guard?.name // superman
yagom?.home?.guard?.job // nil

yagom?.home?.guard?.job = "경비원"




nil 병합 연산자

중위 연산자입니다. ??

예) Optional ??  Value


옵셔널 값이 nil일 경우, 우측의 값을 반환합니다. 

띄어쓰기에 주의하여야 합니다.

var guardJob: String
    
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob) // 경비원

yagom?.home?.guard?.job = nil

guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
print(guardJob) // 슈퍼맨



관련문서

* The Swift Programming Language - Optional Chaining

* The Swift Programming Language - Basic Operators - Nil-Coalescing Operator





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
Posted by yagom

상속

Swift/기본문법 2017.06.29 11:00

상속

스위프트의 상속은 클래스, 프로토콜 등에서 가능합니다. 열거형, 구조체는 상속이 불가능합니다. 스위프트는 다중상속을 지원하지 않습니다.   

이번 파트에서는 클래스의 상속에 대해서 알아봅니다


소스코드



클래스의 상속과 재정의

상속 문법

class 이름: 상속받을 클래스 이름 {
    /* 구현부 */
}



// 기반 클래스 Person
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 finalCalssMethod() {
        print("type method - final class")
    }
}

// Person을 상속받는 Student
class Student: Person {
    var major: String = ""
    
    override func selfIntroduce() {
        print("저는 \(name)이고, 전공은 \(major)입니다")
    }
    
    override class func classMethod() {
        print("overriden type method - class")
    }
    
    // static을 사용한 타입 메서드는 재정의 할 수 없습니다
//    override static func typeMethod() {    }
    
    // final 키워드를 사용한 메서드, 프로퍼티는 재정의 할 수 없습니다
//    override func sayHello() {    }
//    override class func finalClassMethod() {    }

}


동작 확인

let yagom: Person = Person()
let hana: Student = Student()

yagom.name = "yagom"
hana.name = "hana"
hana.major = "Swift"

yagom.selfIntroduce()
// 저는 yagom입니다

hana.selfIntroduce()
// 저는 hana이고, 전공은 Swift입니다

Person.classMethod()
// type method - class

Person.typeMethod()
// type method - static

Person.finalCalssMethod()
// type method - final class


Student.classMethod()
// overriden type method - class

Student.typeMethod()
// type method - static

Student.finalCalssMethod()
// type method - final class



관련문서

The Swift Programming Language - Inheritance





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

옵셔널 체이닝  (0) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
클로저 - Closure  (0) 2017.06.22
클래스, 구조체, 열거형 비교  (0) 2017.06.19
Posted by yagom


Perfect 미니 프로젝트 [1]

1. 프로젝트 개요
2. mongoDB 설치 

3. 패키지 설치 



우분투에 스위프트 설치를 시작으로 이후 두 포스팅에 걸쳐 퍼펙트에 대해 조금 알아보았습니다.



2017/04/03 - [Swift/Perfect] - 우분투(Ubuntu)에 스위프트 설치하기

2017/05/17 - [Swift/Perfect] - Perfect 시작하기

2017/05/24 - [Swift/Perfect] - Perfect 라우팅


이번 부터는 실질적으로 퍼펙트를 사용하여 작은 미니프로젝트를 해보려합니다.

아... 물론 이런 허접한 구성은 실제 서버에서 사용하면 안되지만 간단한 예제로 감만 잡아보는 겁니다. 인증도 없고, 뭐도 없고, 보안도 안되고... 뭐... 암것도 없어요. 그러나 한 번 작은 프로젝트 해보는데에 의의를 가지는 것이니 나중에 삘받으면 하나하나 덧붙여 가면 됩니다.

그저 DB 연결해보고, API로 클라이언트와 핑퐁 해보는 것이 전부이긴 하지만, 그래도 감잡기는 좋을 것 같아요 :)

이 감 말고요... 'ㅁ' 아하핳


프로젝트 개요

간단한 사진게시판을 위한 REST API 서버를 만들어보겠습니다.

API 

HTTP Method 

URI 

사진 게시물 등록 

POST 

/article 

사진 게시물 목록

GET 

사진 게시물 수정 

POST 

/article/{article_id}

사진 게시물 삭제 

DELETE

사진 게시물 정보 가져오기 

GET


사진 게시물 등록

요청(Request)

  • HTTP Method : POST
  • Content-Type: multipart/form-data

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

image 

binary data

 

이미지 데이터 

user_name

string 

 

사용자 이름 

Y

description

string 

 

이미지 설명 

N

title

string 

 

이미지 제목 

Y


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id 

string 

업로드된 게시물 고유 식별자 


사진 게시물 목록

요청(Request)

  • HTTP Method : GET
  • Content-Type: application/json

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

page

integer

조회하고자 하는 페이지 번호

user_name

string 


특정 사용자의 게시물만 받아오고자 할 때

N

articles_per_page

integer

1~100 / 10 

각 페이지 당 게시물 수 

N


응답(Response)

Key 

자료형 

비고 

필수여부 

articles 

json object array (string) 

게시물 정보 배열 

Y

articles_per_page

integer

각 페이지 당 게시물 수

 Y

current_page

integer 

전송된 페이지 

total_page

integer 

전체 페이지 수 

Y

articles item object 형태

Key 

자료형 

비고 

필수여부 

image_url

string

이미지 URL

Y

user_name

string

업로드한 사용자 이름 

description

string 

이미지 설명 

title

string 

이미지 제목 

article_id

string 

게시물 고유 식별자


사진 게시물 수정

요청(Request)

  • HTTP Method : POST
  • Content-Type: multipart/form-data

 매개변수

자료형 

값의 범위/기본 값 

비고 

 필수여부

image 

binary data

이전 데이터

이미지 데이터 

N

user_name

string 


사용자 이름이 이전 사용자 이름과 일치하지 않으면 수정에 실패

Y

description

string 

이전 데이터

이미지 설명 

N

title

string 

이전 데이터

이미지 제목 

N


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id 

string 

수정된 게시물 고유 식별자


사진 게시물 삭제

요청(Request)

  • HTTP Method : DELETE
  • Content-Type: application/json

매개변수 없음


응답(Response)

Key 

자료형 

비고 

필수여부 

article_id

string

삭제된 게시물 고유 식별자

Y


사진 게시물 정보 가져오기 

요청(Request)

  • HTTP Method : GET
  • Content-Type: application/json

매개변수 없음


응답(Response)

매개변수 

자료형 

비고 

필수여부 

image_url

string

이미지 URL

Y

user_name

string

업로드한 사용자 이름 

description

string 

이미지 설명 

title

string 

이미지 제목 

article_id

string 

게시물 고유 식별자



모든 응답데이터는 JSON 형식을 사용합니다.

이 정도를 구현해 볼건데요, 데이터베이스가 필요할테니 쓸만한 데이터베이스가 무엇이 있을지 찾아봅니다.


현재(2017년 6월) 퍼펙트에서 공식적으로 제공하는 데이터베이스 커넥터 목록입니다.

  • SQLite
  • MySQL
  • MariaDB
  • PostgreSQL
  • FileMaker
  • MongoDB
  • Working with BSON
  • Apache CouchDB
  • LDAP
  • Kafka
  • Mosquitto
  • ZooKeeper
  • Hadoop

와우... 많네요!! 저는... 몽고디비를 써봐야겠습니다. 



mongoDB 설치

그러려면 먼저 mongoDB를 설치해봐야겠지요.

설치방법은 아래 링크에 친절히 나와있으니 참고하시면 되겠습니다 :)


퍼펙트에서 몽고디비를 사용하기 위해서는 mongo-c 라이브러리를 사용해야합니다. 

먼저, mongo-c 드라이버를 설치합니다. mongo-c와 관련된 설명은 Perfect의 문서를 참고하셔도 좋습니다.


macOS

HomeBrew를 먼저 설치(링크)하신 후, 터미널에서 아래 명령어를 입력합니다.

> brew install mongo-c-driver


ubuntu

아래 명령어를 입력합니다.

> sudo apt-get install libmongoc


패키지 설치

그리고 이제 새로운 프로젝트를 시작하기 위해서 프로젝트를 진행하길 원하는 위치에서 프로젝트 폴더를 생성합니다. 프로젝트 폴더 이름은 ImageBoard라고 하겠습니다.

> mkdir ImageBoard
> cd ImageBoard


그 다음 스위프트 패키지 매니저를 통해 패키지 초기화를 합니다. 스위프트 패키지 매니저는 지난 포스트[우분투(Ubuntu)에 스위프트 설치하기]에서 설치했었죠?

> swift package init --type executable 


swift package init 명령어를 실행 후 폴더에 생성된 Package.swift 파일을 열어서 Perfect 패키지 의존성을 추가합니다.

> vi Package.swift


Package.swift 파일에 아래 코드를 작성합니다.  (우리는 새로운 프로젝트를 만들면서 함께 mongo-c 패키지도 설치할거예요.)

import PackageDescription let package = Package( name: "ImageBoard", targets: [], dependencies: [ .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", versions: Version(0,0,0)..<Version(10,0,0)), .Package(url:"https://github.com/PerfectlySoft/Perfect.git", versions: Version(0,0,0)..<Version(10,0,0)), .Package(url:"https://github.com/PerfectlySoft/Perfect-MongoDB.git", versions: Version(0,0,0)..<Version(10,0,0)) ] )

위 패키지 설명은 Perfect HTTP Sever, Perfect, Perfect MongoDB 커넥터 패키지를 버전 0부터 10까지 중 최신 버전을 가져오도록 합니다. (현재시점으로 모두 2.x.x 버전)


소스를 저장하고 나와서 패키지를 설치합니다.

> swift package update


만약에 맥에서 Xcode 프로젝트를 생성해주고 싶다면

> swift package generate-xcodeproj

해주시면 해당 폴더에 Xcode 프로젝트가 생성됩니다. 그러면 이후 코딩을 Xcode 프로젝트로 진행할 수 있습니다.



이제 프로젝트 준비가 완료되었습니다!

다음부터는 실질적으로 코딩을 해봅니다 :D

다음에 또 만나요~!




by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (2) 2017.06.27
Perfect 라우팅  (0) 2017.05.24
Perfect 시작하기  (0) 2017.05.17
우분투(Ubuntu)에 스위프트 설치하기  (0) 2017.04.03
Posted by yagom

프로퍼티

프로퍼티는 클래스, 구조체, 열거형과 연관된 값입니다. 타입과 관련된 값을 저장할 수도, 연산할 수도 있습니다.


소스코드


프로퍼티의 종류

  • 인스턴스 저장 프로퍼티
  • 타입 저장 프로퍼티
  • 인스턴스 연산 프로퍼티
  • 타입 연산 프로퍼티
  • 지연 저장 프로퍼티

이번 파트에서는 지연 저장 프로퍼티를 제외한 저장 프로퍼티와 연산 프로퍼티에 대해 알아봅니다.


정의와 사용

프로퍼티는 구조체, 클래스, 열거형 내부에 구현할 수 있습니다. 다만 열거형 내부에는 연산 프로퍼티만 구현할 수 있습니다. 연산 프로퍼티는 var로만 선언할 수 있습니다.

연산프로퍼티를 읽기전용으로는 구현할 수 있지만, 쓰기 전용으로는 구현할 수 없습니다. 읽기전용으로 구현하려면 get 블럭만 작성해주면 됩니다. 읽기전용은 get블럭을 생략할 수 있습니다. 읽기, 쓰기 모두 가능하게 하려면 get 블럭과 set블럭을 모두 구현해주면 됩니다.  

set 블럭에서 암시적 매개변수 newValue를 사용할 수 있습니다.

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)입니다")
    }
     */
    
    // 읽기전용 인스턴스 연산 프로퍼티
    // 간단히 위의 selfIntroduce() 메서드를 대체할 수 있습니다
    var selfIntroduction: String {
        get {
            return "저는 \(self.class)반 \(name)입니다"
        }
    }
        
    /*
     // 타입 메서드
     static func selfIntroduce() {
     print("학생타입입니다")
     }
     */
    
    // 읽기전용 타입 연산 프로퍼티
    // 읽기전용에서는 get을 생략할 수 있습니다
    static var selfIntroduction: String {
        return "학생타입입니다"
    }
}

// 타입 연산 프로퍼티 사용
print(Student.selfIntroduction)
// 학생타입입니다

// 인스턴스 생성
var yagom: Student = Student()
yagom.koreanAge = 10

// 인스턴스 저장 프로퍼티 사용
yagom.name = "yagom"
print(yagom.name)
// yagom

// 인스턴스 연산 프로퍼티 사용
print(yagom.selfIntroduction)
// 저는 Swift반 yagom입니다

print("제 한국나이는 \(yagom.koreanAge)살이고, 미쿡나이는 \(yagom.westernAge)살입니다.")
// 제 한국나이는 10살이고, 미쿡나이는 9살입니다.


응용

struct Money {
    var currencyRate: Double = 1100
    var dollar: Double = 0
    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
            dollar = newValue / currencyRate
        }
    }
}

var moneyInMyPocket = Money()

moneyInMyPocket.won = 11000

print(moneyInMyPocket.won)
// 11000

moneyInMyPocket.dollar = 10

print(moneyInMyPocket.won)
// 11000


지역변수 및 전역변수

저장 프로퍼티와 연산 프로퍼티의 기능은 함수, 메서드, 클로저, 타입 등의 외부에 위치한 지역/전역 변수에도 모두 사용 가능합니다.

var a: Int = 100
var b: Int = 200
var sum: Int {
    return a + b
}

print(sum) // 300


프로퍼티 감시자


프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행할 수 있습니다. 값이 변경되기 직전에 willSet블럭이, 값이 변경된 직후에 didSet블럭이 호출됩니다. 둘 중 필요한 하나만 구현해 주어도 무관합니다. 변경되려는 값이 기존 값과 똑같더라도 프로퍼티 감시자는 항상 동작합니다. 

willSet 블럭에서 암시적 매개변수 newValue를 사용할 수 있고, didSet 블럭에서 암시적 매개변수 oldValue를 사용할 수 있습니다.

프로퍼티 감시자는 연산 프로퍼티에 사용할 수 없습니다.


소스코드



정의 및 사용

struct Money {
    // 프로퍼티 감시자 사용
    var currencyRate: Double = 1100 {
        willSet(newRate) {
            print("환율이 \(currencyRate)에서 \(newRate)으로 변경될 예정입니다")
        }
        
        didSet(oldRate) {
            print("환율이 \(oldRate)에서 \(currencyRate)으로 변경되었습니다")
        }
    }

    // 프로퍼티 감시자 사용
    var dollar: Double = 0 {
        // willSet의 암시적 매개변수 이름 newValue
        willSet {
            print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
        }
        
        // didSet의 암시적 매개변수 이름 oldValue
        didSet {
            print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
        }
    }

    // 연산 프로퍼티
    var won: Double {
        get {
            return dollar * currencyRate
        }
        set {
            dollar = newValue / currencyRate
        }
        
        /* 프로퍼티 감시자와 연산 프로퍼티 기능을 동시에 사용할 수 없습니다
        willSet {
            
        }
         */
    }    
}

var moneyInMyPocket: Money = Money()

// 환율이 1100.0에서 1150.0으로 변경될 예정입니다
moneyInMyPocket.currencyRate = 1150
// 환율이 1100.0에서 1150.0으로 변경되었습니다

// 0.0달러에서 10.0달러로 변경될 예정입니다
moneyInMyPocket.dollar = 10
// 0.0달러에서 10.0달러로 변경되었습니다

print(moneyInMyPocket.won)
// 11500.0



관련문서

The Swift Programming Language - Properties





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
클로저 - Closure  (0) 2017.06.22
클래스, 구조체, 열거형 비교  (0) 2017.06.19
열거형 - Enumerations  (0) 2017.06.15
Posted by yagom

Swift 4 변경사항

Swift 2017.06.23 11:00


오늘의 주제

스위프트 4 추가/변경사항


아, 벌써 스위프트가 4번째 버전이 나오네요! 참으로 빠르게 변합니다. 이제 문법적으로 변경되는 부분이 크게 많지 않아서 서 3 버전과 크게 달라진 문법은 많지 않습니다. 내부적으로 수정되거나 추가된 문법이 많고, 삭제된 부분은 거의 없습니다.

긴 말 필요없이 한 번 무엇이 바뀌었는지 살펴볼까요?

스따뚜~~~~~~~~~~~~


단방향 범위 연산자

이제 범위연산자에서 양 쪽 끝을 모두 신경쓸 필요가 없습니다, 야호! [SE-0172]

var numbers = [1, 2, 3, 4, 5]

// Swift 3
numbers[2..<numbers.endIndex] // [3, 4, 5]
numbers[0...2] // [1, 2, 3]
numbers[0..<2] // [1, 2]

// Swift 4
numbers[2...] // [3, 4, 5]
numbers[...2] // [1, 2, 3]
numbers[..<2] // [1, 2]

numbers = []

// Swift 3
//numbers[0...numbers.endIndex] // out of range - error

// Swift 4
numbers[0...] // []


let number: Int = 100

// Swift 3
switch number {
case Int.min..<0:
    print("negative")
case 0:
    print("zero")
case 1...Int.max:
    print("positive")
default:
    print("unknwon")
}


// Swift 4
switch number {
case ..<0:
    print("negative")
case 0:
    print("zero")
case 1...:
    print("positive")
default:
    print("unknwon")
}


 

String의 많은 변화

Swift 4에서 String은 정말 많은 변화가 있는 부분 중 하나입니다. 그 중 눈에 띌만한 내용을 위주로 정리했습니다. 더 많은 사항은 [String Processing For Swift 4] 문서를 참고해보시기 바랍니다.


String 구조체가 다시 Collection 프로토콜을 따릅니다. [SE-0163]

var greeting: String = "Hello, Swift!"

// Swift 3
greeting.characters.forEach { print($0) }
print(greeting.characters.count) // 13

// Swift 4
greeting.forEach { print($0) }
print(greeting.count) // 13


Unicode 9이 적용되었습니다. [String Processing For Swift 4 - Unicode 9 Conformance]

이전에 문자열 길이 결과가 제각각이었던 유니코드 문자들이 사람이 인지할 수 있는 단위로 세어집니다.

// Swift 3
"😍".characters.count // 1
"🇰🇷".characters.count // 1
"🐻".characters.count // 1
"🙌🏻".characters.count // 2
"🙌🏾".characters.count // 2
"‍‍👨‍👨‍👦".characters.count // 3
"‍‍‍👨‍👨‍👦‍👦".characters.count // 4

// Swift 4
"😍".count // 1
"🇰🇷".count // 1
"🐻".count // 1
"🙌🏻".count // 1
"🙌🏾".count // 1
"‍‍👨‍👨‍👦".count // 1
"‍‍‍👨‍👨‍👦‍👦".count // 1


여러 줄 리터럴 문법이 생겼습니다. [SE-0168]

  • 큰따옴표 세 개를 연결하여 표현하면 여러 줄 문자열을 표현하는 리터럴 문법입니다.   
  • 반드시 """ 다음 줄부터 문자열을 입력해야하며, 문자열 마지막 줄 다음줄에 """로 닫아줘야 합니다.

// Swift 3
greeting = "Hello\nHow are you?\nI'm fine thanks, and you?"

// Swift 4
greeting = """
Hello
How are you?
I'm fine thanks, and you?
"""


Substring 타입과 StringProtocol 프로토콜이 생겼습니다.

기존에 String 타입에 구현되어있던 주요 기능을 StringProtocol에 기본구현 하였습니다.

let subIndex = greeting.index(greeting.startIndex, offsetBy: 4)

// Swift 3
let subGreeting = greeting[greeting.startIndex...subIndex]
print(type(of: subGreeting)) // String

// Swift 4
let subGreeting = greeting[...subIndex]
print(type(of: subGreeting)) // Substring
let stringFromSubstring: String = String(subGreeting)


Character 타입에 unicodeScalars 프로퍼티가 추가되었습니다. [SE-0178]

Swift 3에서는 Character 인스턴스의 유니코드 스칼라 값을 알아내려면 String 타입으로 변환하여야 했지만, 이제는 바로 Character 인스턴스의 unicodeScalars 프로퍼티를 이용해 알아낼 수 있습니다.

let character: Character = ""

// Swift 3
print(String(character).unicodeScalars)

// Swift 4
print(character.unicodeScalars)


DictionarySet

자주 사용하는 콜렉션 타입인 DictionarySet의 기능이 한층 강화되었습니다.  

[SE-0154], [SE-0165]


키와 값의 시퀀스를 통해 새로운 딕셔너리를 생성할 수 있습니다. 또, 키와 값을 기존의 딕셔너리에 병합할 수 있습니다.

let zipSequence = zip("abcdefghijklmnopqrstuvwxyz", 97...)
let asciiTable = Dictionary(uniqueKeysWithValues: zipSequence)
// ["w": 119, "n": 110, "u": 117, "v": 118, "x": 120, "q": 113, ...]

let vegetables = ["tomato", "carrot", "onion", "onion", "carrot", "onion"]
let vegetableZipSequence = zip(vegetables, repeatElement(1, count: Int.max))

var vegetableCounts = Dictionary(vegetableZipSequence, uniquingKeysWith: +)
vegetableCounts.merge([("tomato", 1)], uniquingKeysWith: +)
// ["tomato": 2, "carrot": 2, "onion": 3]


DictionarySetfilter 결과가 이제 Array대신 각각의 원래 타입으로 반환됩니다. 또, 딕셔너리는 mapValues(_:) 메서드를 통해 값이 변형된 새로운 딕셔너리를 만들 수 있습니다.

let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
let asciiVowels = asciiTable.filter({ vowels.contains($0.key) })

// Swift 3
print(type(of: asciiVowels)) // Array<(key : Character, value : Int)>

// Swift 4
print(type(of: asciiVowels)) // Dictionary<Character, Int>
asciiVowels["a"]  // 97
asciiVowels["b"]  // nil

let filteredSet: Set<Character> = vowels.filter{ $0 < "h" }

// Swift 3
print(type(of: filteredSet)) // Array<Character>

// Swift 4
print(type(of: filteredSet)) // Set<Character>

// Swift 3
let strangeMap = asciiTable.map { [$0: "0x" + String($1, radix: 16)] }
print(type(of: strangeMap)) // Array<Dictionary<Character, String>>
// [["u": "0x75"], ["v": "0x76"], ["w": "0x77"], ["x": "0x78"], ["q": "0x71"], ["n": "0x6e"], ...]

// Swift 4
let asciiHexTable = asciiTable.mapValues({ "0x" + String($0, radix: 16) })
// ["w": "0x77", "n": "0x6e", "u": "0x75", "v": "0x76", "x": "0x78", ...]


딕셔너리 서브스크립트 문법이 추가되었습니다. 원하는 키에 해당하는 값이 없으면 nil 대신 기본값을 돌려줍니다. 

기본값이 있다는 것은 값이 항상 존재한다는 의미이므로, 해당 서브스크립트의 반환값은 옵셔널이 아닌 값이라는 뜻입니다. 딕셔너리 옵셔널 전쟁에서 해방될 수도!

let favoriteSubject: [String: String] = ["yagom": "swift", "hana": "communication", "jisu": "swift"]

// Swift 3
type(of: favoriteSubject["minji"]) // Optional<String>

// Swift 4
type(of: favoriteSubject["minji"]) // Optional<String>
type(of: favoriteSubject["minji", default: "unknown"]) // String

var favoriteCount = [String: Int]()

favoriteSubject.forEach { favoriteCount[$0.value, default: 0] += 1 }
print(favoriteCount) // ["swift": 2, "communication": 1]


init(grouping:by:) 이니셜라이저를 통해 특정 조건에 따라 Array 또는 다른 시퀀스를 딕셔너리 형태로 그룹지을 수 있습니다. 그룹 기준값은 딕셔너리의 키가 됩니다.

struct Person: CustomStringConvertible {
    enum Gender {
        case male, female, unknwon
    }
    
    let name: String
    var gender: Gender
    
    var description: String { return name }
}

let yagom = Person(name: "yagom", gender: .male)
let hana = Person(name: "hana", gender: .female)
let jisu = Person(name: "jisu", gender: .unknwon)
let eric = Person(name: "eric", gender: .male)
let mike = Person(name: "mike", gender: .male)

let friends = [yagom, hana, jisu, eric, mike]
let friendsByGender = Dictionary(grouping: friends, by: { $0.gender })
print(type(of: friendsByGender))    // Dictionary<Gender, Array<Person>>
print(friendsByGender)
// [Person.Gender.unknwon: [jisu], Person.Gender.male: [yagom, eric, mike], Person.Gender.female: [hana]]


reserveCapacity(_:) 메서드를 통해 Array 처럼 예약된 공간을 가질 수 있습니다.

var emptyDictionary = [String: String]()
print(emptyDictionary.capacity) // 0
emptyDictionary.reserveCapacity(100)
print(emptyDictionary.capacity) // 192
// 최소 100개의 요소가 들어갈 수 있도록 공간을 확보하기 때문에 꼭 100이지 않을 수 있습니다
emptyDictionary.reserveCapacity(10)
print(emptyDictionary.capacity) // 192
emptyDictionary.reserveCapacity(1000)
print(emptyDictionary.capacity) // 1536


Key path

Cocoa에서 단순한 문자열로 표현하던 key path를 스위프트에서는 정확한 타입(KeyPath 라는 제네릭 클래스) 방식으로 표현합니다. 조금 더 안전한 동적 프로그래밍을 할 수 있지 않을까 기대됩니다. [SE-0161]

  • key path를 통하여 프로퍼티에 접근할 수 있도록 모든 타입에 자동으로 [keyPath:] 서브스크립트 메서드가 추가됩니다.
  • key path는 백슬래시(\)를 통해 표현합니다.

struct Person {
    var name: String
}

struct Stuff {
    var name: String
    var owner: Person
}


var yagom = Person(name: "yagom")
let macbook = Stuff(name: "macbook pro", owner: yagom)

print(type(of: \Person.name)) // WritableKeyPath<Person, String>

var result: Any = macbook[keyPath: \Stuff.name]
print(result) // macbook pro

let keyPath = \Stuff.owner
let nameKeyPath = keyPath.appending(path: \.name)

result = macbook[keyPath: nameKeyPath]
print(result) // yagom

result = macbook[keyPath: \Stuff.owner.name]
print(result) // yagom


프로토콜 혼합 타입

Objective-C 에서 특정 클래스와 프로토콜을 동시에 따르고 있는 타입이라는 의미로 사용되었던 표현이 있습니다. 

SomeClass<SomeProtocol, AnotherProtocol> *name
// SomeProtocol과 AnotherProtocol을 준수하는 SomeClass 타입의 변수 name

이 표현이 이제 스위프트에서도 가능해졌습니다. [SE-0156]

class ClassA { }
class ClassB: ClassA { }
protocol ProtocolA { }
extension ClassB: ProtocolA { }

var someVariable: ClassA & ProtocolA
// someVariable = ClassA()
// 오류발생 : ProtocolA를 충족하지 않음

someVariable = ClassB() // 모든 조건 충족


where 확장

프로토콜과 그 연관 타입에 where 절을 사용하여 타입 제약을 줄 수 있습니다. [SE-0142]

protocol StringRepresentable: RawRepresentable
where RawValue == String { }

protocol RawStringWrapper {
  associatedtype Wrapped: RawRepresentable
    where Wrapper.RawValue == String
}



제네릭 서브스크립트

서브스크립트가 이제 제네릭 매개변수와 반환 타입을 사용할 수 있게 되었습니다. [SE-0148]

struct CustomModel<Key: Hashable, Value> {
    var dictionary: [Key: Value]
    
    subscript<T>(key: Key) -> T? {
        return dictionary[key] as? T
    }
}

let information = CustomModel(dictionary: ["name": "yagom", "age": 100, "height": 183.0])
let name: String? = information["name"] // yagom


Private 접근수준의 변경

기존의 private 접근수준은 같은 파일인 여부와 상관없이 private 요소를 해당 범위를 벗어나면 사용할 수 없었습니다. 대체제로 fileprivate를 사용하였는데, 이는 문제를 야기할 수 있는 가능성을 가지고 있습니다. 같은 파일이라도 접근을 원치 않는 요소가 있는데, extension 등으로 타입을 확장하여 요소를 사용려면 fileprivate로 지정해 주어야 하는 문제가 있었기 때문이죠. 아래 Swift 3의 예를 먼저 보겠습니다.

// Swift 3
struct Person {
    var name: String
    // private로 지정하면 extension에서 접근할 수 없기 때문에 fileprivate로 선언
    fileprivate var age: Int = 0
    init(name: String) {
        self.name = name
    }
}

extension Person {
    mutating func passedYear() {
        self.age += 1
    }
}

var yagom = Person(name: "yagom")
yagom.passedYear()
yagom.age // 접근가능! 원하던 시나리오가 아님!


나이라는 민감한 부분은 숨기고 싶었기 때문에 private로, 어디서든 접근을 막고 싶었지만, 같은 파일의 익스텐션에서 조차 접근할 수 없기 때문에 fileprivate로 접근수준을 지정했습니다. 그 결과, 엉뚱하게 같은 파일의 다른 소스에서도 접근할 수 있게 되었습니다.

// Swift 4

struct Person { var name: String private var age: Int = 0 init(name: String) { self.name = name } } extension Person { mutating func passedYear() { // private라도 같은 파일의 extension에서 접근 가능 self.age += 1 } } var yagom = Person(name: "yagom") yagom.passedYear() // yagom.age // 접근불가

Swift 4에서는 이 문제를 개선하여 같은 파일의 익스텐션에서는 접근할 수 있도록 변경되었습니다.


아카이브와 시리얼라이제이션 / JSON 인코딩

NSCodingNSObject를 손쉽게 사용할 수 있는 클래스 타입을 제외하고, 구조체나 열거타입에서는 아카이빙이 참으로 난해했습니다. 물론 NSObject를 상속받지 않는 스위프트 고유 클래스도 마찬가지였습니다. 그러나 이제 타입이 스스로 어떻게 아카이브하고 시리얼라이즈 할지 정의할 수 있게 되었습니다. 그저 타입과 그 타입의 하위 타입이 모두 Codable 프로토콜을 준수하면 아카이브하고 그것을 풀어낼 수 있도록 할 수 있습니다. 기존에 사용하던 NSKeyedArchiver 클래스도 Codable 프로토콜을 완벽히 지원합니다. Codable과 함께 JSONEncoder, JSONDecoder로 인해 JSON 인코딩과 디코딩이 엄청나게 편해졌습니다!! 으아아아아아아아~!! [SE-0166],  [SE-0167]

struct Person: Codable {
    enum Gender: String, Codable {
        case male, female, unknown
    }
    
    var name: String
    var age: Int
    var gender: Gender
    var friends: [Person]
}

let yagom = Person(name: "yagom", age: 20, gender: .male, friends: [])
let hana = Person(name: "hana", age: 22, gender: .female, friends: [yagom])
let eric = Person(name: "eric", age: 25, gender: .male, friends: [yagom, hana])

var encoder = JSONEncoder()

let jsonData = try encoder.encode(eric)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString) 
// "{\"age\":25,\"gender\":\"male\",\"friends\":[{\"age\":20,\"gender\":\"male\",\"friends\":[],\"name\":\"yagom\"},{\"age\":22,\"gender\":\"female\",\"friends\":[{\"age\":20,\"gender\":\"male\",\"friends\":[],\"name\":\"yagom\"}],\"name\":\"hana\"}],\"name\":\"eric\"}"

let decoder = JSONDecoder()
let decoded: Person = try decoder.decode(Person.self, from: jsonData)
print(decoded.name) // eric


NSNumber 브릿징

예상치 못한 결과를 낼 수 있는 NSNumber 브릿징 결과가 수정되었습니다.

// Swift 3

let numberOne = NSNumber(value: Int64.max)

if numberOne is Int16 {
    print("numberOne == Int16")
} else if numberOne is Int64 {
    print("numberOne == Int64")
} // numberOne == Int16

let numberTwo = NSNumber(value: UInt32(777))
if let value = numberTwo as? Int8 {
    print(value)
} // 9

// Swift 4
let numberOne = NSNumber(value: Int64.max)

if numberOne is Int16 {
    print("numberOne == Int16")
} else if numberOne is Int64 {
    print("numberOne == Int64")
} // numberOne == Int64

let numberTwo = NSNumber(value: UInt32(777))
if let value = numberTwo as? Int8 {
    print(value)
} // 아무 출력 없음


그 외 기타 변경사항

* Core Foundation 타입들은 HashableEquatable 프로토콜을 CFHashCFEqual 함수구현을 통해 준수합니다. Swift 3 모드(Swift 3.2 버전)에서도 적용되는 사항입니다. [SR-2388]

* 암시적인 @objc 선언을 이제 더욱 명확히 표현해 주어야 합니다. [SE-0160]

* raw buffer를 슬라이스 한 결과는 더이상 같은 타입으로 반환되지 않습니다. [SE-0138]

* 재정의(override)가 더 완벽하게 지원됩니다. [SR-1529]

* 제네릭 매개변수를 갖는 서브스크립트를 정의할 수 있습니다. [SE-0148]

* 스위프트 타입 시스템이 이제는 하나의 튜플 전달인자를 가지는 함수와 여러 매개변수를 가지는 함수를 구분합니다. [SE-0110]

* 여러 연산자를 수반한 정수형 상수를 정의하는 C 언어의 매크로의 형태를 더 다양하게 임포트 할 수 있습니다. 

* inout 매개변수를 사용하는 reduce 함수가 추가되었습니다. [SE-0171]

* 컬렉션 타입의 두 요소를 서로 바꿔주는 swapAt(_:_:) 메서드가 추가됐습니다. [SE-0173], [SE-0176]


참고문서

Swift CHANGELOG - Swift 4.0






by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift' 카테고리의 다른 글

Swift 4 변경사항  (8) 2017.06.23
Posted by yagom

클로저

클로저는 코드의 블럭입니다. 일급시민(first-citizen)으로, 전달인자, 변수, 상수 등으로 저장, 전달이 가능합니다.  
함수는 클로저의 일종으로, `이름이 있는 클로저`라고 생각하면 됩니다.


소스코드


기본 클로저 문법

{ (매개변수 목록) -> 반환타입 in
    실행 코드
}


클로저의 사용

// sum이라는 상수에 클로저를 할당
let sum: (Int, Int) -> Int = { (a: Int, b: Int) in
    return a + b
}

let sumResult: Int = sum(1, 2)
print(sumResult) // 3


함수의 전달인자로서의 클로저

클로저는 주로 함수의 전달인자로 많이 사용됩니다. 함수 내부에서 원하는 코드블럭을 실행할 수 있습니다.

let add: (Int, Int) -> Int
add = { (a: Int, b: Int) in
    return a + b
}

let substract: (Int, Int) -> Int
substract = { (a: Int, b: Int) in
    return a - b
}

let divide: (Int, Int) -> Int
divide = { (a: Int, b: Int) in
    return a / b
}

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

var calculated: Int

calculated = calculate(a: 50, b: 10, method: add)

print(calculated) // 60

calculated = calculate(a: 50, b: 10, method: substract)

print(calculated) // 40

calculated = calculate(a: 50, b: 10, method: divide)

print(calculated) // 5

//따로 클로저를 상수/변수에 넣어 전달하지 않고, 
//함수를 호출할 때 클로저를 작성하여 전달할 수도 있습니다.

calculated = calculate(a: 50, b: 10, method: { (left: Int, right: Int) -> Int in
    return left * right
})

print(calculated) // 500



다양한 클로저표현

클로저는 다양한 모습으로 표현될 수 있습니다.

  • 함수의 매개변수 마지막으로 전달되는 클로저는 후행클로저(trailing closure)로 함수 밖에 구현할 수 있습니다. 
  • 컴파일러가 클로저의 타입을 유추할 수 있는 경우 매개변수, 반환 타입을 생략할 수 있습니다.
  • 반환 값이 있는 경우, 암시적으로 클로저의 맨 마지막 줄은 return 키워드를 생략하더라도 반환 값으로 취급합니다.
  • 전달인자의 이름이 굳이 필요없고, 컴파일러가 타입을 유추할 수 있는 경우 축약된 전달인자 이름($0, $1, $2...)을 사용 할 수 있습니다.


소스코드



클로저 매개변수를 갖는 함수 calculate(a:b:method:)와 결과값을 저장할 변수 result를 먼저 선언해둡니다.

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

var result: Int


후행 클로저

클로저가 함수의 마지막 전달인자라면 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로저를 구현할 수 있습니다.

result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
    return left + right
}

print(result) // 20


반환타입 생략

calculate(a:b:method:) 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 때문에 굳이 클로저에서 반환타입을 명시해 주지 않아도 됩니다. 

대신 in 키워드는 생략할 수 없습니다.

result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
    return left + right
})

print(result) // 20

// 후행클로저와 함께 사용할 수도 있습니다
result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
    return left + right
}

print(result) // 20


단축 인자이름

클로저의 매개변수 이름이 굳이 불필요하다면 단축 인자이름을 활용할 수 있습니다. 단축 인자이름은 클로저의 매개변수의 순서대로 $0, $1, $2... 처럼 표현합니다.

result = calculate(a: 10, b: 10, method: {
    return $0 + $1
})

print(result) // 20


// 당연히 후행 클로저와 함께 사용할 수 있습니다
result = calculate(a: 10, b: 10) {
    return $0 + $1
}

print(result) // 20


암시적 반환 표현

클로저가 반환하는 값이 있다면 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급합니다.

result = calculate(a: 10, b: 10) { $0 + $1 } print(result) // 20 // 간결하게 한 줄로 표현해 줄 수도 있습니다 result = calculate(a: 10, b: 10) { $0 + $1 } print(result) // 20


축약 전과 후 비교

//축약 전
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
    return left + right
})

//축약 후
result = calculate(a: 10, b: 10) { $0 + $1 }

print(result) // 20


관련문서

* The Swift Programming Language - Closures

* 일급시민(first-citizen)





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
클로저 - Closure  (0) 2017.06.22
클래스, 구조체, 열거형 비교  (0) 2017.06.19
열거형 - Enumerations  (0) 2017.06.15
클래스  (0) 2017.06.12
Posted by yagom

Class vs Struct/Enum

소스코드


열거형과 구조체는 값 타입이며, 클래스는 참조 타입이라는 것이 가장 큰 차이입니다. 또한, 클래스는 상속이 가능하지만 구조체와 열거형은 상속이 불가능합니다.

struct ValueType {
    var property = 1
}

class ReferenceType {
    var property = 1
}

// 첫 번째 구조체 인스턴스
let firstStructInstance = ValueType()
// 두 번째 구조체 인스턴스에 첫 번째 인스턴스 값 복사
var secondStructInstance = firstStructInstance
// 두 번째 구조체 인스턴스 프로퍼티 값 수정
secondStructInstance.property = 2

// 두 번째 구조체 인스턴스는 첫 번째 구조체를 똑같이 복사한 
// 별도의 인스턴스이기 때문에 
// 두 번째 구조체 인스턴스의 프로퍼티 값을 변경해도
// 첫 번째 구조체 인스턴스의 프로퍼티 값에는 영향이 없음
print("first struct instance property : \(firstStructInstance.property)")    // 1
print("second struct instance property : \(secondStructInstance.property)")  // 2


// 클래스 인스턴스 생성 후 첫 번째 참조 생성
let firstClassReference = ReferenceType()
// 두 번째 참조 변수에 첫 번째 참조 할당
let secondClassReference = firstClassReference
secondClassReference.property = 2

// 두 번째 클래스 참조는 첫 번째 클래스 인스턴스를 참조하기 때문에
// 두 번째 참조를 통해 인스턴스의 프로퍼티 값을 변경하면
// 첫 번째 클래스 인스턴스의 프로퍼티 값을 변경하게 됨
print("first class reference property : \(firstClassReference.property)")    // 2
print("second class reference property : \(secondClassReference.property)")  // 2


관련문서

The Swift Programming Language - Enumerations

The Swift Programming Language - Classes and Structures





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

프로퍼티 - Property  (2) 2017.06.26
클로저 - Closure  (0) 2017.06.22
클래스, 구조체, 열거형 비교  (0) 2017.06.19
열거형 - Enumerations  (0) 2017.06.15
클래스  (0) 2017.06.12
구조체  (2) 2017.06.08
Posted by yagom

열거형


소스코드



정의 문법

스위프트의 열거형은 다른 언어의 열거형과는 많이 다릅니다. 잘 살펴보아야 할 스위프트의 기능 중 하나입니다.

  • enum은 타입이므로 대문자 카멜케이스를 사용하여 이름을 정의합니다
  • 각 case는 소문자 카멜케이스로 정의합니다
  • 각 case는 그 자체가 고유의 값입니다
  • 각 케이스는 한 줄에 개별로도, 한 줄에 여러개도 정의할 수 있습니다

enum 이름 {
	case 이름1
	case 이름2
	case 이름3, 이름4, 이름5
	// ...
}


열거형 사용

enum Weekday {
    case mon
    case tue
    case wed
    case thu, fri, sat, sun
}

// 열거형 타입과 케이스를 모두 사용하여도 됩니다
var day: Weekday = Weekday.mon

// 타입이 명확하다면 .케이스 처럼 표현해도 무방합니다
day = .tue

print(day) // tue

// switch의 비교값에 열거형 타입이 위치할 때
// 모든 열거형 케이스를 포함한다면
// default를 작성할 필요가 없습니다
switch day {
case .mon, .tue, .wed, .thu:
    print("평일입니다")
case Weekday.fri:
    print("불금 파티!!")
case .sat, .sun:
    print("신나는 주말!!")
}


원시값(Raw value)

C 언어의 enum처럼 정수값을 가질 수도 있습니다. rawValue를 사용하면 됩니다. 

case별로 각각 다른 값을 가져야합니다.

enum Fruit: Int {
    case apple = 0
    case grape = 1
    case peach
    
    // mango와 apple의 원시값이 같으므로 
    // mango 케이스의 원시값을 0으로 정의할 수 없습니다
//    case mango = 0
}

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.middle.rawValue == \(School.middle.rawValue)")
// School.middle.rawValue == 중등

// 열거형의 원시값 타입이 String일 때, 원시값이 지정되지 않았다면
// case의 이름을 원시값으로 사용합니다
print("School.university.rawValue == \(School.university.rawValue)")
// School.middle.rawValue == university


원시값을 통한 초기화

rawValue를 통해 초기화 할 수 있습니다. rawValuecase에 해당하지 않을 수 있으므로 rawValue를 통해 초기화 한 인스턴스는 옵셔널 타입입니다.

// rawValue를 통해 초기화 한 열거형 값은 옵셔널 타입이므로 Fruit 타입이 아닙니다
//let apple: Fruit = Fruit(rawValue: 0)
let apple: Fruit? = Fruit(rawValue: 0)

// if let 구문을 사용하면 rawValue에 해당하는 케이스를 곧바로 사용할 수 있습니다
if let orange: Fruit = Fruit(rawValue: 5) {
    print("rawValue 5에 해당하는 케이스는 \(orange)입니다")
} else {
    print("rawValue 5에 해당하는 케이스가 없습니다")
} // rawValue 5에 해당하는 케이스가 없습니다


메서드

스위프트의 열거형에는 메서드도 추가할 수 있습니다.

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()


관련문서

The Swift Programming Language - Enumerations





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

클로저 - Closure  (0) 2017.06.22
클래스, 구조체, 열거형 비교  (0) 2017.06.19
열거형 - Enumerations  (0) 2017.06.15
클래스  (0) 2017.06.12
구조체  (2) 2017.06.08
옵셔널  (0) 2017.06.05
Posted by yagom

클래스

Swift/기본문법 2017.06.12 11:00

클래스


소스코드


정의 문법

클래스는 참조 타입입니다. 타입이름은 대문자 카멜케이스를 사용하여 정의합니다.

class 이름 {
	/* 구현부 */
}


프로퍼티 및 메서드 구현

클래스의 타입 메서드는 두 종류가 있습니다. 상속 후 재정의가 가능한 class 타입메서드, 상속 후 재정의가 불가능한 static 타입메서드가 있습니다. 자세한 내용은 상속 부분에서 다시 다룹니다.

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 mutableReference: Sample = Sample()

mutableReference.mutableProperty = 200

// 불변 프로퍼티는 인스턴스 생성 후 수정할 수 없습니다
// 컴파일 오류 발생
//mutableReference.immutableProperty = 200


// 인스턴스 생성 - 참조정보 수정 불가
let immutableReference: Sample = Sample()

// 클래스의 인스턴스는 참조 타입이므로 let으로 선언되었더라도 인스턴스 프로퍼티의 값 변경이 가능합니다
immutableReference.mutableProperty = 200

// 다만 참조정보를 변경할 수는 없습니다
// 컴파일 오류 발생
//immutableReference = mutableReference

// 참조 타입이라도 불변 인스턴스는 
// 인스턴스 생성 후에 수정할 수 없습니다
// 컴파일 오류 발생
//immutableReference.immutableProperty = 200


// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method

// 인스턴스에서는 타입 프로퍼티나 타입 메서드를
// 사용할 수 없습니다
// 컴파일 오류 발생
//mutableReference.typeProperty = 400
//mutableReference.typeMethod()


학생 클래스 만들어보기

class Student {
	// 가변 프로퍼티
    var name: String = "unknown"

    // 키워드도 `로 묶어주면 이름으로 사용할 수 있습니다
    var `class`: String = "Swift"
    
    // 타입 메서드
    class func selfIntroduce() {
        print("학생타입입니다")
    }
    
    // 인스턴스 메서드
    // self는 인스턴스 자신을 지칭하며, 몇몇 경우를 제외하고 사용은 선택사항입니다
    func selfIntroduce() {
        print("저는 \(self.class)반 \(name)입니다")
    }
}

// 타입 메서드 사용
Student.selfIntroduce() // 학생타입입니다

// 인스턴스 생성
var yagom: Student = Student()
yagom.name = "yagom"
yagom.class = "스위프트"
yagom.selfIntroduce()   // 저는 스위프트반 yagom입니다

// 인스턴스 생성
let jina: Student = Student()
jina.name = "jina"
jina.selfIntroduce() // 저는 Swift반 jina입니다




관련문서

The Swift Programming Language - Classes and Structures





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

클래스, 구조체, 열거형 비교  (0) 2017.06.19
열거형 - Enumerations  (0) 2017.06.15
클래스  (0) 2017.06.12
구조체  (2) 2017.06.08
옵셔널  (0) 2017.06.05
반복문  (0) 2017.06.01
Posted by yagom

구조체

Swift/기본문법 2017.06.08 11:00

구조체


소스코드



정의 문법

스위프트 대부분의 타입은 구조체로 이루어져 있습니다.
구조체는 값 타입입니다.
타입이름은 대문자 카멜케이스를 사용하여 정의합니다.

struct 이름 {
	/* 구현부 */
}


프로퍼티 및 메서드 구현

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")
    }
}


구조체 사용

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

mutable.mutableProperty = 200

// 불변 프로퍼티는 인스턴스 생성 후 수정할 수 없습니다
// 컴파일 오류 발생
//mutable.immutableProperty = 200

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

// 불변 인스턴스는 아무리 가변 프로퍼티라도
// 인스턴스 생성 후에 수정할 수 없습니다
// 컴파일 오류 발생
//immutable.mutableProperty = 200
//immutable.immutableProperty = 200


// 타입 프로퍼티 및 메서드
Sample.typeProperty = 300
Sample.typeMethod() // type method

// 인스턴스에서는 타입 프로퍼티나 타입 메서드를
// 사용할 수 없습니다
// 컴파일 오류 발생
//mutable.typeProperty = 400
//mutable.typeMethod()


학생 구조체 만들어보기

struct Student {
	// 가변 프로퍼티
    var name: String = "unknown"

    // 키워드도 `로 묶어주면 이름으로 사용할 수 있습니다
    var `class`: String = "Swift"
    
    // 타입 메서드
    static func selfIntroduce() {
        print("학생타입입니다")
    }
    
    // 인스턴스 메서드
    // self는 인스턴스 자신을 지칭하며, 몇몇 경우를 제외하고 사용은 선택사항입니다
    func selfIntroduce() {
        print("저는 \(self.class)반 \(name)입니다")
    }
}

// 타입 메서드 사용
Student.selfIntroduce() // 학생타입입니다

// 가변 인스턴스 생성
var yagom: Student = Student()
yagom.name = "yagom"
yagom.class = "스위프트"
yagom.selfIntroduce()   // 저는 스위프트반 yagom입니다

// 불변 인스턴스 생성
let jina: Student = Student()

// 불변 인스턴스이므로 프로퍼티 값 변경 불가
// 컴파일 오류 발생
//jina.name = "jina"
jina.selfIntroduce() // 저는 Swift반 unknown입니다


관련문서

The Swift Programming Language - Classes and Structures





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

열거형 - Enumerations  (0) 2017.06.15
클래스  (0) 2017.06.12
구조체  (2) 2017.06.08
옵셔널  (0) 2017.06.05
반복문  (0) 2017.06.01
조건문  (0) 2017.05.29
Posted by yagom

옵셔널

Swift/기본문법 2017.06.05 11:00

옵셔널


소스코드

학습자료


옵셔널 값 추출


소스코드

학습자료




관련문서

The Swift Programming Language - The Basics





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 


저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

클래스  (0) 2017.06.12
구조체  (2) 2017.06.08
옵셔널  (0) 2017.06.05
반복문  (0) 2017.06.01
조건문  (0) 2017.05.29
함수  (2) 2017.05.25
Posted by yagom

반복문

Swift/기본문법 2017.06.01 11:00

반복문

  • for-in
  • while
  • repeat-while

소스코드


for-in 구문

기존 언어의 for-each 구문과 유사합니다. Dictionary의 경우 이터레이션 아이템으로 튜플이 들어옵니다. 튜플에 관해서는 Swift Language Guide의 Tuples 부분을 참고하면 되겠습니다.


for-in 구문의 기본 형태

for item in items {
    /* 실행 구문 */
}

for-in 구문의 사용

var integers = [1, 2, 3]
let people = ["yagom": 10, "eric": 15, "mike": 12]

for integer in integers {
    print(integer)
}

// Dictionary의 item은 key와 value로 구성된 튜플 타입입니다
for (name, age) in people {
    print("\(name): \(age)")
}

while 구문

while 구문의 기본 형태

while 조건 {
    /* 실행 구문 */
}


while 구문의 사용

while integers.count > 1 {
    integers.removeLast()
}


repeat-while 구문

기존 언어의 do-while 구문과 형태 및 동작이 유사합니다

repeat-while 구문의 기본 형태

repeat {
    /* 실행 구문 */
} while 조건


while 구문의 사용

repeat {
    integers.removeLast()
} while integers.count > 0


관련문서

The Swift Programming Language - Control Flow







by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

구조체  (2) 2017.06.08
옵셔널  (0) 2017.06.05
반복문  (0) 2017.06.01
조건문  (0) 2017.05.29
함수  (2) 2017.05.25
컬렉션 타입  (0) 2017.05.22
Posted by yagom

조건문

Swift/기본문법 2017.05.29 11:00

조건문

  • if-else
  • switch

소스코드


if-else 구문

if-else 구문의 기본 형태

if만 단독적으로 사용해도 되고, else if, else와 조합해서 사용 가능합니다. if 뒤의 조건 값에는 Bool 타입의 값만 위치해야 하며, 조건 값을 감싸는 소괄호는 선택사항입니다.

if 조건 {
    조건
} else if 조건 {
    /* 실행 구문 */
} else {
    /* 실행 구문 */
}


if-else의 사용

let someInteger = 100

if someInteger < 100 {
    print("100 미만")
} else if someInteger > 100 {
    print("100 초과")
} else {
    print("100")
} // 100

// 스위프트의 조건에는 항상 Bool 타입이 들어와야합니다
// someInteger는 Bool 타입이 아닌 Int 타입이기 때문에
// 컴파일 오류가 발생합니다
//if someInteger { }


switch 구문

스위프트의 switch 구문은 다른 언어에 비해 굉장히 강력한 힘을 발휘합니다. 기본적으로 사용하던 정수타입의 값만 비교하는 것이 아니라 대부분의 스위프트 기본 타입을 지원하며, 다양한 패턴과도 응용이 가능합니다. 스위프트의 다양한 패턴은 Swift Programming Language Reference의 패턴에서 확인할 수 있습니다.


  • 각각의 case 내부에는 실행가능한 코드가 반드시 위치해야 합니다
  • 매우 한정적인 값(ex. enum의 case 등)이 비교값이 아닌 한 default 구문은 반드시 작성해야 합니다
  • 명시적 break를 하지 않아도 자동으로 case마다 break 됩니다  
  • fallthrough 키워드를 사용하여 break를 무시할 수 있습니다
  • 쉼표(,)를 사용하여 하나의 case에 여러 패턴을 명시할 수 있습니다


switch 구문의 기본 형태

switch 비교값 {
case 패턴:
    /* 실행 구문 */
default:
    /* 실행 구문 */
}

switch 구문의 사용

// 범위 연산자를 활용하면 더욱 쉽고 유용합니다
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")
} // 100

// 정수 외의 대부분의 기본 타입을 사용할 수 있습니다
switch "yagom" {
case "jake":
    print("jake")
case "mina":
    print("mina")
case "yagom":
    print("yagom!!")
default:
    print("unknown")
} // yagom!!


기본 문법을 익힌 뒤 차후에 더 많은 switch 구문과 패턴의 활용에 대해 알아봅시다


관련문서

The Swift Programming Language - Control Flow





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

옵셔널  (0) 2017.06.05
반복문  (0) 2017.06.01
조건문  (0) 2017.05.29
함수  (2) 2017.05.25
컬렉션 타입  (0) 2017.05.22
데이터 타입  (0) 2017.05.18
Posted by yagom

함수

Swift/기본문법 2017.05.25 11:00

함수



소스코드


함수의 선언

함수선언의 기본형태

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

// 예)
// sum이라는 이름을 가지고 
// a와 b라는 Int 타입의 매개변수를 가지며 
// Int 타입의 값을 반환하는 함수
func sum(a: Int, b: Int) -> Int {
    return a + b
}


반환 값이 없는 함수

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

// 예)
func printMyName(name: String) -> Void {
    print(name)
}

// 반환 값이 없는 경우, 반환 타입(Void)을 생략해 줄 수 있습니다
func printYourName(name: String) {
    print(name)
}

매개변수가 없는 함수

func 함수이름() -> 반환타입 {
    /* 함수 구현부 */
    return 반환값
}

// 예)
func maximumIntegerValue() -> Int {
    return Int.max
}


매개변수와 반환값이 없는 함수

func 함수이름() -> Void {
    /* 함수 구현부 */
    return
}

// 함수 구현이 짧은 경우
// 가독성을 해치지 않는 범위에서
// 줄바꿈을 하지 않고 한 줄에 표현해도 무관합니다
func hello() -> Void { print("hello") }


// 반환 값이 없는 경우, 반환 타입(Void)을 생략해 줄 수 있습니다
func 함수이름() {
    /* 함수 구현부 */
    return
}

func bye() { print("bye") }


함수의 호출

sum(a: 3, b: 5) // 8

printMyName(name: "yagom") // yagom

printYourName(name: "hana") // hana

maximumIntegerValue() // Int의 최댓값

hello() // hello

bye() // bye




매개변수 기본 값

매개변수에 기본적으로 전달될 값을 미리 지정할 수 있습니다.

기본값을 갖는 매개변수는 매개변수 목록 중에 뒤쪽에 위치하는 것이 좋습니다.

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

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

// 매개변수 기본값을 가지는 매개변수는 호출시 생략할 수 있습니다
greeting(friend: "hana") // Hello hana! I'm yagom
greeting(friend: "john", me: "eric") // Hello john! I'm 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: "yagom") // Hello hana! I'm yagom

가변 매개변수

전달 받을 값의 개수를 알기 어려울 때 사용할 수 있습니다. 가변 매개변수는 함수당 하나만 가질 수 있습니다.

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

func sayHelloToFriends(me: String, friends: String...) -> String {
    return "Hello \(friends)! I'm \(me)!"
}
print(sayHelloToFriends(me: "yagom", friends: "hana", "eric", "wing"))
// Hello ["hana", "eric", "wing"]! I'm yagom!

print(sayHelloToFriends(me: "yagom"))
// Hello []! I'm yagom!


위에 설명한 함수의 다양한 모양은 모두 섞어서 사용 가능합니다.


데이터 타입으로서의 함수

스위프트는 함수형 프로그래밍 패러다임을 포함하는 다중 패러다임 언어이므로 스위프트의 함수는 일급객체입니다. 그래서 함수를 변수, 상수 등에 할당이 가능하고 매개변수를 통해 전달할 수도 있습니다.


함수의 타입표현

반환타입을 생략할 수 없습니다.

 (매개변수1타입, 매개변수2타입 ...) -> 반환타입


함수타입 사용

var someFunction: (String, String) -> Void = greeting(to:from:)
someFunction("eric", "yagom") // Hello eric! I'm yagom

someFunction = greeting(friend:me:)
someFunction("eric", "yagom") // Hello eric! I'm yagom


// 타입이 다른 함수는 할당할 수 없습니다 - 컴파일 오류 발생
//someFunction = sayHelloToFriends(me: friends:)


func runAnother(function: (String, String) -> Void) {
    function("jenny", "mike")
}

// Hello jenny! I'm mike
runAnother(function: greeting(friend:me:))

// Hello jenny! I'm mike
runAnother(function: someFunction)


참고 : 스위프트의 전반적인 문법에서 띄어쓰기는 신경써야할 때가 많습니다


관련문서

The Swift Programming Language - Functions





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

반복문  (0) 2017.06.01
조건문  (0) 2017.05.29
함수  (2) 2017.05.25
컬렉션 타입  (0) 2017.05.22
데이터 타입  (0) 2017.05.18
상수와 변수  (0) 2017.05.15
Posted by yagom

Perfect 라우팅

Swift/Perfect 2017.05.24 11:00


Perfect 라우팅

1. Routes 
2. HTTP Method, URI 지정 
3. 
Web Root 디렉터리 설정
4. JSON 응답



지난 번에는 Perfect 서버 애플리케이션을 처음 구동해 보았습니다. 

2017/05/17 - [Swift/Perfect] - Perfect 시작하기

이번에는 라우팅 하는 방법에 대해서 알아보려고 합니다 :)


* 참고 *

2017년 5월 현재 Swift 3 / Perfect 최신버전 2.0.x 환경에서 진행함을 알려드립니다.

* Swift 최신 버전 확인

* Perfect 최신 버전 확인


Routes

HTTP 요청에 의해 여러 메서드, URI에 따른 동작을 처리하려면 Routes라는 구조체를 사용합니다.

지난 번 main.swift파일에 서버 포트를 지정해 주었던 server.serverPort = 8080 코드 아래에 routes 변수에 Routes 인스턴스를 하나 할당해줍니다.

var routes = Routes()


HTTP Method, URI 지정

Routes인스턴스를 생성할 때, 원하는 HTTP Method와 URI를 지정할 수 있으며, 요청이 온 경우 handler 클로저를 통해 요청을 처리합니다. 이후, addRoutes() 메서드를 통해 서버에 라우트 정보를 추가해줍니다.

routes.add(method: .get, uri: "/blog", handler: { request, response in
	response.setBody(string: "Blog handler was called")
	response.completed()
})
server.addRoutes(routes)

이렇게 지정하고 실행하면 8080포트blog 경로를 지정하여 요청하면 "Handler was called" 문자열이 돌아오게 될 것입니다.


handler 클로저의 내부에서 request 인스턴스에서 param(name:) 등 많은 메서드를 통해 HTTP 요청 정보를 읽어낼 수도 있습니다. 
routes.add(method: .get, uri: "/blog", handler: { request, response in
    
    let pageToResponse: Int
    
    if let page = request.param(name: "page"), let pageNumber = Int(page) {
        pageToResponse = pageNumber
    } else {
        pageToResponse = 0
    }
    
    response.setBody(string: "Handler was called page number:  \(pageToResponse)")
    response.completed()
})


Web Root 디렉터리 설정

연습을 해봤으니 실질적으로 HTML 응답과 JSON 응답을 해보려합니다. 먼저 웹의 데이터를 모아 둘 WebDocuments 폴더를 사용자 디렉터리의 하위폴더로 생성해줍니다.
> mkdir ~/WebDocuments

WebDocuments 폴더를 생성하면 블로그 메인페이지로 사용할 index.html 파일을 넣어줍니다. (파일을 이동하거나 복사하는 방법에 대해서는 따로 설명하지 않겠습니다... 'ㅁ')

이제 WebDocuments 폴더를 웹서버의 Root 폴더로 지정합니다. 

사용자 디렉터리의 주소를 가져오기 위해 Foundation 프레임워크를 이용했습니다. main.swift 파일 상단에 import Foundation을 통해 Foundation 프레임워크를 불러와주세요.


그리고 아래 코드를  server.addRoutes(routes) 라인 아래에 작성해주세요.

if let homeURL = URL(string: NSHomeDirectory()) {
    var rootPath = homeURL.appendingPathComponent("WebDocuments", isDirectory: true).absoluteString
    
    try Dir(rootPath).create()
    
    server.documentRoot = rootPath
}

이렇게 설정하고 하위 경로 없이 접속하면 WebDocuments 폴더의 index.html 파일을 자동으로 메인 페이지로 볼 수 있습니다.




JSON 응답

이번에는 단순히 HTML 웹페이지가 아닌 JSON 응답을 줘보려고 합니다. 먼저 응답을 제어하기 위한 handler 클로저를 따로 함수로 구현하려 합니다. 가독성 면에서도 그게 좋을 것 같아요.


서버 인스턴스 생성코드 let server = HTTPServer() 전에 handler 함수를 구현해봅니다. 

func jsonHandler(request: HTTPRequest, response: HTTPResponse) {
    
    var jsonDictionary: [String: Any] = [:]
    jsonDictionary["name"] = "yagom"
    jsonDictionary["languages"] = ["Swift", "Objective-C"]
    jsonDictionary["age"] = 10
    jsonDictionary["alive"] = true
    
    do {
        response.setHeader(.contentType, value: "application/json; charset=utf-8")
        try response.setBody(json: jsonDictionary)
        response.completed()
    } catch {
        response.completed(status: .internalServerError)
    }
}


그리고 경로를 지정해주기 위해서 server.addRoutes(routes) 코드 위에 아래 코드를 작성합니다.

routes.add(method: .get, uri: "/json", handler: jsonHandler(request:response:))



다시 실행해서 /json 경로로 GET 요청을 하면 아래와같이 JSON 문자열을 응답한 것을 확인할 수 있습니다.




정리

지금까지 작성한 main.swift 파일 코드

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer
import Foundation

func jsonHandler(request: HTTPRequest, response: HTTPResponse) {
    
    var jsonDictionary: [String: Any] = [:]
    jsonDictionary["name"] = "yagom"
    jsonDictionary["languages"] = ["Swift", "Objective-C"]
    jsonDictionary["age"] = 10
    jsonDictionary["alive"] = true
    
    do {
        response.setHeader(.contentType, value: "application/json; charset=utf-8")
        try response.setBody(json: jsonDictionary)
        response.completed()
    } catch {
        response.completed(status: .internalServerError)
    }
}

let server = HTTPServer()

server.serverPort = 8080
var routes = Routes()
routes.add(method: .get, uri: "/blog", handler: { request, response in
    
    let pageToResponse: Int
    
    if let page = request.param(name: "page"), let pageNumber = Int(page) {
        pageToResponse = pageNumber
    } else {
        pageToResponse = 0
    }
    
    response.setBody(string: "Handler was called page number:  \(pageToResponse)")
    response.completed()
})

routes.add(method: .get, uri: "/json", handler: jsonHandler(request:response:))

server.addRoutes(routes)


if let homeURL = URL(string: NSHomeDirectory()) {
    var rootPath = homeURL.appendingPathComponent("WebDocuments", isDirectory: true).absoluteString
    
    try Dir(rootPath).create()
    
    server.documentRoot = rootPath
}

do {
    try server.start()
} catch PerfectError.networkError(let error, let message) {
    Log.error(message: "Error: \(error), \(message)")
}


---------------------------------

물론 이 외에도 정말 많은 응용 방법과 라우트 하는 방법, 더 다양한 기능들이 있지만 모두 설명할 수 없으므로 정말 간단하게 감을 잡을 수 있는 정도만 해봤습니다. 차후에 간단한 프로젝트를 통해서 필요한 부분은 조금씩 더 설명하려고 합니다.


이정도 만들어 봤으면 대강 핑퐁은 해봤으니, 작은 미니 프로젝트 한 번 시작해 봐도 되지 않을까요?

다음 번에는 미니 프로젝트를 시작해볼 예정입니다~


다음에 또 만나요! :D


참고문서

* PerfectDocs - Routing

* PerfectDocs - HTTPResponse

* PerfectDocs - JSON Converter



by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 




저작자 표시 비영리 변경 금지
신고

'Swift > Perfect' 카테고리의 다른 글

사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (2) 2017.06.27
Perfect 라우팅  (0) 2017.05.24
Perfect 시작하기  (0) 2017.05.17
우분투(Ubuntu)에 스위프트 설치하기  (0) 2017.04.03
Posted by yagom


티스토리 툴바