'Swift/응용, 생각거리'에 해당되는 글 3건

  1. 2017.04.10 Swift에서 Objective-C의 상수 대체제에 관하여
  2. 2017.03.15 Swift - 프로토콜 지향 프로그래밍
  3. 2015.06.11 Swift에 대한 오해와 진실 (10)


오늘의 주제

Objective-C의 상수와 스위프트의 상수


안녕하세요 야곰입니다.

Objective-C를 쓰다가 스위프트로 넘어왔을 때 고민되었던 부분 중의 하나인 스위프트의 상수에 관해 이야기해 보려 합니다.


혼자 프로젝트를 진행하다 보면 상수의 필요성을 많이 느낄 수 없을지 모르지만, 누군가 협업을 하게 된다면, 혹은 미래 또는 과거의 나와 협업(?)을 하게 된다면 상수의 필요성을 많이 느끼게 됩니다. 바꾸면 안 되는 데이터를 실수로 바꿔서 난감했던 경험이 없나요? 혹시 그런 적이 있다면 상수의 필요성에 대해서는 많이 공감하실 것으로 생각됩니다.


* 오해가 생길 소지의 표현들이 있어서 처음 발행 이후 조금 수정하였습니다. 좋은 의견 주신 과니님 고맙습니다 :)


사라진 const


사실 저는 Objective-C에서 특별한 경우가 아니면 const를 많이 사용하지 않았습니다(반성).


그런데 스위프트에서는 전역이든 지역이든 데이터를 저장할 공간(변수 또는 상수)에는 var 또는 let을 사용하여 변수인지 상수인지 명확히 명시해 주어야 하므로 상수에 대한 고민을 많이 하게 되었습니다.


사실 지역상수가 필요한 경우에 const 키워드를 쓰는 귀찮음 때문에 상수를 많이 사용하지 않았는데 스위프트에서는 var 또는 let을 선택해야 하는 시간이 조금 걸렸지만, 지금은 큰 시간 들일 필요 없이 자연스럽게 상수를 많이 사용하고 있습니다. 또, 혹시라도 상수로 사용해야 하는 경우임에도 변수로 선언하고 사용한다면 Xcode에서 변수 대신 상수를 사용하는 게 어떻냐는 경고를 보여줍니다. 




[Xcode의 경고]


지역상수


지역상수를 사용하는 경우에는 사실 Objective-C와 크게 다를 바 없이 사용할 수 있습니다. 상수의 이름을 지어주는 명명법도 크게 다를 바 없습니다. 

상수로 사용할 변수 앞에 const를 붙여 쓴 것처럼 let을 사용하여 상수를 선언하여 사용하면 됩니다.



// Objective-C
const NSInteger someConstant = 100;


// Swift
let someConstant: Int = 100



전역상수


전역상수를 사용할 때 조금 더 생각해보아야 할 것들이 있습니다. 저는 처음 스위프트를 사용할 때 별생각 없이 Objective-C에서 사용하던 것과 같은 명명법으로 전역상수를 사용했습니다.


// Objective-C NSInteger const  YGSomeGlobalConstant = 100;


// Swift
let YGSomeGlobalConstant: Int = 100


그런데 Objective-C에서 'YG'와 같이 접두어(prefix)를 붙이는 것은 Objective-C에는 네임스페이스(name space)가 없기 때문인데, 스위프트에는 이 단점을 극복했다는 것에 대한 생각이 문득 스쳤습니다.


Objective-C에는 네임스페이스가 없어서 전역변수를 선언하고 사용할 때 꽤 골치가 아팠습니다. 귀찮기도 아주 귀찮았죠. 이름도 매우 길어지기만 했습니다. 열거형(enum)은 정수밖에 지원하지 않기 때문에 다른 타입의 값들은 관련된 상수끼리 묶어 쓰기도 어렵기도 했지요.


// Objective-C의 상수들... // 네임스페이스가 없기 때문에 // 연관된 상수들을 접두어를 사용하여 표현합니다 // 요일 상수들 NSString *const YGWeekMonday = @"MON"; NSString *const YGWeekTuesday = @"TUE"; NSString *const YGWeekWednesday = @"WED"; NSString *const YGWeekThursday = @"THU"; NSString *const YGWeekFriday = @"FRI"; NSString *const YGWeekSaturday = @"SAT"; NSString *const YGWeekSunday = @"SUN"; // 네트워킹 관련 상수들 NSTimeInterval const YGNetworkingTimeoutInterval = 10.0f; NSUInteger const YGNetworkingMaxRetryCount = 3; NSString *const YGNetworkingBaseURLString = @"https://abc.com";


정말 보기만 해도 정신이 없습니다. 또, 실수로 복사 붙여넣기를 하다가 중복된 값을 넣었다면 컴파일 오류가 발생하지 않기 때문에 실수를 찾아내기도 매우 어려워집니다.


스위프트에서는 이런 전역 상수들을 조금 더 멋진 방법으로 표현해 볼 수 있습니다. 타입 내부에 다른 타입을 정의하는 방법으로 네임스페이스를 사용할 수 있습니다. 더군다나 네임스페이스 덕분에 접두어는 더 이상 스위프트에서 사용하지 않습니다.


// Week라는 구조체 타입 내부에
// 여러 타입 상수를 선언
struct Week {
    static let mon: String = "MON"
    static let tue: String = "TUE"
    static let wed: String = "WED"
    static let thu: String = "THU"
    static let fri: String = "FRI"
    static let sat: String = "SAT"
    static let sun: String = "SUN"
}

// 실제 사용시 Week.mon // "MON" Week.sat // "SAT"


처음에 이렇게 선언해 보았습니다. 그런데 만약 복사 붙여넣기를 하다가 실수로 중복된 값을 넣는다면...? 가령 "SUN"을 넣어야 하는데 "SAT"를 넣어버렸다면? 이때는 중복 값이 들어있는지 확인할 수 없습니다. 물론 의도적으로 다른 이름의 상수에 같은 값을 넣을 수도 있지만 그렇지 않은 경우에는 낭패입니다.


그래서 이렇게 개선해 봅니다.


// 열거형의 연관 값(associated value)을 사용하여
// 상수처럼 사용
enum Week: String {
    case mon = "MON"
    case tue = "TUE"
    case wed = "WED"
    case thu = "THU"
    case fri = "FRI"
    case sat = "SAT"
    case sun = "SUN"
}

// 실제 사용시 Week.mon.rawValue // "MON" Week.sat.rawValue // "SAT"



이렇게 사용하면 열거형 내부의 연관 값이 중복되는 경우에 컴파일오류가 발생하게 됩니다. 그래서 미리 실수를 발견하기도 좋습니다.


또, 위의 Objective-C로 선언했던 네트워킹 관련 상수를 스위프트에서 선언한다면 이렇게 바꿔볼 수 있을 것 같습니다.


// Objective-C NSTimeInterval const YGNetworkingTimeoutInterval = 10.0f; NSUInteger const YGNetworkingMaxRetryCount = 3; NSString *const YGNetworkingBaseURLString = @"https://abc.com";
// 사용 YGNetworkingTimeoutInterval // 10.0


// Swift
struct Networking {
    static let timeoutInterval: TimeInterval = 10.0
    static let maxRetryCount: Int = 3
    static let baseURL: URL? = URL(string: "https://abc.com")
}

// 사용

Networking.timeoutInterval // 10.0


이처럼 꼭 열거형의 연관 값을 사용하지 않고 용도에 맞게 구조체의 타입 상수(static let)로 사용해도 좋습니다. 


상수뿐만 아니라 전역변수, 전역함수(메서드) 등에도 충분히 응용할 수 있습니다.


또한 구조체 내부에 다른 타입(구조체, 클래스, 열거형) 등등 몇 단계를 걸쳐 내부 타입을 정의할 수 있기 때문 연관된 값을 타입의 타입의 타입까지 여러 번에 걸쳐 정의도 가능합니다.



struct CalendarItem {
    typealias Year = Int
    typealias Day = Int
    
    enum Week: String {
        case mon = "MON", tue = "TUE" //...
    }
    
    enum Month: Int {
        case jan = 1, feb, mar //...
    }
    
    static let startYear: Int = 1970
    static let startMonth: CalendarItem.Month = .jan
    
    struct Date {
        var day: Day = 1
        var weekDay: CalendarItem.Week = .mon
        var month: CalendarItem.Month = .jan
        var year: Year = CalendarItem.startYear
    }
}

var userBirthday: CalendarItem.Date userBirthday = CalendarItem.Date.init(day: 10, weekDay: .tue, month: .mar, year: 2017)



마치며


같은 Cocoa 플랫폼 위에서 코드를 작성하는데도 불구하고 역시나 언어의 특성을 살려 새로이 구조를 설계하기는 쉽지 않습니다 하핳


무언가 두서없이 써내려간 느낌입니다만, 잘 이해가 가지 않거나 궁금한 점이 있다면 댓글 남겨주세요 :D





by yagom

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

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


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

RSS Feed 받기   


Posted by yagom


오늘의 주제

1. 프로토콜 지향 프로그래밍



안녕하세요, 야곰입니다. 


지난 포스팅에서는 스위프트의 프로토콜과 익스텐션에 대해 알아봤습니다.


2017/01/23 - [Swift] - Swift란 어떤 언어인가?

2017/01/25 - [Swift] - Swift 기초문법 - 변수, 상수, 기초 데이터 타입

2017/02/06 - [Swift] - Swift - 함수, 콜렉션 타입

2017/02/28 - [Swift] - Swift - 구조체 클래스

2017/03/07 - [Swift] - Swift - 프로토콜, 익스텐션

이번에는 스위프트와 함께 대두된 프로토콜 지향 프로그래밍 디자인 패턴에 대해 알아보겠습니다 :)



프로토콜 지향 프로그래밍

애플은 2015년 9월, WWDC에서 스위프트 버전 2.0을 발표하면서 스위프트는 프로토콜 지향 언어(Protocol-Oriented Language)라고 발표했습니다. 프로토콜 지향 언어는 도대체 무슨 뜻일까요? 스 위프트의 표준 라이브러리에서 타입과 관련된 것을 살펴보면 대부분이 구조체로 구현되어 있습니다. 객체지향 프로그래밍 패러다임에 기반을 둔 언어는 대부분 클래스의 상속을 사용해 타입에 공통된 기능을 구현합니다. 그런데 스위프트는 클래스로 구현된 타입은 별로없고, 대부분 구조체로 기본 타입이 구현되어 있습니다. 상속도 되지 않는 구조체로 어떻게 그렇게 다양한 공통 기능을 가질 수 있는 걸까요? 해답은 프로토콜과 익스텐션에 있습니다.

프로토콜 초기구현

지난 포스팅에서 프로토콜(Protocol)특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진이라고 말씀드렸습니다. 프로토콜을 채택(Adopted)한 타입은 프로토콜이 요구하는 기능을 구현하여 프로토콜을 준수해야(Conform)합니다. 
익스텐션은 기존 타입의 기능을 확장하며, 프로토콜은 프로토콜을 채택한 타입이 원하는 기능을 강제로 구현한다는 점을 우리는 알고 있습니다. 그런데 특정 프로토콜을 정의하고 여러 타입에서 이 프로토콜을 준수하게 만들어 타입마다 똑같은 메서드, 똑같은 프로퍼티, 똑같은 서브스크립트 등을 구현해야 한다면...? 얼마나 많은 코드를 중복 사용해야 하며 또 유지보수는 얼마나 힘들어질지 생각만 해도 머리가 아플 겁니다. 이때 필요한 게 바로 익스텐션과 프로토콜의 결합입니다. 
프로토콜을 채택한 타입의 정의부에 프로토콜의 요구사항을 구현하지 않더라도 프로토콜의 익스텐션에 미리 프로토콜의 요구사항을 구현해 둘 수 있습니다. 이를 프로토콜 초기구현이라고 합니다. 

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

struct Person: Talkable {
    var topic: String
    var name: String
    
    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
}

여기 지난 포스팅에서 예제로 보았던 Talkable 프로토콜이 있습니다. 이 프로토콜은 Person이라는 구조체 타입에만 채택이 되었으므로 여러 프로퍼티와 메서드를 구현하더라도 Person에만 구현하면 되므로 큰 문제가 없었습니다. 그런데, Talkable이라는 프로토콜을 Person 뿐만 아니라 다른 타입에서도 채택하고 싶다면? 아마도 그 타입에서도 Talkable 프로토콜이 요구하는 사항을 모두 구현해 주어야 할 것입니다.

protocol Talkable { var topic: String { get set } func talk(to: Self) } struct Person: Talkable { var topic: String var name: String func talk(to: Person) { print("\(topic)에 대해 \(to.name)에게 이야기합니다") } } struct Monkey: Talkable { var topic: String func talk(to: Monkey) { print("우끼끼 꺄꺄 \(topic)") } }


그런데 프로토콜이 요구하는 사항을 미리 모두 한꺼번에 구현해 둘 수 있다면 중복된 코드를 피할 수 있을 것입니다.

protocol Talkable { var topic: String { get set } func talk(to: Self) } // 익스텐션을 사용한 프로토콜 초기 구현 extension Talkable { func talk(to: Self) { print("\(to)! \(topic)") } } struct Person: Talkable { var topic: String var name: String } struct Monkey: Talkable { var topic: String } let yagom = Person(topic: "Swift", name: "yagom") let hana = Person(topic: "Internet", name: "hana") yagom.talk(to: hana) hana.talk(to: yagom)


위의 코드에서는 Person과 Monkey에 Talkable의 요구사항인 talk(to:) 메서드를 구현하지 않았음에도 전혀 오류가 발생하지 않습니다.
이렇게 하나의 프로토콜을 만들어두고, 초기 구현을 해둔다면 여러 타입에서 해당 기능을 사용하고 싶을 때 프로토콜을 채택하기만 하면 됩니다.
만약에 프로토콜 초기 구현과 다른 동작을 해야한다면, 그저 그 타입에 프로토콜의 요구사항을 재정의해주면 됩니다.

struct Monkey: Talkable { var topic: String func talk(to: Monkey) { print("\(to)! 우끼기기기끼기기") } } let sunny = Monkey(topic: "바나나") let jack = Monkey(topic: "나무") sunny.talk(to: jack)


프로토콜 초기 구현을 잘 해둔다면 여러 프로토콜을 그저 채택만 하기만하면 그 타입에 기능이 추가되는 것이죠.

protocol Flyable { func fly() } extension Flyable { func fly() { print("푸드득 푸드득") } } protocol Runable { func run() } extension Runable { func run() { print("후다닥 후다닥") } } protocol Swimable { func swim() } extension Swimable { func swim() { print("어푸 어푸") } } protocol Talkable { func talk() } extension Talkable { func talk() { print("재잘재잘 쪼잘쪼잘") } } struct Bird: Flyable, Talkable { } let bird = Bird() bird.fly() bird.talk() struct Person: Runable, Swimable, Talkable { } let person = Person() person.run() person.talk() person.swim()


위 코드처럼 프로토콜 초기 구현을 잘 해두면 이렇게 프로토콜 채택만으로도 그 기능을 사용할 수 있게 됩니다. 프로토콜 초기 구현이 프로토콜 지향 프로그래밍의 핵심이라고 볼 수 있습니다.

프로토콜 지향 프로그래밍을 추구하는 이유

그렇다면 프로토콜 지향 프로그래밍을 하는 이유는 무엇일까요?

  • 구조체, 클래스, 열거형 등 구조화된 타입 중에 상속은 클래스 타입에서만 가능합니다.
    • 클래스는 참조 타입이므로 참조 추적에 비용이 많이 발생합니다. 비교적 비용이 적은 값 타입을 활용하고 싶어도, 상속을 할 수 없으므로 때마다 기능을 다시 구현해 주어야 했지만, 프로토콜 지향 프로그래밍은 그 한계를 없앴습니다.
  • 기능의 모듈화가 더욱 명확해 집니다.
    • 클래스가 상속을 할 수 있도록 설계되어 있다고 하더라도 다중상속을 지원하는 언어는 많지 않습니다. 다중상속을 지원하지 않는다는 뜻은 하나의 상속체계에서 다른 상속체계에 속해있는 기능을 끌어다 쓸 수 없다는 뜻입니다. 그런데 프로토콜 지향 프로그래밍은 기능을 프로토콜이라는 단위로 묶어 표현하고 초기 구현을 해 둘 수 있으니 상속이라는 한계점을 탈피할 수 있습니다.

어떠신가요? 프로토콜 지향 프로그래밍! 정말 매력적이지 않나요?

익스텐션을 통한 각 프로토콜의 초기구현은 구현코드를 볼 수 없기 때문에 어떻게 구현되었는지 는 확실히 볼 수 없지만 Array의 정의만 보더라도 제네릭, 프로토콜을 다양하게 사용한 것을 볼 수 있습니다. 아마도 각 타입별로 공유하는 초기구현은 익스텐션으로 구현되어 있을 것입니다. 

스위프트의 주요 기능을 하나하나 알아갈수록 스위프트 표준 라이브러리의 코드가 눈에 잘 들어오실거예요. 하나의 기능을 알아갈 때마다 스위프트 표준 라이브러리를 살펴보면서 어떤 기능을 통해 구현 되었는지, 어떻게 연관이 되는지 읽어보고, 해석해보고, 상상해보는 것도 언어를 이해하는 데 도움이 됩니다. 

이번 포스팅에서는 프로토콜 지향 프로그래밍(Protocol Oriented Programming, POP) 대해 알아보았습니다. 
앞으로 또 무슨 주제를 써 나갈까요? 
그 동안 많은 도움이 되었길 빕니다!

고맙습니다 :D




by yagom

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

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

twitter : http://www.twitter.com/yagomSoft ( @yagomsoft )

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

RSS Feed 받기   

Posted by yagom



Swift에 대한 생각과 생각

안녕하세요! 야곰입니다.

오랜만에 포스팅으로 찾아뵙는군요. 이제껏 Swift를 포스팅 하지 않았던 이유(Swift 뿐만 아니라)는 개인적인 여유가 없었던 이유도 있지만, 언어의 버전이 올라감에 따라 문법이 너무 많이 바뀌어와서 조금 조심스러운 점도 없지 않았나 싶어요.


제목이 왜 이러나 궁금하실텐데요, Swift를 직접 사용해보지 않고 겉표면을 보고 이런저런 생각을 말씀해 주시거나, 질문은 하는 분들이 대부분 비슷한 패턴으로 비슷한 오해(?) 또는 겉모습만 보고 조금 편향적인 판단을 하고 있지 않나 싶어 글을 쓰게 되었습니다.


주변에서 많이 받는 질문이나 생각들을 제 나름의 짧은 생각과 곁들어 정리해 보았습니다.


이 글을 Swift 2.0이 공개된 바로 직후(2015년 6월 10일)에 쓰여졌으니 향후 오랜시간이 지나면 그 상황에 맞지 않는 의견일 수 있음을 미리 밝힙니다.


많이 듣는 질문 or 의견

1. Swift는 스크립트 언어이다

아니요. 애플의 Swift는 함수형 언어이자 컴파일 언어입니다. (거의)실시간적으로 결과를 보여주는 플레이그라운드라는 툴 때문에 이런 오해가 많이 생긴 것 같습니다. 플레이그라운드도 실시간으로 정보를 보여주는 것이 아니라, 몇 초 단위로 컴파일하여 결과를 보여주는 툴일 뿐입니다. 즉, 정확히 말하자면 스크립트 언어가 절대 아닙니다. 문법적으로나 생김새로 스크립트 언어라는 착각을 불러 일으킬 수 있지만 절대 스크립트언어가 아닙니다. 이런 오해를 불러일으킬만한 소지는 원래의 Swift 언어는 병렬 스크립트 언어이기 때문입니다. 그런데 애플은 스크립트 언어로 채용한 것이 아니라, 컴파일 언어로 채용했습니다. 원래의 Swift라면 스크립트 언어라고 표현하는 것이 잘못된 것은 아니지만, 애플이 발표한 Swift는 스크립트 언어가 아닙니다.


2. Swift는 자료형에 관대하다

아니요. 저는 절대 그렇게 생각하지 않습니다. 겉으로는 형식추론을 통해 자료형에 관대하게 보일 수 있지만, 오히려 이제껏 어느 언어보다도 더 자료형에 굉장히 제한적이고 엄격합니다. 이는 옵셔널이라는 성격만 보아도 쉽게 알 수 있습니다. Swift의 근본 원칙(?) 신념(?)은 Safe, Modern, Powerful 입니다. 여기서 우리가 눈여겨 보아야 할 점은 Safe 입니다. Safe를 지향하는 언어가 과연 자료형에 대해서 신경쓰지 않고 맘대로 코딩하도록 만들었을까요? 잘 생각해 보시기 바랍니다.


3. Swift는 초보자(혹은 스크립트 언어 개발자)가 배우기 쉽다

음... 글쎄요.. 아마 조건이 붙어야 하지 않나 싶습니다. '객체지향 프로그래밍의 기초가 빠삭한' 이라는 수식어가 붙거나, '여타 다른 객체지향 언어를 아는' 정도의 수식어가 붙는다면 조금 이해할 수 있습니다. 그러나, 초보자가 어떤 언어를 배우기 쉽다. 음... 학습용 언어인 스크래치 같은 경우가 아니고서야 초보자가 배우기 쉬운 언어가 정말 따로 존재하는 걸까요? 

'Objective-C 보다는 배우기 쉽다' 라는 반응은 충분히 수긍이 갑니다. 하지만, 그냥 'Swift는 초보자도 배우기 쉽다'라는 말은 좀처럼 쉽게 수긍이 가지는 않습니다. 스크립트 언어를 활발히 사용하던 개발자라도, 객체지향 언어와 함수형 언어의 특징을 제대로 이해하고 있지 않다면 (특히나 애플의 프레임워크에 사용하기 위해 Swift를 사용한다면) 충분히 배우기 쉽지 않은 것 같습니다.


4. Swift는 이미 철지난 언어인 Objective-C 보다 훨씬 강력하고 빠르다
네. 반은 맞고 반은 틀리다고 할 수 있습니다. Swift가 훨씬 현대적이고 강력한 언어라는 점은 충분히 동의합니다. 저도 그 매력에 한참 빠졌거든요. 그렇지만, Objective-C 보다 훨씬 성능이 좋다는 것은 옳지 않은 표현인 것 같습니다. 때에 따라서 Objective-C가 더 성능이 좋을 때도 있구요, 그렇지 못할 때도 있습니다. 여기서 성능은 아마도 컴파일 성능이나, 런타임 성능을 말하는 것이겠지요. 어떤 의미든간에 해당되는 말입니다. 더불어, 개발자의 생산성에 있어서도, Swift로 해결할 때 더 빠른 문제가 있고, Objective-C로 해결할 때 더 빠른 문제가 있는 것입니다. 결국 선택의 문제겠지요. 하지만 애플은 아직까지는 두 언어를 선택적으로, 혹은 혼합하여 사용할 수 있는 길을 충분히 열어두었습니다. 가장 좋은 방법은 때에따라 두 언어를 적절히 섞어 프로젝트를 구성하는 것이라고 생각합니다.

힘을 합치면 더 강력해지는 이 친구들처럼 말이죠...ㅎㅎ




5. Apple은 Objective-C를 결국에는 버릴 것이다
글쎄요...^^ 그건 애플만이 알겠죠? 여기에는 더 할 말이 없습니다. 다만, Swift는 정말 강력한 언어라는 것 밖에는 표현할 길이 없군요. 하지만, Objective-C도 그 나름대로의 강력함을 가지고 있기 때문에 굳이 버릴 필요가 있는가에 대한 의문도 듭니다. 여러분은 어떻게 생각하시는지요? 궁금합니다.



이렇게 몇 가지 많은 분들의 생각에 저의 생각을 덧붙여 봤습니다. 물론 틀린 내용이 있을 수도 있고 제가 잘못알고 있는 부분도 있을 것 같기도 하구요, 읽고계신 분들과 다른 의견이 있을 수 있습니다. 여러분의 다양한 의견을 듣고싶습니다 :)

by yagom

facebook : http://fb.yagom.net

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

twitter : http://twitter.yagom.net ( @yagomsoft )

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

RSS Feed 받기   


Posted by yagom