[swift2] Xcode7 beta3 Swift 언어 변경사항

Swift 2015. 7. 11. 01:37



Swift Language Changes


1. 표현식 결과 포맷팅 방식 변경

po 또는 expr -O를 사용할 때 Swift 표현식 결과를 포맷팅하는 방식이 beta3에서 상당히 변화되었습니다.
첫 번째 beta 버전에서 소개되었던 Customization이 다음과 같은 방식으로 개선되었습니다.

  • CustomDebugStringConvertible 또는 CustomStringConvertible 프로토콜을 구현하는 타입들은
    상대적으로 debugDescription, description 메소드를 사용하여 포맷팅 결과를 제공합니다. 둘다 구현하지 않은경우
    타입이름이 출력되고, 레퍼런스 타입들은 Objective-C 클래스처럼 참조되는 주소를 보여줄 것입니다.

  • 레퍼런스 타입들과는 다르게 enums, tuples, struct 같은 value 타입들은 디폴트로 summary 아래에 모든 멤버들이
    들여쓰기 되어 보여질것입니다. CustomReflectable 구현함으로써 이러한 동작들을 커스터마이징 할수 있습니다.

출력 커스터마이징은 모든 필드와 값들의 리스트를 제공하기 위해 p 또는 expr을 -O옵션없이 사용해서 건너뛸 수 있습니다.


2. String 타입 Enum의 기본값

string raw타입으로 선언된 enum 엘리먼트가 명시적으로 raw value를 갖지 않으면, enum의 이름을 디폴트 텍스트로
갖습니다.

예를 들어 다음 선언은

enum WorldLayer : String {
    case Ground, BelowCharacter, Character
}

아래와 동일합니다.

enum WorldLayer : String {
    case Ground = "Ground"
    case BelowCharacter = "BelowCharacter"
    case Character = "Character"
}  


3. 이름 없는 파라미터

이름없는 파라미터는 명시적으로 이름이 없음을 의미하는 _:를 사용합니다**. 예를 들어 다음은 이제 에러입니다.

func f(Int) {}  // 에러 

다음과 같이 작성되야합니다.

func f(_:Int) {}  

이것이 인자의 레이블 모델을 단순화시킵니다.
그리고 func f((a: Int, b:Int)) 같은 경우에 a 그리고 b라는 이름의 파라미터를 가지지 않는지 명료하게 해줍니다.


4. array에 tuple 추가가 가능합니다.


5. Objective-C 클래스의 제네릭 서브클래싱이 지원됩니다.


6. 문법이 조정되어 .으로 시작하는 라인들은 항상 메서드 또는 프로퍼티 룩업으로 파싱되어 다음과 같은 코드 포맷팅이 가능합니다.

foo
    .bar
    .bas = 68000  

문맥에 따른 static member looup 으로 시작하는 라인은 더이상 사용 불가능합니다.

.staticVar = MyType()  


7. try!

try! 표현식에서 발생한 에러, 최상위 또는 playground에서 처리되지 않은 에러들은 trap 메시지에 에러 값들이 표시됩니다.


8. NS_REFINDED_FOR_SWIFT 매크로

오리지널 구현을 사용가능하면서도, Swift의 동일 API에서는 더 좋은 버전을 제공하기 위해 NS_REFINED_FOR_SWIFT
매크로는를 Objective-C 선언부에 사용할수 있습니다. 예를 들어 클래스를 인자로 받는 Objective-C API는 Swift에서는
파라미터 타입을 더 정밀하게 제공할수 있습니다.

NS_REFINDED_FOR_SWIFT 매크로는 경우에 따라 다르게 동작합니다.

  • init 메소드는 첫 번째 외부 파라미터 이름에 __이 붙는 initializer가 임포트 됩니다.
- (instancetype)initWithClassName:(NSString*)name NS_REFINED_FOR_SWIFT

=> init(__className:String)
  • 다른 메소드들은 기본이름에 __이 붙어 임포트 됩니다.
- (NSString*)displayNameForMode:(DisplayMode)mode NS_REFINED_FOR_SWIFT;
=> func __displayNameForMode(mode:DisplayMode) -> String  
  • subscript 메소드는 다른 메서드들처럼 취급되며 subcript로 임포트 되지 않습니다.

  • 다른 선언들은 이름 앞에 __이 붙습니다.

@property DisplayMode mode NS_REFINED_FOR_SWIFT;
var __mode:DisplayMode { get set }


9. 타입 제네릭 파라미터

Swift 표준 라이브러리에서 제공하는 타입 제네릭 파라미터들은 API에서 타입의 역할을 반영하도록 이름이 변경되었습니다.
예를 들어 Array는 Array이 되었고, UnsafePointer는 UnsafePointer가 되었습니다.




[swift2] Swift에서 에러 처리 (Error Handling)

Swift 2015. 7. 10. 00:55


(본 포스트는 The Swift Programming Language - Swift 2 Prerelease. 의 Error Handling 장을 번역한 문서입니다.)

에러처리는 프로그램에서 에러조건에 대응하고 회복하는 과정입니다. Swift는 런타임에 회복 가능한 에러를 던지고, 잡고,
전파하고, 조작하는 first-class(1급) support를 제공합니다. 몇몇 함수와 메소드는 항상 완전한 실행과 유용한 정보를
출력한다고 보장할 수 없습니다. Optional은 값의 부재를 표현하기 위해 사용되지만 함수가 실패했을때 실패원인을 이해하고
거기에 맞춰 코드를 대응하는게 더 유용할때가 있습니다.

디스크의 파일로부터 데이터를 읽고 처리하는 작업을 예로 들어보겠습니다.
경로에 파일이 없거나 읽기 권한이 없거나, 파일이 호환되지 않는 포맷으로 인코딩 되어있는 것 등 여러가지 이유로 작업이
실패할 수 있습니다. 이러한 상황의 차이를 구별하고 필요하다면 사용자와 커뮤니케이션해야 에러를 해결하고 회복할수 있습니다.


1. 에러의 표현 (Representing Errors)

스위프트에서 에러는 ErrorType 프로토콜을 구현하는 타입값으로 표현됩니다.
Swift enumerations는 특히 관련있는 에러조건(에러의 원인에 대한 정보를 연관값으로 가진채) 그룹을 모델링하는데
적합합니다. 이러한 이유로, Swift enumerations는 ErrorType을 자동으로 구현하고, 컴파일러에 의해 생성된 구현체를 갖습니다.

자판기의 에러조건을 표현하기 위한 예입니다.

enum VendingMachineError: ErrorType {
    case InvalidSelection
    case InsufficientFunds(required: Double)
    case OutOfStock

}

이 예제에서 자판기는 다음과 같은 이유로 실패할수 있습니다.

  • 1. 요청한 아이템이 유효한 선택이 아닌 경우. InvalidSelection 으로 표현.
  • 2. 요청한 아이템 비용이 입금한 비용보다 더 큰 경우. InsufficentFunds로 표현.
    연관된 Double 값은 트랜잭션을 완료하기 위해 필요한 추가요금.
  • 3. 요청한 아이템의 재고가 없는 경우. OutOfStock으로 표현.


2. 에러 던지기 (Throwing Errors)

함수 또는 메서드가 에러를 던질수 있다는것을 나타내기 위해, 선언부의 파라미터 다음에 throws 키워드를 사용합니다.
만일 return 타입이 있으면, throws 키워드는 리턴 화살표 앞에 표기합니다. 함수, 메소드, 클로져는 명시적으로 표시하지
않으면 에러를 던질 수 없습니다.

func canThrowErrors() throws -> String

func canThrowErrors() -> String

throws를 선언한 함수의 몸체 어느 시점에서도 throw 문을 사용하여 에러를 던질수 있습니다.
아래 예제에서 vend(itemNamed:) 함수는 요청한 아이템의 재고가 없거나, 금액이 부족할 때 에러를 던집니다.

stuct Item {
    var price:Double
    var count:Int
}

var inventory = [
    "Candy Bar": Item(price:1.25, count:7),
    "Chips": Item(price: 1.00, count:4),
    "Pretzels": Item(price: 0.75, count:11)
]

var amountDeposited = 1.00  

func vend(itemNamed name:String) throws {
    guard var item = inventory[name] else {
        throw VendingMachineError.InvalidSelection
    }

    guard item.count > 0 else {
        throw VendingMachineError.OutOfStock
    }

    if amountDeposited >= item.price {
        amountDeposited -= item.price
        --item.count
        inventory[name] = item
    } else {
        let amountRequired = item.price - amountDeposited
        throw VendingMachineError.InsufficentFunds(required: amountRequired)
    }
}

먼저 quard 문은 item 상수와 count 변수를 inventory의 대응하는 값과 바인드하기 위해 사용되었습니다.
item 이 inventory에 없으면 InvalidSelection 에러를 던집니다. 그 다음으로 요청한 item의 count를 체크해
가용성을 결정합니다. count가 0보다 같거나 작으면 OutOfStock 에러를 던집니다. 마지막으로 요청한 아이템의 가격과
현재 입금된 금액을 비교합니다. 입금된 금액이 아이템의 가격보다 크면, 입금액에서 가격만큼 차감되고, inventory에 있는
재고량이 감소합니다. 그리고 함수는 요청한 아이템을 반환합니다. 그렇지 않으면 초과된 금액이 연관값으로 전달되어 InsufficentFunds 에러를 던져집니다. throw 문은 즉시 프로그램의 제어를 이동시키기 때문에 아이템은 구매를 위한
모든 요구조건이 만족 될때만 판매됩니다.
throwing 함수를 호출할 때, 호출부 앞에 try를 붙힐 수 있습니다. 이 키워드는 함수가 에러를 던질수 있고,
try 이후 코드라인들이 실행되지 않을 수 있음을 암시합니다.

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels"
]

func buyFavoriteSnack(person:String) throws {
    // a ?? b 구문은 a가 nil이 아니면 a를, nil이면 b를 반환. 
    let snackName = favoriteSnacks[person] ?? "Candy Bar"

    try vend(itemNamed: snackName)
}

buyFavoriteSnack() 함수는 person이 가장 좋아하는 snack을 찾고, 그것을 구매하려고 시도합니다.
좋아하는 snack이 리스트에 없다면 candy bar를 사려고 시도합니다. 에러를 던질수도 있는 vend 함수가 호출되기
때문에 try 를 함수 앞에 사용했습니다. buyFavoriteSnack(_:) 함수는 throwing 함수이기 때문에, vend 함수
던지는 어떤 에러도 buyFavoriteSnack 함수가 호출되는 상위로 전파될수 있습니다..


3. 에러를 잡고 처리하기 (Catching and Handling Errors)

에러를 잡아 처리하기 위해 do-catch 문을 사용할 수 있습니다.

do {
    try function that throws
    ...
} catch pattern {
    statements
}

에러가 던져지면 catch 절에 의해 처리 될때까지 에러는 외부 스코프로 전파됩니다. catch 절은 catch 키워드 다음에
매칭할 에러패턴 그리고 실행할 구문집합으로 구성됩니다. switch 문처럼 컴파일러는 catch절이 모두 포괄되어 있는지
검사합니다. 그러한 결정이 이루어지면 에러는 처리됩니다. 해당 스코프에서는 에러를 처리하거나 또는 함수가 throws로
선언되어야 합니다. 에러가 처리됐다는 것을 보장하기 위해 모든 에러들과 매칭되는 패턴을 가진 catch절을 사용합니다.
catch절이 패턴을 명시하지 않으면 그 절은 로컬상수 error에 모든 에러를 매칭하여 바인딩합니다. 패턴 매칭에 관한 더
자세한 정보는 Patterns 장을 참고하세요.

do {

    try vend(itemNamed: "Candy Bar")

} catch VendingMachineError.InvalidSelection {

    print("Invalid Selection.")

} catch VendingMachineError.OutOfStock {

    print("Out of Stock.")

} catch VendingMachineError.InsufficientFunds(let amountRequired) {

    print("Insufficient funds. Please insert an additions $\(amountRequired).")

}

위 예제에서, vend(itemNamed:) 함수는 에러를 던질 가능성이 있기 때문에 try 표현식 안에 표현되었습니다. 에러가 던져지면, 실행흐름은 즉시, 전파를 계속할지 결정되는 catch 절로 이동합니다. 만일 에러가 던져지지 않았다면,
vend(itemNamed:)의 반환값은 snack에 할당되고 do문의 남아있는 구문들이 실행됩니다.


4. 에러 전파 비활성화 (Disabling Error Propagation)

사실상 런타임에 에러를 던지지 않는 throwing 함수나 메서드를 알수 있는 경우가 있습니다. 이러한 경우에는
보통 사용하는 try 표현 대신에 try!(forced-try) 표현을 사용하여 throwing 함수나 메서드를 호출할 수 있습니다.
try!를 사용하여 throwing 함수나 메서드를 호출하면 error 전파가 비활성화 되고, 에러를 던지지 않는 런타임
assertion 안에서의 호출로 랩핑합니다. 만일 실제로 에러가 thrown 되면, 런타임 에러를 만나게 될것입니다.


5. 클린업 액션 지정하기 (Specifying Clearn-Up Actions)

defer 구문을 사용해서, 코드 실행흐름이 현재 코드블럭을 떠나기 직전에, 일련의 코드 집합을 실행할 수 있습니다.
에러의 발생유무와는 상관없이 반드시 실행되야할 클린업 작업을 처리할수 있습니다. 예를 들면 열려진 파일 식별자를
닫거나, 수동으로 할당된 메모리를 해제하는 작업이 될 수 있습니다.

defer 문은 현재 scope이 끝날 때까지 실행을 연기합니다. defer 키워드와 나중에 실행할 코드 구문들로 구성됩니다.
연기된 구문들은 제어흐름을 벗어나게 하는 break 또는 return, 에러를 던지는 것과 같은 코드를 포함해서는 안됩니다.
연기된 액션들은 기술된 순서의 역순으로 실행됩니다. 즉 처음 defer 문에 있는 코드는 두 번째 defer 문의 코드 다음에
실행됩니다.

func processFile(filename:String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }

        while let = try file.readline() {
            // work with the file
        }
        // close(file) is called here, at the end of the scope.
    }
}

위 예제에서는 open() 함수가 대응하는 close() 함수 호출을 보장하도록 defer문을 사용했습니다. 이 호출은
에러가 던져졌는지 유무와 상관없이 실행됩니다.




[swift2] 타입의 텍스트적인 표현 커스터마이징하기

Swift 2015. 7. 1. 00:12


1. 값의 문자열 표현 얻기

모델 객체를 화면이나 디버깅 코드에 표시할 때, 텍스트적인 표현을 생성하면 유용할 때가 있습니다.
swift에서는 특정 타입을 string 형태로 표현하거나, 디버깅 목적으로 화면에 출력하기위해 CustomStringConvertible
CustomDebugStringConvert 프로토콜을 따르도록 선언할수 있습니다. 이 프로토콜 중 하나를 따르는 타입을 정의하면 print,
debugPrint와 같은 출력함수와 string interpolation 이 기본객체의 description을 호출하지 않고 이 함수들을 호출할 것입니다.

다음과 같은 그룹채팅을 위한 Message 타입이 있습니다.

struct Message {
    let from: String
    let contents: String
    let date: NSDate
}
 messages = [
    Message(from: "Sandy", contents: "Hey, what's going on tonight?", date: messageDates[0]),
    Message(from: "Michelle", contents: "Studying for Friday's exam. You guys aren't?", date: messageDates[1]),
    Message(from: "Christian", contents: "Nope. That's what tomorrow is for. Let's get food, I'm hungry!", date: messageDates[2]),
    Message(from: "Michelle", contents: "Maybe. What do you want to eat?", date: messageDates[3])
]  

메세지 객체중 하나를 출력하면, struct의 기본 description을 얻을 수 있습니다.
구조체의 모든 정보를 출력하는데, 채팅앱과 같이 특정한 경우의 로깅과 디버깅을 위해 보기좋게 포맷팅 되어 있지는 않습니다.
디버깅을 위해 특정타입이 출력되는방식을 커스터마이징 하기위해 CustomDebugStringConvertiable 프로토콜을 적용할 수 있습니다.


2. 디버깅 정보 출력 - CustomDebugStringConvertible 프로토콜

CustomDebugStringConvertiable 프로토콜은 한개의 필수 속성인 debugDescription를 가집니다.
이 속성에서 반환된 string은 객체의 description을 제공하기 위해 debugPrint 와 같은 함수에 의해 사용됩니다.
CustomDebugStringConvertible 프로토콜을 적용한 뒤 메시지 출력을 시도해보겠습니다.

extension Message: CustomDebugStringConvertible {
    public var debugDescription: String {
        return "[\(date) From: \(from)] \(contents)"
    }
}

debugPrint(messages[0])

이 description은 디버깅에 적합하며, 사용자의 채팅 메시지를 보여주고자 할때, 로컬시간이 적용된 좀더 사용자 친화적인 시간을
보여주고 싶을 수 있습니다. 좀더 사용자 친화적인 버전의 description을 생성하는 CustomStringnConvertible 프로토콜을 구현할 수 있습니다.


3. 사용자 친화적인 형식으로 출력 - CustomStringConvertible 프로토콜

CustomStringCovertible description을 제공하면, string interpolation과 print 함수는 더이상 debugDescription 속성을 사용하지 않습니다.

import Foundation

let dateFormatter = NSDateFormatter()
dateFormatter.doesRelativeDateFormatting = true
dateFormatter.dateStyle = .ShortStyle
dateFormatter.timeStyle = .ShortStyle

extension Message: CustomStringConvertible {
    public var description: String {
        return "\(contents)\n   \(from) \(dateFormatter.stringFromDate(date))"
    }
}

print(messages[0])



4. 요약

  • 디버깅 정보를 출력하기 위해서 CustomDebugStringConvertible 프로토콜을 구현합니다.
  • 사용자 친화적인 정보를 출력하기 위해서 CustomStringConvertible 프로토콜을 구현합니다.



[swift2] Xcode7 beta2 Swift 언어 변경사항

Swift 2015. 6. 24. 21:56



Swift Language Changes


  • payload를 가진 Enum case들은 함수처럼 사용될수 있습니다.
 enum Either<T, U> { 
    case Left(T), Right(U) 
 }
 let lefts: [Either<Int, String>] = [1, 2, 3].map(Either.Left)
 let rights: [Either<Int, String>] = [“one”, “two”, “three”].map(Either.Right) 
 // => {{Left 1}, {Left 2}, {Left 3}}

  • 구조체, enum, 프로토콜의 non-mutationg 메소드들에 부분적으로 self 파라미터가 적용됩니다.
let a: Set<Int> = [1, 2, 3]
let b: [Set<Int>] = [[1], [4]]
b.map(a.union)   
// => [[1, 2, 3], [1, 2, 3, 4]]

  • 정적타입 참조 또는 타입객체에 static 메소드처럼 .init를 사용하여 생성자를 초기화함수에 접근할 수 있습니다.
let x = String.init(5)
let stringType = String.self
let y = stringType.init(5)
let oneTwoThree = [1, 2, 3].map(String.init).reduce(“”, combine: +)  
// => "123"

  • String(5) 처럼 정적타입을 사용해서 생성할때도 여전히 .init은 암묵적으로 호출됩니다. .init은 동적타입 객체를 사용할 때, 함수인자로 initializer를 참조할 때 사용됩니다.


  • 거대한 구조체 또는 enum 타입의 코드를 생성할 때 코드 크기를 감소시키도록 개선되었습니다.


  • 이제 Swift는 assertion이 활성화 된채 빌드됩니다. 실용적인 crash report를 생성하는데 도움이 될것입니다.


  • 실행결과






[swift] 문자열 Indexing과 Slicing

Swift 2015. 6. 23. 23:00



Indexing and Slicing Strings

Swift string의 Indexing과 Slicing 대해서 알아보겠습니다.
string은 화면에 보여지는 문자들의 순서집합이라고 생각하는 것이 일반적입니다.
하지만 하나의 문자로 보일지라도 메모리 상에서는 다중 혹은 가변길이의 유니코드값으로 표현되는 경우도 있습니다.

유니코드 문자열

예를 들어 hat emoji (🎩)는 문자 a와 같은 문자보다 인코딩에 더 많은 비트가 필요로합니다.
Swift의 String 타입은 이러한 세부사항들을 처리해줍니다. string을 이루는 각 문자는 (메모리에서 차지하는 길이와는 상관없이)
사용자가 인식하는 유니코드 문자를 표현합니다. 이런 추상화 때문에 정수를 사용해서 string의 인덱스를 표현하는 것이 적합하지
않을수 있습니다.

대신 표준라이브러리는 string에서의 위치를 표현하기에 적합한 String.Index를 제공합니다.

문자열 카운팅

다음 문자열은 가변길이의 유니코드 문자들로 구성되어 있습니다.

var str = "Héllo, 🇺🇸laygr😮und!"
// str의 유니코드 문자의 개수: 18
str.characters.count  // swift2.0
count(str)            // swift1.2
// str의 UTF-16코드 포인트의 개수: 22
str.utf16.count      // swift2.0
count(str.utf16)     // swift1.2

깃발 (🇺🇸) 문자는 사실 2개의 유니코드 스칼라값이 결합되어 구성되지만 Swift는 1개의 문자로 카운팅합니다.
이런 특성이 폼 유효성 검사, 커서 포지셔닝, 텍스트 wrapping 구현을 더 쉽게합니다.
문자열 카운팅, 인덱싱, 슬라이싱은 모두 메모리가 아닌 사용자가 인식하는 문자 인덱스를 기반으로 하고있습니다.

문자열 slicing

string을 index하고 slice하는데 사용하는 Range 값을 생성해보겠습니다.

let badRange = 4..<12   // Range<Int>를 생성합니다.

str[badRange]   // error: 'subscript' is unavailable: cannot subscript String with range of Int

위 코드는 정수형을 string의 인덱스로 사용할수 없다는 컴파일 에러를 발생시킵니다.

대신 다음과 같이 String.Index 객체를 사용할 수 있습니다.

let range = advance(str.startIndex, 4)..<advance(str.startIndex, 12)

string은 startIndexendIndex 속성을 갖습니다.
string의 successor(), predecessor() 함수(swift2.0에서 지원)는 인덱스를 증가 또는 감소시킵니다.

advance() 함수를 사용하여 파라미터의 값만큼 offset를 이동할수 있으며, advance()함수는 인자로 전달받은 offset 만큼 successor(),predecessor()를 호출합니다.

String.Index 타입은 유니코드를 인식할수 있어서 advance()함수가 항상 string index를 적절하게 이동시킬 수 있습니다.

advance() 함수는 인자로 받은 index 타입과 동일한 타입의 값을 반환하기 때문에 ..< range 연산자는 그 타입에 해당하는 range를
생성할수 있습니다.

advance() 함수를 사용하여 string의 특정 index의 문자를 조회해보겠습니다.

str[advance(str.startIndex, 7)] // 🇺🇸를 반환한다.
str[range.startIndex]            // o를 반환한다.
str[range.endIndex]              // 😮를 반환한다.
str[range]                       // o, 🇺🇸laygr를 반환한다.

advance() 함수

또 다른 버전의 advance()함수를 사용해서 메시지를 10자로 제한하는 기능을 구현해보겠습니다.

let messageCharacterLimit = 10
let range = advance(str.startIndex, messageCharacterLimit , str.endIndex)..<advance(str.endIndex, 0)
let result = str.substringWithRange(range)
if result.isEmpty {
    print("empty result")
}
// "ygr😮und!"를 출력합니다.

advance()함수는 startIndex, offset 외에 세번째 파라미터로 endIndex를 전달하면 offset이 endIndex를 초과하는 경우
end.Index를 반환해줍니다. 세번째 인자는 일종의 상한값으로 동작합니다.




[swift] Map, Filter, Reduce

Swift 2015. 6. 22. 23:09



함수를 인자로 취하는 함수를 고차원 함수(higher-order function)라고 합니다.
Swift 표준라이브러리에서 array의 메서드로 제공하는 고차원 함수인 map, filter, reduce에 대해서 알아보겠습니다.

1. Map

    func map<U>(transform: (T) -> U) -> Array<U>

Map은 배열 각 요소 x에 변환함수 transform을 적용하고 그 결과값으로 구성된 배열을 반환하는 함수입니다.
배열요소들을 다른 값으로 맵핑하는 함수이지요.

2. Filter

func filter(includeElement: (T) -> Bool) -> Array<T>

Filter는 조건식을 인자로 받아, 조건식이 true를 만족하는 요소들로만 구성된 배열을 반환하는 함수입니다.
쉽게 말하면, 배열요소들을 필터링하는 함수입니다.

3. Reduce

func reduce<U>(initial: U, combine: @noescape (U, T) -> U) -> U

Reduce는 U를 초기값으로 하여,각 배열요소들과 순차적으로 결합연산을 하여 누적된 단일값을 반환하는 함수입니다.


4. 사용예

이제 사용법에 대해서 알아볼까요? 다음과 같은 정수형 배열이 있습니다.

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  1. 1. 정수형 배열의 각 요소에 1을 더한 배열을 얻고 싶다.
  2. 2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
  3. 3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다.
  4. 4. 정수형 배열 값들의 총합을 얻고 싶다.

map 함수를 사용하여 1,2번 문제의 답을 쉽게 얻을 수 있습니다.

// 1. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.  
arr.map { (x) -> Int in
    return x+1
}

// 2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
arr.map { (x) -> Int in
    return x*2
}

filter를 사용하여 3번을 해결해보겠습니다.

// 3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다. 
arr.filter { (x) -> Bool in
    return x%2 == 0
}  

당연히 4번은 reduce의 몫입니다.

// 4. 정수형 배열 값들의 총합을 얻고 싶다.
arr.reduce(0, combine: { (result, x) -> Int in
   return result + x
})

Trailing Closure 를 사용하여 다소 낯선 형태지만 코드가 너무 짧아 놀라셨죠?   

(Trailing Closure는 함수의 마지막 인자가 클로져(함수)일 경우, 클로져를 함수 호출부의 꼬리(trailing)에 표기하는 방식입니다. )

이러한 고차원 함수들을 복합적으로 사용하여 프로그래밍하는 방식을 함수형 프로그래밍이라고 합니다. 

함수형 프로그래밍은 짧고 명료한 코드를 작성할 수 있게하고, 버그의 가능성 또한 훨씬 줄인다고 합니다.

낯선 함수형 프로그래밍 방식에 익숙해지려면 다소 시간이 걸리겠지요? ^^


5. Map 함수 구현하기

학습한 내용을 좀더 잘 이해하기 위해 직접 map 함수를 만들어 보겠습니다.

일반적인 방법으로 1,2번 문제를 구현하는 코드를 작성했습니다.

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

func pluseOne(arr:[Int]) -> [Int] {
    var result:[Int] = []
    for x in arr {
        result.append(x + 1)
    }
    return result
}

func multiplyTwo(arr:[Int]) -> [Int] {
    var result:[Int] = []
    for x in arr {
        result.append(x * 2)
    }
    return result
}  

두 함수는 연산하는 행위만 제외하고 거의 비슷합니다. 연산하는 행위를 함수로 묶어내면 일반화가 가능해보이네요.

func map(arr:[Int], transform:(Int->Int)) -> [Int] {
    var result:[Int] = []
    for x in arr {
        result.append(transform(x))
    }
    return result
}  

이제 제네릭을 사용하여 범용타입으로 대치하면

func map<T, U>(arr:[T], transform:(T->U)) -> [U] {
    var result:[U] = []
    for x in arr {
        result.append(transform(x))
    }
    return result
}

map 함수가 완성되었습니다. 우리는 글로벌 함수로 구현했지만 표준라이브러리에서는 array의 메소드로 제공한다는 점이 유일한 차이입니다.


5. Filter 함수 구현하기

Map 함수를 구현해보아서 Filter 함수 구현은 좀더 간단합니다.

// 일반적인 방법으로 구현하고
func filter(arr:[Int]) -> [Int] {
    var result:[Int] = []
    for x in arr {
        if x%2 == 0 {
            result.append(x)
        }
    }
    return result
}

// 함수로 공통 행위를 묶어낸 다음
func filter(arr:[Int], includeElement:(Int->Bool)) -> [Int] {
    var result:[Int] = []
    for x in arr {
        if includeElement(x) {
            result.append(x)
        }
    }
    return result
}


// 제네릭 적용
func filter<T>(arr:[T], includeElement:(T->Bool)) -> [T] {
    var result:[T] = []
    for x in arr {
        if includeElement(x) {
            result.append(x)
        }
    }
    return result
}


6. Reduce 함수 구현하기

단계적으로 구현해보니 고차원 함수 구현이 어렵지 않죠? 이제 reduce를 구현해 볼까요?

// 일반적으로 구현하고  
func reduce(arr:[Int]) -> Int {
    var result:Int = 0
    for x in arr {
        result = result + x
    }
    return result
}

// 초기값 할당과 누적하는 행위를 파라미터로 분리하고 
func reduce(arr:[Int], initial:Int, combine:(Int,Int)->Int) -> Int {
    var result:Int = initial
    for x in arr {
        result = combine(result, x)
    }
    return result
}

// 제네릭 적용 
func reduce<T, U>(arr:[T], initial:U, combine:(U, T)->U) -> U {
    var result:U = initial
    for x in arr {
        result = combine(result, x)
    }
    return result
}  

지금까지 Swift 표준라이브러리의 Map, Filter, Reduce에 대해 알아보았습니다.




'Swift' 카테고리의 다른 글

[swift2] Xcode7 beta2 Swift 언어 변경사항  (0) 2015.06.24
[swift] 문자열 Indexing과 Slicing  (0) 2015.06.23
[swift] Map, Filter, Reduce  (0) 2015.06.22
[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29

[swift2] New in Swift2.0

Swift 2015. 6. 19. 23:02


올 가을 Swift2.0 정식버전이 발표된다고 합니다. Xcode7 beta Release Notes를 통해 그 변경사항을 잠시 알아볼까요?
원문: Xcode7 beta Release Notes


Swift Language Features

1. 에러처리

에러를 throw, catch 그리고 관리하는 함수를 생성할 수 있습니다. file-not-found 에러 혹은 네트워크 타임아웃
같은 회복가능한 에러를 처리하고, 의도를 명확하게 드러낼 수 있습니다. Swift 에러 처리는 NSError와 상호동작합니다.

2.가용성 체크

선택된 deployement target 보다 더 신버전의 운영체제에서 지원하는 API를 호출하면
Swift는 컴파일 타임에 에러를 보고합니다. 런타임에 영속적으로 사용 불가능한 API를 검사하려면,
if 또는 guard 문에서 새롭게 추가된 available()조건을 사용하세요.

if #available(iOS 8.0, OSX 10.10, *) {
    // Use Handoff APIs when available.
    let activity = NSUserActivity(activityType:"com.example.ShoppingList.view")
     activity.becomeCurrent()
} else {
    // Fall back when Handoff APIs not available.
} 

3. @available() 속성

함수 선언부에 @available() 속성을 사용하여 가용성 정보를 명시할 수 있습니다.

// iOS 8.0 이상, OSX 10.10 이상, 다른 플랫폼의 모든 버전에서 사용가능합니다.
@available(iOS 8.0, OSX 10.10, *)
func startUserActivity() -> NSUserActivity {
    ...
}

4. 프로토콜 Extensions

프로토콜 타입을 위한 Extensions을 작성할 수 있습니다. 특정 프로토콜를 따르는 어떠한 타입에도 메서드와 속성을 추가
할 수 있게되어 더 많은 코드를 재사용 할수 있습니다. 유연한 인터페이스 원칙에 따라 호출 측의 dot 메서드 문법을 더 자연스럽게 하고,
제네릭 코드 정의를 더 단순하게 합니다.

5. 프로토콜 디폴트 구현

프로토콜은 프로토콜 extension 명세에 따른 기본 구현체를 가질 수 있습니다. mixin, trait 같은 패턴구현이 가능해집니다.

func xyz() throws {

6. defer 구문

defer 구문은 scope를 빠져나올때 클린업 코드를 실행합니다. 특히 새 에러처리 모델과 결합하여 사용하면 유용합니다.

let f = fopen("x.txt", "r")
defer { fclose(f) } 
try foo(f)      // 에러가 발생하면 f가 닫힘

let f2 = fopen("y.txt", "r") 
defer { fclose(f2) }
try bar(f, f2)   // 에러가 발생하면 f2가 닫히고, f가 닫힘
} 
// 정상흐름에선 f2가 닫히고, 그다음 f가 닫힘

7. guard 구문

scope를 조기에 빠져나갈 수 있게합니다.
(의도하지 않는 조건에선 조기에 scope를 빠져나가게 하여, 다음 코드를 신경쓰지 않도록 해준다는 내용입니다.)

guard let z = bar() else { return }
use(z)

scope를 빠져나가기 위해 else 블럭이 필요합니다. (예를 들면 return,throw,break,continue와 함께)

8. 패턴매칭 향상

switch/case 패턴매칭이 if/case, while/case, guard/case, 그리고 for-in/case를 포함하여
많은 새로운 조건 제어구문에서 사용가능합니다. for/in 문은 where 절을 가질수 있습니다.

9. do 구문

do 구문을 사용하여 새로운 scope를 정의할수 있습니다.

do {
    //new scope
 do {
     //another scope
 }
}

10. 테스트성 향상

Swift2.0 프레임워크와 앱들의 테스트(코드)들은 내부 루틴을 public 으로 만들지 않고 작성되었습니다.
테스트 소스코드에서 모든 것을 public으로 만들어 내부 루틴을 사용할 수 있게 만들어주는 다음 구문을 사용하세요.

@testable import { ModuleName }  

앱과 프레임워크 타겟은 "Enable Testability" 빌드설정을 활성화하고 컴파일 해야합니다.
Enable Testability 설정은 앱과 프레임워크의 모든 내부 심볼들까지 exporting 할지 결정하여 최적화를 막을 수 있기때문에
Debug configuration 에서만 사용되야 합니다.

11. C 함수포인터 Native 지원

함수포인터를 인자로 갖는 C함수는 clusers 또는 글로벌 함수를 사용하여 호출할 수 있습니다.

// 표준C qsort
var array = [3, 14, 15, 9, 2, 6, 5]
qsort(&array, array.count, sizeofValue(array[0])) { a, b in
    return Int32(UnsafePointer<Int>(a).memory - UnsafePointer<Int>(b).memory)
}
print(array)

12. 진단기능 향상

var 대신 let 사용을 권장하기 위한 새로운 warning과 unused variables warning이 추가되었습니다. invalid mutation warning 진다는 보다 더 정밀해졌으며, unreachable switch cases 절도 warning을
발생시킵니다.

13.enum 다중 제네릭 연관값을 지원

enum Either<T, U> {
    case Left(T), Right(U)
}

13. enum 프린팅 구문 - enum case와 playload 출력 (가능한 경우에만)

@objc enum과 다중 payload를 갖는 enum 에는 적용되지 않습니다.

14. 제네릭 타입의 public extension 허용

    public extension Array { … }

15. 제네릭 클래스로부터 non 제네릭 클래스 상속이 가능

16. swift 문자열 접합연산 - 컴파일 타임에 최적화를 보장

17. 실패 가능한 Convenience 생성자에서 self.init 호출전에 nil 리턴 허용

여전히 Designated 생성자들은 nil을 리턴하기 전에 모든 저장속성들을 초기화 해야합니다.

18. 중첩함수는 재귀적으로 자기자신과 다른 중첩함수들을 참조 가능

19. if 문에 라벨링 가능

라벨이 있는 break 문은 매칭되는 if문 밖으로 jump 하는데 사용됩니다.
라벨이 없는 break 문은 if문을 종료시키지 못하지만, 루프문과 스위치 구문은 종료시킵니다.

20. x? 패턴

.Some(x) 와 동일한 의미로 optional에 붙어 x? 패턴을 사용할수 있습니다.

21. 인스턴스 멤버에 @objc 키워드가 붙는 것을 방지해주는 @nonobjc 속성 추가

(Swift 코드를 Objective-C에 노출시킬 때, @nonobjc 속성이 붙은 속성과 메소드는 노출되지 않습니다.)

22. 표준 라이브러리에 readLine() 추가





'Swift' 카테고리의 다른 글

[swift] 문자열 Indexing과 Slicing  (0) 2015.06.23
[swift] Map, Filter, Reduce  (0) 2015.06.22
[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29
[swift] Swift 기초 - 3  (1) 2014.10.29

[swift] Apple Swift Blog

Swift 2015. 6. 10. 01:00


애플 WWDC 2015 에서 Swift2 공개와 올해 말 오픈소스화 발표가 있었습니다.

애플 swift 블로그를 방문하니 역시나 새롭게 단장되어있네요.   Swift2 의 변화를 훑어보기 위해 대문 페이지를 번역해보았습니다. 

[출처]: Apple Swift blog




Swift.  안전하고, 빠르며, 인터렉티브한 

최신의 프로그래밍 언어


Swift는 iOS, OS X, watchOS를 위한 강력하고 직관적인 프로그래밍 언어입니다. 

Swift 코드를 작성하는 것은 인터렉티브하고, 재미있습니다. 문법은 간결하지만 표현력이 풍부하며,  

앱은 번개처럼 빠르게 실행됩니다.  Swift는 당신의 다음 프로젝트 - 또는 현재 앱에도 사용할수 있도록 

준비되어있습니다. - 왜냐하면  Swift는 Objective-C 와 함께 동작하기 때문이지요.




Swift 2 소개

Swift 는 밑바닥부터 개선되었습니다. 릴리즈, 디버그 빌드를 막론하고 더 빠른  코드를 생성합니다.

Swift 컴파일러는 var 대신 let를 사용하라는 새로운 Fix-it 제안을 제공해주는데도 더 빠릅니다.

주석에 마크다운를 사용하여 rich text와 내장 이미지를 추가할 수 있습니다.

새로운 assistant는 header-like 뷰에서 Swift API를 보여줍니다.  그리고 Cocoa 프레임워크와 Objective-C 

코드를 더 풍부하고 훨씬 안정성있게 해주는 새로운 문법들이 개선되었습니다. 




에러처리 모델(Error Handling Model)

진보된 에러처리 모델은 에러를 캐치하고 던지는 명확하고, 풍부한 문법을 제공합니다. 

쉽게 커스텀 에러타입을 정의하고 명확하고 의미있는 이름으로 에러 케이스를 기술할  수 있습니다. 

새로운 에러모델은  NSError 클래스와 Cocoa 프레임워크와 잘 조화되도록 설계되었습니다.


에러 처리 코드의 예입니다.


개선된 문법(Syntax improvements)

새로운 문법 특징들은 언어를 일관성 있게 개선하면서도 더 풍부한 코드를 

작성할 수 있게 합니다. SDK는 Objective-C에, Swift를 더 명확하고 안전하게 하는 

 generics와 nullability 어노테이션을 도입했습니다. 

Swift 2.0에 개선된 몇가지 예입니다. 

- do, guard, defer, repeat를 사용한 강력한 제어흐름 

- 함수와 메서드  키워드 네이밍 규칙 통합

- 프로토콜 extension 과 디폴트 구현 

- if문과 for 루프에서의 확장된 패턴매칭 

Xcode7는 애플리케이션과 playground 코드를 최신 Swift2.0 의 개선된 문법으로 

변환해주는 강력한 마이그레이터를 포함합니다.


가용성(Availability)

최신의 특징, 문서, API 변경사항을 접근하기 위해서 최신의 SDK 사용해야 했지만

때때로 앱이 이전 버전의 OS 에서 실행되야할 때가 있습니다.

Swift2.0 가용성 체킹 기능을 가지고 있습니다. 기능은 target OS version  

가장 적합한 앱을 쉽게 빌드할 있게 해줍니다. 컴파일러는  최소 OS 타겟에서는 지원하지 않는

새로운 api 사용하려 할때 에러를 발생시킵니다

그리고 특정 버전의 OS에서만 실행되도록 버전분기를 해주는 코드블럭를 

랩핑하는 키워드들 제공합니다.


오픈소스(Open Source)

올해 Swift 오픈소스로 릴리즈 됩니다

우아하고, 강력하고 안전한 Swift 결정체가 산업 전반에 소개될 수 있는

기회를 제공합니다. 우리가 함께 개발하는 것을 상상하니 흥미진진합니다. 


모던(Modern)

Swift 수십년의 애플 플랫폼 개발 경험과 가장 최신의 프로그래밍 언어 연구결과의 집합체입니다.

Objective-C에서 차용한 named 파라미터는 명확한 문법으로 표현하되, 가독성과 유지보수를 쉽게합니다.

타입추론은 모듈이 헤더를 제거하고 네임스페이스를 제공하면서, 코드를 더 깔끔하게 하고, 그리고 실수를 덜 

유발하게 합니다. 메모리는 자동으로 관리되며, 세미콜론 조차 타이핑 할 필요 없습니다.

모든 이런 현대적인 아이디어들이 언어를 쉽고, 사용하기 재미있게 만들어줍니다. 



Swift는 코드를 더 풍부하게 하는 다른 많은 특징들을 가지고 있습니다. 

- 클로저는 함수 포인터와 통합되었습니다.

- 튜플과 다중 리턴값 

- 제네릭

- 빠르고 간결한 range와 collection iteration

- 메서드, extension, 프로토콜을 지원하는 구조체

- map, filter와 같은 함수형 프로그래밍 패턴

- try/catch/throw 를 사용하는 네이티브 에러처리 



 

   인터렉티브 플레이그라운드 (Interactive Playgrounds)

Playgrounds 는 Swift 코드 작성을 믿을수 없을 정도로 단순하고 재미있게 합니다. 

한 줄의 코드 타이핑 결과가 즉시 나타납니다. 코드 오른편에서 빠르게 결과를 훑어보거나, 

그 결과를 바로 아래에 고정할수 있습니다. 결과뷰는 그래픽, 결과리스트, 시간의 흐름에 따른 그래프로 

볼수 있습니다. Time Assistant를 열어 관련된 뷰들이 변화하고 애니메이션 되는것을 볼수 있습니다. 

새로운 UI코드를 실험하고, 코딩하면서 애니메이션 되는 SpriteKit Scene을 실행해보기 좋습니다. 

Playground에서 코드작성을 완료했을 때, 단순히 프로젝트로 코드를 옮기세요. 

Xcode7의 새로운 기능중 하나로  Playgrounds는 이탤릭 볼드, 불릿 리스트, 내장이미지와 링크 등을 가진 

rich text를 사용하여 주석을 달수 있습니다.  심지어 리소스도 내장할수 있어서 코드는 단순하게 하면서도 

Swift 소스 코드를 믿을수 없을 정도로 강력하고 매력적이게 할수 있습니다.


아름다운 텍스트와 인터렉티브한 코드를 사용하여 프로그래밍 커리큘럼을 공유할 수 있습니다.

새로운 알고리즘을 설계하고, 단계마다 그 결과를 볼수 있습니다.

테스트 슈트를 만들기 전에 새로운 테스트를 작성하고 동작하는지 검증할 수 있습니다.

Swift 코딩 기술을 연마하기 위해 새로운 API를 실험해볼 수 있습니다.

실험결과를 Playgrounds 안에서 동작하는 코드예제를 갖는 문서로 만들 수 있습니다.


Read-Eval-Print-Loop(REPL)

Xcode의 LLDB 디버깅 컨솔은 Swift 언어의 인터렉티브한 버전을 포함하고 있습니다.

스위프트 문법을, 실행되고 있는 앱과 상호작용하고 evaluate하는데 사용해보세요.

또 스크립트 같은 환경에서는 어떻게 동작하는지 새로운 코드를 작성해보세요.

Xcode 컨솔과 터미널에서 사용가능합니다.


안정성을 위한 설계(Designed for Safety)

Swift는  불안전한 코드들을 제거했습니다. 변수는 사용하기 전에 초기화 되어야 하고, 

배열과 정수타입은 오버플로그 검사를 하며 메모리는 자동으로 관리됩니다. 

문법은 의도를 잘 드러내도록 튜닝되었습니다.

예를 들어 단순하게 3문자 키워드를 사용하여 변수(var) , 상수(let)를 선언합니다. 

또 다른 사례로, 디폴트로 Swift 객체는 nil이 될 수 없습니다. 

Swift 컴파일러는  컴파일 타임 에러로 객체가 nil이 되는 것을 예방합니다. 

이것이 코드작성을 훨씬 명확하게 하고, 안전하게 합니다. 그리고 당신 앱의 수많은 종류의 

런타임 크래시들을 예방해줍니다. 그러나 nil이 유효하고 적절한 값일 경우가 있습니다. 

이런 상황에서는 optional이라고 알려진 혁신적인 특징을 사용합니다.

optional은 nil을 포함할 수 있으며, Swift 문법은 당신이 이해할 수 있고, 안전하게 처리할수 있도록

 ?구문을 사용하도록 강제합니다. 


빠르고 강력하다(Fast and Powerful)

초기 컨셉부터 Swift는 속도를 염두해두고 개발되었습니다. 고성능 LLVM 컴파일러를 사용하여, Swift 코드는 

대부분의 최신 하드웨어에 최적화된 네이티브 코드로 변환됩니다. 문법과 표준라이브러리는 코드가 가장 잘 실행되도록

튜닝되었습니다.

Swift는 C와 Objective-C 언어들을 계승합니다.  로우레벨 기본형 타입들과, 제어흐름, 연산자들을 포함하고 있습니다.

클래스, 프로토콜, 제네릭과 같은 객체지향 특징들은 Cocoa, Cocoa Touch 개발자들이 필요로 하는 성능과 힘을 제공합니다.


Objective-C 상호운영성 (Interoperability) 

Swift를 사용하여 전체 애플리케이션을 작성할 수 있습니다.

현재 앱의 새로운 기능 구현을 위해서 Swift코드를 사용할 수도 있습니다.

Swift 코드는 동일 프로젝트에 있는 Objective-C 파일들과  함께 존재하며, 전체 Objective-C API 에 접근하며 쉽게 적용할

수 있습니다.







'Swift' 카테고리의 다른 글

[swift] Map, Filter, Reduce  (0) 2015.06.22
[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29
[swift] Swift 기초 - 3  (1) 2014.10.29
[swift] Swift 기초 - 2  (0) 2014.10.29

[swift] Swift 기초 - 4

Swift 2014. 10. 29. 13:32



4번의 Swift 스터디 자료중 마지막 자료입니다. 





지난 시간에 학습한 내용을 간단히 정리해볼까요?

구조체는 member-wise 생성자를 디폴트로 가지며, pass by value 즉 복사되어 함수에 전달됩니다. 

클래스는 designated / convenience 생상자를 갖으며, pass by reference 즉 참조로 함수에 전달됩니다.

구조체,클래스의 속성으로는 저장속성, 계산속성이 있으며, 속성 옵저버에 대해 간략히 배웠습니다.

서브스크립트를 구현하면 클래스의 데이터를 인데스 문법을 사용하여 접근할 수 있습니다. 

데이터 초기화 역할을 하는 생성자, 그리고 소멸자는 사용한 자원을 해제하는 역할을 담당합니다. 




이번 시간에는...

사용한 객체의 참조 횟수를 추적해서, 필요없을 때 자동으로 해제해주는 ARC, 

상속에 의존하지 않고 클래스를 확장할 수 있는 Extension,

Java 언어에서 인터페이스 역할을 하는 Protocol, 

타입을 인자로 하여 범용타입의 자료구조를 정의할 수 있게 해주는 Generic에 대해 알아보겠습니다.






iOS5.0 부터 Objective-C에서 메모리 관리를 획기적으로 쉽게 만든 ARC는 Swift에서 기본으로 지원합니다.

Automatic Reference Counting 이라는 이름 그대로, 객체마다 참조되는 횟수를 추적하여 

참조횟수가 0이 될때, 자동으로 메모리에서 해제시켜주는 기법입니다. 

ARC의 동작과정을 알아보기 위해,  생성,해제 시점에 출력하는 Person 클래스를 정의하였습니다.




위 할당문 사이사이에 코드 출력문을 삽입하고, 어떤 결과가 나오는지 지켜볼까요?




Person 객체를 참조하는 변수 ref1, ref2, ref3 모두 nil이 되는 순간, 

즉 레퍼런스 카운트가 0이 되는 순간 자동으로 객체가 해제되는 것을 볼 수 있습니다.





사람(Person)이 입주할 수 있는 아파트(Apartment) 클래스를 정의하였습니다. 




위 코드를 실행하면 다음 결과를 출력합니다.



Person 객체의 초기화는 이루어졌지만, 해제가 되지 않았습니다. 무엇 때문일까요? 

네. Apartment 객체의 tenant 속성이 Person을 참조하고 있고,

Person의 home 속성이 Apartment을 참조하고 있기 때문입니다.

이렇게 서로 참조하는 상황을 순환참조(reference cycle)라고 합니다. 

순환참조가 이루어지면 메모리 해제되지 않고 남아있는  메모리 누수가 발생합니다.

Swift에서는 순환참조를 방지하기 위해 2가지 키워드를 제공합니다. 




weak 키워드를 사용하면, 참조횟수를  1증가시키지 않고, 약한참조로 동작합니다. 

참조하는 객체가 메모리에서 해제되면 weak 참조변수는 자동으로 nil로 초기화되어 안정성을 보장합니다.

이런 특성 때문에 optional 타입변수만 weak 참조변수가 될수 있습니다. 



weak 참조는 참조 카운트를 증가시키지 않기 때문에, 

renters["Elsvette"] = nil 실행과 동시에 Person 객체가 해제되고, 

apts[507] = nil 의 의해 Apartment 객체가 해제됩니다.





순환참조를 방지하는 또다른 키워드는  unowned Reference 입니다. 

신용카드(CreditCard)를 소유하는 고객(Customer) 클래스를 정의하였습니다.





신용카드(CreditCard) 클래스는  카드를 소유하는 고객(Customer)을 unowned 로 참조합니다. 

unowned 참조는 말그대로 소유권을 갖지않는 참조로 역시 참조 카운트를 증가시키지 않습니다. 

unowned 참조변수는 nil로 초기화되지 않으며, optional 타입에 사용할 수 없습니다. 

unowned 참조는 보통 생명주기가 타 클래스에 의존적인 클래스에 주로 사용됩니다.

신용카드는 소유주인 고객이 존재하는 동안만 의미를 갖는다는 것을 생각해보세요.




코드 실행결과를 볼까요?



고객(Customer) 객체가 해제되고 신용카드 객체도 해제되어 순환참조 문제가 발생하지 않습니다.


정리하면, 모델링 하는 대상 클래스들의 의존성 여부와 optional  가능유무에 따라 

weak와 unowned 를 구분하여 사용하면 되겠습니다! 





자바에서는 인터페이스, Objective-C, Swift에서는 프토토콜 이라고 합니다. 

프로토콜의 메소드는 디폴트로 required로 동작합니다. 

Thing를 상속하는 Boards 클래스는 Pullable 프로토콜 메소드 pull()을 

구현하지 않았기 때문에 컴파일 에러가 발생합니다. 





이렇게 pull() 메소드를 구현하면 컴파일 에러가 발생하지 않습니다.




인스턴스가 프로토콜을 따르는지 유무를 알기 위해, is, as?, as! 연산자를 사용할 수 있습니다.

is는 특정 프로토콜을 따르는 경우 true,  그렇지 않으면 false를 반환합니다.

as?는 특정 프로토콜을 따르면 해당 타입으로 변환해주고, 그렇지 않으면 nil을 반환합니다.

as!는 특정 프로토콜로 강제 형변환을 시도하며, 실패할 경우 런타임 에러가 발생합니다. 






Extension은 Objective-C에서 카테고리와 유사합니다.  

기존에 존재하는 클래스를 변경하지 않고, (기능을) 확장할 수 있습니다.

코드의 재사용성을 획기적으로 높일 수 있는 Swift 언어의 장점 중 하나입니다.

Double 클래스를 확장하는 Extension을  만들고, 거리단위를 환산하는 메소드를 정의했습니다.





Extension 에 정의한 메소드를 활용하면, 짧고 명료한 방법으로

다양한 단위의 Double 형 부동소수값을  미터 단위로 변환할 수 있습니다.





역시 정수형 횟수만큼 특정 task 함수를 반복하는 코드를 쉽게 정의할수 있습니다.





정수형에 제곱을 구하는 Extension 메소드입니다.






Extension은 서브스크립트와도 조합하여 사용할 수 있습니다. 

index에 해당하는 십진수의 자리 수를 구하는  subscript를 정의하였습니다.





문자의 자음, 모음 유무를 출력하는 Extension 메소드를 정의하였습니다.





다음과 같은 결과를 출력합니다.






제네릭은 컬렉션, 구조체, 클래스에 타입을 매개변수화하여 전달할 수 있는 기능입니다. 

만일 제네릭이 없다면, 스택 데이터 구조를 정의할 때, 정수형, 문자형, 부동소수형 등 모든 타입에 대해

스택 데이터 구조를 각각 정의해야 할 것입니다.

제네릭은 타입에 대한 유연성과 안정성을 제공해주는 유용한 기능입니다.





앞서 정의한 정수형 스택에서, 타입T를 매개변수화하여 제네릭으로 정의하였습니다.





동일한 코드로, 정수형, 문자열 등 다양한 타입의 스택을 사용할 수 있습니다.



4번의 스터디를 통해 간략하게 나마 Swift 언어에 대해 훑어보았습니다.  

Swift 언어는 지금도 시시각각으로 변화하고 있으며 발전하고 있습니다. 

혹자는 어차피 바뀔건데 왜 벌써 공부해 ?라고 할수도 있지만, 무엇이든지 변화되는 과정을

지켜보는 것은 즐겁습니다. 그 변화 과정속에서 혹시 남들이 보지못하는 혜안을 가지게 될지 모르잖아요?






'Swift' 카테고리의 다른 글

[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29
[swift] Swift 기초 - 3  (1) 2014.10.29
[swift] Swift 기초 - 2  (0) 2014.10.29
[swift] Swift 기초 - 1  (1) 2014.10.29

[swift] Swift 기초 - 3

Swift 2014. 10. 29. 13:32



Swift 스터디 세 번째 시간입니다. 





이번 시간에는 

구조체와 클래스, 속성과 메서드,

서브스크립트와 상속, 생성자와 소멸자에 대해서 알아보겠습니다. 

(생성자와 소멸자라고 번역하는게 맞는지 모르겠지만, 당분간 이 용어를 사용하겠습니다.)





클래스는 class 키워드를 사용하여 정의합니다.

신기하게도 Swift 에서는 클래스를 정의하면, 디폴트로 어떠한 클래스(NSObject와 같은...) 상속하지 않습니다.





콜론: 을 사용하여 클래스 상속을 표현합니다.

운송수단이라는 클래스를 상속받아 자전거 클래스를 정의했습니다.





 저장속성은 단순히 데이터를 담는 속성입니다.

Vehicle(운송수단) 클래스는 바퀴의 개수 저장속성과 승객수 저장속성을 가지고 있습니다.





계산속성은 저장속성을 바탕으로 계산되는 속성입니다. 

자동차 클래스의 스피드 속성은 최저속도, 가속도 속성을 바탕으로 

매번 계산되는 계산속성의 좋은 예입니다.

계산속성은 set, get 키워드를 사용하여 setter, getter 를 정의합니다.





운송수단 클래스에 바퀴수 저장속도를 사용하여 description 계산속성을 정의했습니다.

계산속성은 접근할 때마다 매번 가공되는(계산되는) 속성이라 할 수 있습니다.

계산속성은 선언시 반드시 타입을 정의해야 합니다.





운송수단 클래스를 사용하는 예제입니다.

멤버연산자(.dot)를 사용하여 저장속성과 계산속성에 접근합니다.





클래스는 디폴트로 init() 생성자 함수를 가지고 있습니다.

서브클래스는 생성자를 재정의(override) 할 수 있습니다.

운송수단 클래스를 상속하는 자전거 클래스는 

init 생성자를 재정의하여 바퀴수를 2로 초기화합니다





계산속성은 메소드 처럼 서브클래스에서 재정의가 가능합니다.

운송수단 클래스에서 정의한 description 계산속성을

자동차 서브클래스에서 재정의하여 사용했습니다.





재정의한 계산속성을 사용하는 예제입니다.





Swift에서는 속성 옵저버를 제공하여 쉽게 옵저버 패턴을 구현할 수 있습니다.

계산속성을 정의할 때, willSet, didSet 블럭을 지정합니다.

속성이 변경되기 전, 변경된 후에 수행할 작업을 명시하면 됩니다.

willSet 블럭에서는 새롭게 변경될 속성 값을 newValue 변수로 접근할 수 있으며,

didSet 블럭에서는 변경되기 전 속성값을 oldValue 변수로 접근할 수 있습니다.





클래스의 메서드는 함수와 동일하게 func 키워드를 사용하여 클래스 내부에 정의합니다.





Swift에서 함수는 독특하게도, 내부 파라미터 이름과 외부 파라미터 이름을 갖습니다.

내부 파라미터 이름은 함수의 몸체 즉 내부에서 사용하는 이름입니다.

외부 파라미터 이름은 함수를 사용(호출)하는 곳에서 사용하는 이름입니다.





파라미터 이름 앞에 #을 사용하면 컴파일러는 

내부 파라미터 이름과 외부 파라미터 이름을 동일하게 다룹니다.

각각 이름을 명시해야하는 번거로움을 피할 수 있습니다.





구조체 또한 클래스와 같이 속성과 메서드를 가질 수 있습니다.





구조체도 클래스처럼 생성자를 가지고 있습니다.  

다만 구조체의 디폴트 생성자는, 파라미터 이름과 포함하고 있는 속성이름이 일치하는데,

member-wise 생성자라고 합니다.





구조체도 저장속성과 메서드를 정의할 수 있습니다.





이 시점에서 생기는 의문점!  

"구조체가 속성과 메서드를 가질수 있다면 클래스와 다른게 무엇인가?" 

핵심은 클래스는 참조로 전달되고, 구조체는 값이 복사 된다는 점입니다. 

Window 클래스의 frame 속성을 할당한 newFrame의 속성을 변경해도, window.frame은 수정되지 않지만,

setup 함수로 전달된 window의 frame은 변경사항이 반영되는 것을 알수 있습니다.





var, let 키워드의 규칙은 기본형 뿐만 아니라 클래스, 구조체에도 일관되게 적용됩니다. 

다만 조금의 차이는 있습니다.

let 으로 선언된 클래스 변수에는 다른 클래스 참조를 할당하면 컴파일 에러가 발생합니다.

let 으로 선언된 클래스 변수 자체가 상수가 됩니다. 





let으로 선언된 구조체는 멤버 속성값을 변경할 수 없습니다.

즉 구조체의 멤버 속성들이 상수가 됩니다.

클래스는 참조, 구조체는 값이 복사된다는 특성을 알면 쉽게 이해할 수 있습니다.





재미있는 특성중에 하나로, 구조체의 메소드 내부에서는 속성을 변경할 수 없습니다. 

속성을 수정하려면 명시적으로 mutating 키워드를 메서드 앞에 지정해야 합니다.





앞서 구조체는 member-wise 생성자를 갖는다고 이야기했습니다.





구조체도 커스텀 생성자를 정의할 수 있습니다. 

다만 모든 속성은 사용하기 전에 초기화 되어야 합니다. 

blue 속성을 초기화 하기 전에  validateColor() 함수를 호출하면

컴파일 에러가 발생합니다.




클래스의 모든 속성도 사용하기 전에 초기화 되어야 하는 것은 동일합니다.

모든 변수는 사용하기 전에 초기화되야 한다는 것은 Swift 언어의 공통된 규칙이지요.





기본 생성자 외에 편의상의 목적으로 조금씩 다른 여러 생성자를 정의할 수 있습니다. 

Swift에서는 이런 목적의 생성자에 친절하게도 convenience 키워드를 제공합니다. 

클래스 설계자의 의도대로 생성되도록 제공하는, 의도된(designated) 생성자 외에 

편의성을 위해 제공하는 생성자라 생각하면 됩니다.

보통 convenience 생성자는 다른 생성자를 다시 호출하는 체이닝(chaining)을 통해 구현됩니다.





소멸자는 클래스의 메모리가 해제되는 시점에 사용한 자원을 정리하는 역할을 수행하는 함수입니다.

파일을 열어 사용했다면, 소멸자에서 파일을 닫도록 명시해 의도치 않은 메모리 누수를 막을 수 있습니다.





필요치 않은 자원을 조기부터 낭비하는 것을 막기위해, 

실제 자원을 사용할때 메모리를 할당하는 레이지 로딩이라는 기법이 있습니다.

Swift에서는 속성에 lazy 키워드를 사용하여 레이지 로딩 기법을 제공합니다.

lazy로 선언된 MultiplayerManager 객체는 

실제로 객체가 최초로 사용 될때(multiplayerManager.addPlayer(player) 호출 시점에)  메모리에 할당됩니다.





서브스크립트는 구조체나 클래스의 속성을  인덱스를 제공하여 접근할 수 있는 인터페이스를 제공합니다.

 subscript 키워드를 사용하여, get, set 블럭을 정의합니다.

인덱스 형태로 접근하면 다루기 쉬운 특정 데이터 구조를 다룰때 편리한 문법입니다.

대표적으로 행렬을 예로 들수 있습니다.





구구단을 출력하기 위해 TimesTable 구조체에 서브스크립트를 정의하여 사용하는 예제입니다.

서브스크립트를 사용했을  때의 2가지 장점을 엿볼 수 있습니다.

첫째는 속성에 접근하는 단순화된 문법과 둘째는, 메모리 절약입니다.

3단을 출력하기 위한 모든 데이터를 저장 할 필요없이 

인덱스와 단수 변수만을 사용하여 메모리를 절약했습니다.





행렬 구조체에 서브스크립트를 정의하고 있습니다.





메소드를 정의하여 똑같이 구현할수 있는 기능이지만 

서브스크립트를 사용하면 쉽고 가독성이 좋습니다.







'Swift' 카테고리의 다른 글

[swift2] New in Swift2.0  (0) 2015.06.19
[swift] Apple Swift Blog  (0) 2015.06.10
[swift] Swift 기초 - 4  (0) 2014.10.29
[swift] Swift 기초 - 3  (1) 2014.10.29
[swift] Swift 기초 - 2  (0) 2014.10.29
[swift] Swift 기초 - 1  (1) 2014.10.29
  • 차바 2016.12.16 11:42 ADDR 수정/삭제 답글

    다른 swift 파일에 있는 클래스 내 메소드는 어떻게 호출해야 하나요?

    클래스이름.메소드() 로 하니 에러가 나네요..ㅠㅜ