Swift 빠르게 훑어보기

Apple에서 차세대 언어로 Swift를 발표했습니다. 개인적으로는 Obj-C에 비해 꽤 정감이 가는 문법이라 요즘 한창 Swift로 외도 중입니다. 그래서 공부도 할겸 간단히 정리해보았습니다. 주요 기능을 정리하긴 했지만 전부를 정리한 것은 아니니 이 점에 주의해주세요.

기본

  • 스트롱 타입(Strong type) 언어입니다.
    변수를 선언하거나 사용할 때, 또는 함수의 인수를 선언하거나 반환값을 선언할 때 자료형을 꼭 정해줘야 한다는 뜻입니다. 원칙적으로는 그런데 타입 추론(type inference) 기능도 있어서 변수 선언할 때 초기값과 함께 선언하면 대충 알아서 판단하기도 합니다.
  • 유니코드 기반입니다.
    자바스크립트처럼 유니코드 기반이라 변수 이름으로 유니코드 문자를 사용할 수 있습니다(이모지도 가능). 문자열의 길이를 셀 때도 영문과 한글이 모두 1 길이입니다.
  • 대소문자 구분합니다.
  • null이 아닌 nil을 사용합니다.
// 한 줄 주석은 이렇게 씁니다.
/* 여러 줄 주석은
   이렇게 씁니다.
*/
/* 여러 줄 주석은
   /* 이렇게 중첩도 가능한 것이 */
   특이한 점입니다.
*/

var num = 1 // 타입 추론에 의해 정수형으로 선언됩니다. 선언은 var 키워드를 사용합니다.
var 둘 = 2 // 유니코드 변수명 가능합니다. 한 줄에 한 문장만 쓰면 세미콜론 안 써도 됩니다.
var 셋 = 3; var 넷 = 4 // 하지만 두 문장 이상을 쓰려면 문장 끝에 세미콜론 붙여야 합니다.
var (five, six) = (5, 6) // 이렇게 하면 five와 six 변수에 각각 5, 6이 저장됩니다.
var 일곱 // 변수 이름만 선언하면 에러가 발생합니다.
var eight:Int = 8 // 타입과 함께 선언할 때는 "var 변수이름:타입"으로 선언합니다.
eight = 8.2 // 타입이 맞지 않아 에러가 발생합니다.
eight = Int(8.2) // 형 변환하면 잘 됩니다.
eight = nil // 에러가 발생합니다. 변수가 nil을 가지려면 아래처럼 선언할 때 ?를 추가해야 합니다.
var nine:Int? = nil // 타입 뒤에 ?을 붙이면 nil을 할당할 수 있습니다.
var ten:Int? // 이래도 nil이 자동으로 할당됩니다.
var million:Int = 1_000_000 // 숫자 사이에 _를 쓸 수 있습니다.
var twomillion:Int = 2__0_0_0_00_ // 사실 제일 앞만 아니면 숫자 어디에 써도 됩니다.

let pi = 3.14 // 상수는 let 키워드로 선언합니다.
pi = 3.1415 // 값을 바꾸려 하면 에러가 발생합니다.
// 그 외에는 변수와 같습니다.

let str = "문자열"
let 명시적문자열:String = "String 타입과 함께 선언합니다."
var 여덟:String = "eight은 " + String(eight) + "입니다." // 형변환이 필요합니다.
var 백만:String = "million은 \(million)입니다." // \(...) 문법이 더 편리합니다.
var 백만여덟 = "이 값은 \(million + eight)입니다." // 수식이나 함수 실행도 가능합니다.

배열과 딕셔너리

// 배열
var shoppingList = ["catfish", "water"] // [..]로 선언합니다.
shoppingList[1] = "생수 한 병" // 수정
shoppingList[2] = "마늘" // 에러. 이런 식으로는 추가 못합니다.
shoppingList[0] = 5 // 에러. 한 배열에는 같은 타입만 저장할 수 있습니다.
shoppingList = [] // 빈 배열로 만듭니다.
shoppingList.append("대파") // .append()로 추가합니다.
shoppingList += "배추" // += 연산자로도 추가합니다.
shoppingList += ["생강", "무"] // 배열도 합칠 수 있습니다.
/* shoppingList는 ["대파", "배추", "생강", "무"] */
shoppingList[2..4] = ["사이다", "당근"] // 3, 4번째 원소를 "사이다","당근"으로 교체.

var emptyList = [] // 빈 배열
emptyList.append("아이템") /* 타입을 정하지 않은 빈 배열이라 .append 메소드가 없습니다. 에러가 발생합니다. */

var stringList:String[] = [] // 빈 문자열 배열
stringList.append("나랏말싸미") // 이건 잘 동작합니다.

// 딕셔너리
var 제조사 = [
    "TV" : "LG",
    "노트북" : "Apple", // 마지막 쉼표는 없어도 됩니다.
]
제조사["마우스"] = "로지텍" // 이런 식으로 추가 할 수 있습니다.

let 빈_문자열_배열 = String[]()
// 또는
let 빈_문자열_배열:String[] = []
let 빈_딕셔너리 = Dictionary<String, Float>()

제어문

자바스크립트를 비롯한 C 계열의 많은 언어와 달리 제어문의 조건절에는 괄호를 쓰지 않습니다.

// if
var optionalName:String? = "John"
var greeting = "Hello!"
if optionalName {
    greeting = "Hello, \(optionalName)"
} else {
    greeting = "Who are you?"
}
// 또는
if let name = optionalName {
    greeting = "Hello, \(name)"
} else {
    greeting = "Who are you?"
}

// for .. in
let fruits = ["귤", "사과", "수박"]
for fruit in fruits {
    println(fruit)
}

let prices = ["귤":300, "사과":1000, "수박":12_000]
for (fruit, price) in prices {
    println(fruit+"의 가격은 \(price)입니다.")
}

for character in "Hello" {
    println(character) // H, e, l, l, o가 각각 출력
}

// for..조건..증감
var index:Int = 0
for index = 0; index < 3; index++ {
    println("index = \(index)")
}
// 위 코드는 for...in으로 다음과 같이도 표현할 수 있습니다.
for index in 0..3 {
    println("index = \(index)")
}

// while
var i = 1
while i < 1000 {
    i *= 2
}

// do while
var i = 1
do {
    println("hello \(i++)")
} while i < 3

// switch
/* case 문 끝날 때 break 안 씁니다. 쓰면 오히려 에러가 발생합니다.
   C, JS처럼 다음 case문으로 넘어가고 싶다면 fallthrough를 사용합니다.
 */
let 채소 = "청양고추"
switch 채소 {
case "상추":
    let 채소설명 = "먹으면 졸리다."
case "오이", "양상추": // 여러값
    let 채소설명 = "샐러드 만들 때 좋다."
case let x where x.hasSuffix("고추"): // where를 사용해 조건도 가능
    let 채소설명 = "\(x)는 매울까?"
default: // 만족하는 case가 없을 때 기본값
    let 채소설명 = "채소는 몸에 좋다."
}
// 결과 : 채소설명 == "청양고추는 매울까?"

// switch - 범위
let count = 3_000_000
var 별의숫자:String = ""
switch count {
case 0:
    별의숫자 = "없다"
case 1...3: // 1부터 3까지
    별의숫자 = "아주 조금 있다"
case 4...9: // 4부터 9까지
    별의숫자 = "약간 있다"
case 10...99:
    별의숫자 = "수십개 있다"
case 100...999:
    별의숫자 = "수백개 있다"
case 1000...9999:
    별의숫자 = "수천개 있다"
default:
    별의숫자 = "어마어마하게 많다"
}
println("하늘에 별이 \(별의숫자).")

// switch - 튜플
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    println("(0, 0)은 원점입니다.")
case (_, 0): // 두번째 값만 일치할 때
    println("(\(somePoint.0), 0)은 X축 위에 있습니다.")
case (0, _): // 첫번째 값만 일치할 때
    println("(0, \(somePoint.1))은 Y축 위에 있습니다.")
case (-2...2, -2...2): // 범위
    println("(\(somePoint.0), \(somePoint.1))은 상자 안에 있습니다.")
default:
    println("(\(somePoint.0), \(somePoint.1))은 상자 바깥에 있습니다.")
}

함수

함수는 객체로서 전달할 수도 있고 중첩도 가능합니다.

// 문자열을 반환하는 함수
func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")

// 인수 기본값 설정
func greet(name: String, day: String = "Monday") -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob")

// 튜플로 여러 값을 한꺼번에 반환하는 함수
func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}
let (p1, p2, p3) = getGasPrices()

// 참조 인수 - inout 키워드를 사용합니다.
func swapInt(inout a: Int, inout b: Int) {
    let tempA = a
    a = b
    b = tempA
}
var (someInt, anotherInt) = (3, 107)
swapInt(&someInt, &anotherInt)

// 여러 개의 숫자를 인수로 받는 함수. 인수가 배열로 전달됩니다.
func setup(numbers: Int...) {
    // 실행 코드 자리
}
setup(5, 16, 38) // 전달한 숫자는 배열로서 전달됩니다.

// 함수 중첩
func printWelcomeMessage() -> String {
    var y = "Hello,"
    func add() {
        y += " world" // 부모 함수의 값을 사용하고 있다는 점에도 주목
    }
    add()
    return y
}
printWelcomeMessage() // Hello, world

// 함수를 반환하기
func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

// 함수를 인수로 받기
func myMax( 갑:Int, 을:Int, 앞이크다:(Int, Int) -> Bool ) -> Int {
    if 앞이크다(갑, 을) {
        return 갑
    } else {
        return 을
    }
}

// 함수를 변수에 저장
func 더하기(a:Int, b:Int) -> Int {
    return a + b
}
var addFunc:(Int, Int) -> Int = 더하기

클로져

자바스크립트 개발자들에게는 익숙하지만 컴파일 언어 사용자들에게는 아직도 다소 생소할 수 있는 개념입니다.

// 클로저문법 : { (인수선언) -> 반환타입 in 함수몸체 }
// `->` 앞은 인수, 뒤는 반환값 타입
// `in` 앞은 클로저 헤더(인수 선언, 반환값 정의), 뒤는 클로저 본문(함수 몸체)
var numbers = [1, 2, 3, 4, 5]
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

// 가능한 경우 선언시 타입을 생략할 수 있습니다.
numbers = [1, 2, 6]
numbers = numbers.map({ number in 3 * number })
println(numbers) // [3, 6, 18]

// 굳이 인수 이름이 필요없는 경우에는 인수 선언도 생략할 수 있습니다.
// 전달된 인수는 첫 번째 인수부터 $0, $1, $2...와 같이 접근할 수 있습니다.
numbers = [2, 5, 1]
numbers.map { 3 * $0 } // [6, 15, 3]

enum

enum 방향 {
    case 동
    case 서
    case 남
    case 북
}
// 또는
enum 방향 {
    case 동, 서, 남, 북
}

// 할당 및 사용 - 값 앞의 점(.)에 주의
let 어디로 = 방향.동
switch 어디로 {
    case .동:
        println("해뜨는 동쪽")
    case .서:
        println("해지는 서해바다")
    case .남:
        println("따뜻한 남쪽나라")
    case .북:
        println("북쪽은 추울텐데")
}

// 타입과 값의 명시적 선언
enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

클래스와 구조체

사실 겉보기에는 클래스와 구조체는 다른 점이 거의 없습니다. 둘 다 생성자도 추가할 수 있고, 메소드도 추가할 수 있으며 상속도 되고 (뒤에 나오는) protocol을 적용하는 것도 가능합니다. 가장 큰 차이점은 클래스는 참조 타입(Reference Type)이고 구조체는 하나의 자료형이라는 것입니다. 공식 문서에 따르면 이러한 특성 때문에, 할당 또는 복사할 때 구조체는 passed by value 방식으로 아예 다른 인스턴스로 복제되지만 클래스는 passed by reference 방식으로 참조만 복제된다고 합니다. 그 외의 차이점으로는 구조체는 다른 구조체를 상속할 수 없다는 점, ... 등이 있습니다.
클래스와 구조체의 인스턴스를 만들 때는 구조체이름() 또는 클래스이름()처럼 마치 함수를 실행하듯 사용합니다. 인스턴스를 만드는 별도의 키워드가 없습니다.

// 구조체
struct Resolution {
    var width = 0
    var height = 0
}
let res = Resolution()
// 또는
let hd = Resolution(width: 1920, height: 1080) // 초기화

// 클래스
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}
let someVideoMode = VideoMode()

// 생성자 - init() 사용.
struct 섭씨 {
    var 온도:Double = 0.0
    init(화씨온도:Double) {
        온도 = (화씨온도 - 32.0) / 1.8
    }
    init(켈빈온도:Double) {
        온도 = 켈빈온도 - 273.15
    }
}
let 물의끓는점 = 섭씨(화씨온도:212.0)
// 물의끓는점.온도 = 100.0

class 도형 {
    init() {
    }
    func 면적() -> Int {
        return 0
    }
}

// 상속
class 사각형: 도형 {
    var width = 0
    var height = 0
    init(width:Int, height:Int) {
        super.init()
        self.width = width
        self.height = height
    }
    override func 면적() -> Int {
        return width * height
    }
}

// 프로퍼티 getter, setter
class 정사각형: 사각형 {
    var 한변의길이:Int {
        get {
            return width
        }
        set(길이) {
            width = 길이
            height = 길이
        }
    }
}

// 프로퍼티 변경 감시 - willSet(변경전), didSet(변경후)
class 자동차 {
     var 이동거리:Int = 0 {
         willSet(거리) {
             println("\(거리)만큼 움직일 겁니다.")
         }
         didSet {
             if 이동거리 < oldValue {
                 println("\(oldValue - 이동거리)만큼 뒤로 이동")
             } else {
                 println("\(이동거리 - oldValue)만큼 앞으로 이동")
             }
         }
     }
}

// 타입 프로퍼티(Type Property) - 정적 프로퍼티
struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static func someMethod() {
        // ...
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
}
class SomeClass {
    class var storedTypeProperty = "Some value."
    class func someMethod() {
        // ...
    }
}

// @lazy 프로퍼티 속성 - 사용할 때 초기화
class XmlExporter {
    func saveToFile(filename:String) {
        // 메소드 내용
    }
}

class XMLData {
    @lazy var exporter = XmlExporter()
    var data: String
    // 그 외 기능들
}
let xml = XMLData()
// 이 시점에서는 xml.exporter가 초기화되지 않음.
xml.exporter.saveToFile() // 여기서 초기화

기타

프로토콜(Protocol)

C++의 interface와 비슷합니다. enum, struct, class가 프로토콜을 상속합니다. 변수나 함수의 타입까지만 정의할 뿐 몸체를 정의하지는 않습니다.

protocol 예제프로토콜 {
    var 읽기쓰기가능변수 : Int { get set }
    var 읽기전용변수 : Int { get }
    func 어떤함수() -> Double
}

class 클래스: 부모클래스, 예제프로토콜, 프로토콜은여러개도가능 {
    // 클래스 내용
}

struct 구조체: 예제프로토콜, 또다른프로토콜 {
    // 구조체 내용
}

Extension

이미 정의된 클래스, 구조체, enum을 확장할 수 있는 손쉬운 방법입니다.

extension Int {
    func 번_반복(클로저:() -> ()) {
        for _ in 0..self { // _는 변수 인덱스가 필요없을 때 사용
            클로저()
        }
    }
}

2.번_반복({
    println("안녕하세요!")
})

연산자 정의

기존의 연산자를 재정의 할 수도 있고, / = - + * % < > ! & | ^ . ~ 문자를 조합해 커스텀 연산자를 만들 수도 있습니다.

/* @infix는 좌항, 우항이 있는 이항 연산자. 예) a + 3
   @assignment는 할당 연산자. 예) a += 3
   @prefix는 값 앞에 붙는 연산자. 예) ++a
   @postfix는 값 뒤에 붙는 연산자. 예) a++
*/
@infix func + (a: Int, b: Int) -> Int {
    return a - b
}
var x = 5 + 4 // x == 1

// Vector2D 타입에 대해 더하기 연산자 정의
struct Vector2D {
    var x = 0.0, y = 0.0
}
@infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}

// 커스텀 연산자
operator postfix ^-^ {}
@postfix @assignment func ^-^ (inout num:Int) -> Int {
    num += 2
    return num
}
var 숫자 = 1
println("숫자의 값은 \(숫자^-^)")

Generic

C++ 또는 Java 같은 스트롱 타입 언어에서 코드에 유연함을 더할 수 있는 방법의 하나입니다. 간단히 말하면 타입의 템플릿을 만들어두고 여러 형태로 사용하겠다는 건데, 경험해보지 않은 분들에게는 다소 헷갈릴 수도 있습니다.

func 서로바꿈<T>(inout a: T, inout b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
let num1 = 3.5, num2 = 5.8
서로바꿈<Double>(num1, num2)

struct Stack<T> {
    var elements = T[]()

    mutating func push(element: T) { // mutating은 하단에서 설명합니다.
        elements.append(element)
    }

    mutating func pop() -> T {
        return elements.removeLast()
    }
}

class 계산<T> {
    func 더하기(a:T, b:T) -> T {
        return a + b
    }
    func 곱하기(a:T, b:T) -> T {
        return a * b
    }
}
var calc = 계산<Int>()
calc.더하기(3, 5)

Type Alias

C계열의 typedef 와 비슷한 기능을 합니다.

typealias 실수 = Double
let num: 실수 = 30.2

typealias 포인트 = (Int, Int)
var point = (10, 20)

typealias 정수더하기함수: (Int, Int) -> Int

Mutating

struct 또는 enum 타입의 메소드는 기본적으로 인스턴스에서 프로퍼티의 값을 바꿀 수 없습니다. 하지만 메소드 앞에 mutating 키워드를 추가하면 프로퍼티의 값을 수정할 수 있습니다.

struct Point {
    var x = 0.0, y = 0.0
    func moveBy(x deltaX:Double, y deltaY:Double) {
        x += deltaX
        y += deltaY
    }
}
var pt = Point(x:1.0, y:1.0)
pt.moveBy(x:3.0, y:5.0) // 값을 바꿀 수 없으므로 에러

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX:Double, y deltaY:Double) {
        x += deltaX
        y += deltaY
    }
}
var pt = Point(x:1.0, y:1.0)
pt.moveBy(x:3.0, y:5.0) // 정상 동작
// pt.x == 4.0, pt.y == y:6.0

참고자료

[adsense]

댓글을 남겨주세요

This site uses Akismet to reduce spam. Learn how your comment data is processed.