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 만들기 [4]  (0) 2017.08.30
사진 게시판 API 만들기 [3]  (0) 2017.08.09
사진 게시판 API 만들기 [2]  (0) 2017.07.11
사진 게시판 API 만들기 [1]  (6) 2017.06.27
Perfect 라우팅  (0) 2017.05.24
Perfect 시작하기  (0) 2017.05.17
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
옵셔널 체이닝  (2) 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
옵셔널 체이닝  (2) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
Posted by yagom

인스턴스의 생성과 소멸

인스턴스를 생성하는 이니셜라이저와 클래스의 인스턴스가 소멸될 때 호출되는 디이니셜라이저 그리고 그와 관련된 것들에 대해 알아봅니다.

  • 프로퍼티 초기값
  • 이니셜라이저 init
  • 디이니셜라이저 deinit


소스코드



프로퍼티 기본값

스위프트의 모든 인스턴스는 초기화와 동시에 모든 프로퍼티에 유효한 값이 할당되어 있어야 합니다.
프로퍼티에 미리 기본값을 할당해두면 인스턴스가 생성됨과 동시에 초기값을 지니게 됩니다.

class PersonA {
    // 모든 저장 프로퍼티에 기본값 할당
    var name: String = "unknown"
    var age: Int = 0
    var nickName: String = "nick"
}

// 인스턴스 생성
let jason: PersonA = PersonA()

// 기본값이 인스턴스가 지녀야 할 값과 맞지 않다면
// 생성된 인스턴스의 프로퍼티에 각각 값 할당
jason.name = "jason"
jason.age = 30
jason.nickName = "j"


이니셜라이저

프로퍼티 기본값을 지정하기 어려운 경우에는 이니셜라이저 init을 통해 인스턴스가 가져야 할 초기값을 전달할 수 있습니다.

class PersonB {
    var name: String
    var age: Int
    var nickName: String
    
    // 이니셜라이저
    init(name: String, age: Int, nickName: String) {
        self.name = name
        self.age = age
        self.nickName = nickName
    }
}

let hana: PersonB = PersonB(name: "hana", age: 20, nickName: "하나")


> 프로퍼티의 초기값이 꼭 필요 없을 때 

옵셔널을 사용!

class PersonC {
    var name: String
    var age: Int
    var nickName: String?
    
    init(name: String, age: Int, nickName: String) {
        self.name = name
        self.age = age
        self.nickName = nickName
    }
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let jenny: PersonC = PersonC(name: "jenny", age: 10)
let mike: PersonC = PersonC(name: "mike", age: 15, nickName: "m")


암시적 추출 옵셔널은 인스턴스 사용에 꼭 필요하지만 초기값을 할당하지 않고자 할 때 사용

class Puppy {
    var name: String
    var owner: PersonC!
    
    init(name: String) {
        self.name = name
    }
    
    func goOut() {
        print("\(name)가 주인 \(owner.name)와 산책을 합니다")
    }
}

let happy: Puppy = Puppy(name: "happy")
// 강아지는 주인없이 산책하면 안돼요!
//happy.goOut() // 주인이 없는 상태라 오류 발생
happy.owner = jenny
happy.goOut()
// happy가 주인 jenny와 산책을 합니다


실패가능한 이니셜라이저

이니셜라이저 매개변수로 전달되는 초기값이 잘못된 경우 인스턴스 생성에 실패할 수 있습니다. 인스턴스 생성에 실패하면 nil을 반환합니다. 그래서 실패가능한 이니셜라이저의 반환타입은 옵셔널 타입입니다. init?을 사용합니다.

class PersonD {
    var name: String
    var age: Int
    var nickName: String?
    
    init?(name: String, age: Int) {
        if (0...120).contains(age) == false {
            return nil
        }
        
        if name.characters.count == 0 {
            return nil
        }
        
        self.name = name
        self.age = age
    }
}

//let john: PersonD = PersonD(name: "john", age: 23)
let john: PersonD? = PersonD(name: "john", age: 23)
let joker: PersonD? = PersonD(name: "joker", age: 123)
let batman: PersonD? = PersonD(name: "", age: 10)

print(joker) // nil
print(batman) // nil


디이니셜라이저

deinit은 클래스의 인스턴스가 메모리에서 해제되는 시점에 호출됩니다. 인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있습니다. 자동으로 호출되므로 직접 호출할 수 없습니다. 인스턴스가 메모리에서 해제되는 시점은 ARC(Automatic Reference Counting) 의 규칙에 따라 결정됩니다. ARC에 대해 더 자세한 사항은 ARC 문서를 참고하세요.

디이니셜라이저는 클래스 타입에만 구현할 수 있습니다.

class PersonE {
    var name: String
    var pet: Puppy?
    var child: PersonC
    
    init(name: String, child: PersonC) {
        self.name = name
        self.child = child
    }
    
    // 인스턴스가 메모리에서 해제되는 시점에 자동 호출
    deinit {
        if let petName = pet?.name {
            print("\(name)가 \(child.name)에게 \(petName)를 인도합니다")
            self.pet?.owner = child
        }
    }
}

var donald: PersonE? = PersonE(name: "donald", child: jenny)
donald?.pet = happy
donald = nil // donald 인스턴스가 더이상 필요없으므로 메모리에서 해제됩니다
// donald가 jenny에게 happy를 인도합니다



관련문서

* The Swift Programming Language - Initialization

* The Swift Programming Language - Deinitialization

* The Swift Programming Language - Automatic Reference Counting





by yagom

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

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


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

RSS Feed 받기   


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


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

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

타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (2) 2017.07.06
인스턴스의 생성과 소멸  (0) 2017.07.03
상속  (0) 2017.06.29
프로퍼티 - Property  (2) 2017.06.26
클로저 - Closure  (0) 2017.06.22
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 > 기본문법' 카테고리의 다른 글

옵셔널 체이닝  (2) 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


티스토리 툴바