[swift2.1] Array와 Dictionary 기초

Swift 2015. 12. 22. 00:43

Swift는 컬렉션 타입으로 Array, Dictionary, Set을 가지고 있습니다. 그중 Array와 Dictionary의 사용법을 간략히 알아보겠습니다.


Array

Array는 순서있는 아이템을 저장하는 컬렉션으로, Aarry에 저장되는 아이템들의 값은 중복을 허용합니다.


배열 생성

Array는 아이템의 타입(Int)을 제테릭 인자로 받아 선언할 수 있습니다.

var intArray = Array<Int>()

축약형식을 사용하면 좀더 간단하게 선언할 수 있습니다.

var ints = [Int]()

Array 리터럴을 사용에서 하나 이상의 요소를 갖는 배열을 선언해보겠습니다.

var shoppingList:[String] = ["Eggs", "Milk"]

할당되는 값에서 타입추론이 가능하기 때문에 [String] 타입을 명시하지 않아도 됩니다.

var shoppingList = ["Eggs", "Milk"]  

Array의 count 속성을 사용해서 배열 아이템의 개수를 알수 있습니다.

print("쇼핑리스트에는 \(shoppingList.count)개의 아이템이 들어있습니다.")

isEmpty 속성을 사용하면 배열이 비어있는지 유무를 알수 있습니다.

if shoppingList.isEmpty {
   print("쇼피리스트가 비어있습니다.")
} else {
   print("쇼피리스트가 비어있지 않습니다.")
}


아이템 추가

쇼핑리스트에 아이템을 추가해보겠습니다.

shoppingList.append("Flour")  
// ["Eggs", "Milk", "Flour"]

shoppingList += ["Baking Powder"]
// ["Eggs", "Milk", "Flour", "Baking Powder"]

이번에는 여러 아이템을 동시에 추가해보겠습니다.

shoppingList += ["Chocolate Spread", "Cheese", "Butter"]  
// ["Eggs", "Milk", "Flour", "Baking Powder", "Chocolate Spread", "Cheese", "Butter"]


아이템 삽입

이번에는 특정 인덱스에 아이템을 삽입해보겠습니다.

shoppingList.insert("Maple Syrup", atIndex: 0)
// ["Maple Syrup", "Eggs", "Milk", "Flour", "Baking Powder", "Chocolate Spread", "Cheese", "Butter"]


아이템 확인

인덱스를 사용해 배열 아이템에 접근할 수 있습니다.

var firstItem = shoppingList[0]  
// firstItem은 "Maple Syrup"

first 속성은 배열의 첫번째 아이템입니다.

if let first = shoppingList.first {
    // first는 "Maple Syrup"
}


아이템 변경

몇 가지 쇼핑품목을 변경해보겠습니다.

shoppingList[1] = "Six eggs"  
// ["Maple Syrup", "Six eggs", "Milk", "Flour", "Baking Powder", "Chocolate Spread", "Cheese", "Butter"]

range 연산자를 사용하면 특정범위의 아이템을 한번에 교체할 수 있습니다. 다음 구문은 4~6 인덱스 범위의 3개의 아이템을 ["Banana", "Apples"] 로 교체합니다.

shoppingList[4...6] = ["Bananas", "Apples"]
// ["Maple Syrup", "Six eggs", "Milk", "Flour", "Bananas", "Apples", "Butter"]

Array의 인덱스 범위를 초과하여 접근하면 Array index out of range 런타임 에러가 발생합니다.


아이템 삭제

배열의 첫번째와 마지막 아이템을 삭제해보겠습니다.

// 첫번째 아이템 삭제
let mapleSyrup = shoppingList.removeAtIndex(0)  

// 마지막 아이템 삭제 
let butter = shoppingList.removeLast()

// ["Six eggs", "Milk", "Flour", "Bananas", "Apples"]


배열 순회

가장 간단한 순회방법은 for 문을 사용하는 방법입니다.

for var i=0; i < shoppingList.count; i++ {
    print(shoppingList[i])
}

인덱스를 사용할 필요가 없다면, 좀더 심플한 for-in 문을 사용합니다.

for item in shoppingList {
    print(item)
}

나열자를 사용하면 좀더 심풀한 문법으로 순회가 가능합니다.

for (index, value) in shoppingList.enumerate() {
    print("index \(index) : \(value)")
}    

반대방향으로의 순회도 가능합니다.

for (index, value) in shoppingList.enumerate().reverse() {
    print("index \(index) : \(value)")
}

SequenceType 타입의 forEach 함수를 사용해서도 배열을 순회할수 있습니다. (Array는 SequenceType 프로토콜을 구현하고 있음)

// forEach 함수에 trailing 클로져로 반복해서 처리할 몸체를 전달합니다.  
shoppingList.forEach {
    print($0)
}

forEach 함수는 루프가 아니기 때문에 몸체안에서 continue 또는 break 문을 사용하여 도중에 중단하거나 건너뛸수 없습니다. return 문을 사용해서 루프를 건너뛸수는 있습니다.


초기값을 갖는 Array 생성

count, repeatedValue 인자를 받는 생성자를 사용하여 초기화된 배열을 생성할수 있습니다.

var threeDoubles = [Double](count: 3, repeatedValue: 0.0)

var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)    




Dictionary

Dictionary는 순서가 없는 키,값 쌍을 저장하는 자료구조입니다. Hashable 프로토콜을 구현하는 타입만이 Dictionary의 키로 사용될 수 있습니다. String, Int, Float, Double, Bool 및 연관값이 없는 enum 은 모두 디폴트로 Hashable 프로토콜을 구현하기 때문에 키로 사용될 수 있습니다.


Dictionary 생성

String 타입의 키와 값을 갖는 배열을 생성해보겠습니다.

var airports:Dictionary<String, String> = ["TYO":"Tokyo", "DUB":"Dublin"]

축약형태의 사용도 가능합니다.

var dictionary = [String:String]()

var airports = ["TYO":"Tokyo", "DUB":"Dublin"]


아이템의 개수

count 속성은 Dictionary에 저장된 아이템의 개수입니다.

print("airports 사전에는 \(airports.count) 개의 아이템을 포함하고 있습니다.")


키-값 설정

키-값 쌍을 추가해보겠습니다.

airports["LHR"] = "London"
airports["APL"] = "Apple International"


키-값 조회

Dictionary는 특정 키-값 쌍이 없을 수 있기 때문에 Optional 타입으로 반환됩니다.

airports["LHR"] = "London"  

print(airports["LHR"])
// Optional("London")

Dictionary에서 키값 조회는 안전하게 옵셔널 바인딩을 사용합니다.

if let airportName = airports["DUB"] {
    print("공합 이름은 \(airportName) 입니다.")
} else {
    print("해당 공항은 사전에 포함되어 있지 않습니다.")
}


키-값 변경

updateValue 함수를 사용하면 값을 변경하면서 이전 값을 반환해줍니다.

if let oldValue = airports.updateValue("Doublin International", forKey: "DUB") {
    print("DUB의 이전 값은 \(oldValue) 입니다.")
}


키-값 삭제

해당키의 값을 nil로 설정하여 삭제할 수 있습니다.

airports["APL"] = nil

print(airports["APL"])
// nil

삭제되는 값을 참조해야 한다면 removeValueForKey 함수를 사용합니다.

if let removedValue = airports.removeValueForKey("DUB") {
    print("삭제되는 값은 \(removedValue) 입니다.")
} else {    
    print("공항사전은 DUB 값을 포함하지 않습니다.")
}


Dictionary 순회

for-in 문을 사용해서 키,값을 순회할 수 있습니다.

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}

keys 속성을 통해 Dictionary의 모든 키 배열을 얻어올 수 있습니다.

for airportCode in airports.keys {
    print("공항 코드: \(airportCode)")
}

values 속성으로 모든 값의 배열을 얻어올 수 있습니다.

for airportName in airports.values {
    print("공항 이름: \(airportName)")
}



Dictionary의 복사동작

Dictionary가 다른 변수/상수에 할당 되거나, 함수의 인자로 전달될 때 어떤 일이 발생할까요?

Value 타입은 값이 복사되고, Reference 타입은 참조가 복사된다

위와 같이 Swift의 기본규칙을 이해하면 Dictionary의 복사동작을 쉽게 이해할 수 있습니다. Dictionary의 키와 값들은 value 타입이면 복사되고, 클래스와 같은 참조타입이면 참조변수가 복사됩니다.

var ages = [ "Peter":23, "Wei":35, "Anish":65, "Katya":19 ]

ages Dictionary 를 다른 변수에 할당해보겠습니다.

var copiedAges = ages

할당문을 만나는 순간 ages를 이루는 키,값들은 모두 복사됩니다.(String, Int는 value 타입.)

copiedAges["Peter"] = 24

print(copiedAges["Peter"]) // 24
print(ages["Peter"])       // 23

copiedAges Dictionary의 "Peter" 키의 값을 변경해도 ages의 동일 키의 값이 변경되지 않는 것을 통해 Dictionary가 복사되었음을 알 수 있습니다.



Array의 복사동작

Array의 동작도 Dictionary와 유사합니다. 할당 또는 함수의 인자로 전달되면 아이템이 value 타입인 경우 복사됩니다.

var a = [1, 2, 3]
var b = a
var c = a  

배열 a를 변수 b와 c에 할당합니다.

print(a[0]) // 1
print(b[0]) // 1
print(c[0]) // 1

배열 a의 첫번째 아이템을 변경해보겠습니다.

a[0] = 42

b, c의 첫번째 아이템들은 어떻게 되었을까요?

print(a[0]) // 42
print(b[0]) // 1
print(c[0]) // 1

b,c 모두 첫번째 아이템이 변경되지 않았습니다. 즉 할당과 동시에 복사가 이루어졌다는 것을 알수 있습니다. Swift 레퍼런스 문서에서는 성능 최적화의 일환으로 "Array의 크기를 변경하는 연산이 발생한 경우에만 복사가 이루어진다" 고 하는데, 문서가 업데이트 되지 않은 건지, 최적화 옵션에 따라 동작이 다른건지는 의문입니다.

다음 시간에는 Set에 대해 정리해보겠습니다.





[swift2.1] Swift 프로토콜 지향 프로그래밍 2

Swift 2015. 12. 9. 01:00



이 글은 아래의 자료를 참고하여 작성하였습니다.



지난 시간에 이어, 프로토콜 지향 프로그래밍에 또다른 예제를 살펴보겠습니다. 프로토콜과 구조체가 어떻게 클래스를 대체할 수 있는지 좀더 자세히 알아보기 위해, 도형 그리기를 목적으로 설계된 Renderer 를 소개합니다.



프로토콜 Renderer

  • Renderer 프로토콜은 그려지는 대상과 그리는 방법을 알고 있습니다. (어디에 어떻게 그릴까? 도화지와 물감)
  • Renderer는 특정 점으로 이동하고, 라인을 그리고, 호를 그리는 함수 인터페이스를 정의합니다.
protocol Renderer {
    func moveTo(p:CGPoint)
    func lineTo(p:CGPoint)
    func arcAt(center:CGPoint, radius:CGFloat, startAngle:CGFloat, endAngle:CGFloat)
}



프로토콜 Drawble

  • Drawable 프로토콜은 그릴 객체(대상)을 표현합니다. (무엇을 그릴까? 그림체의 데이터를 제공)
  • draw 함수는 renderer 프로토콜이 제공하는 인터페이스를 사용하여 도형의 정보를 제공합니다.
protocol Drawable {
    func draw(renderer:Renderer)
}
  • 이제 Drawable을 구현하는 도형을 정의합니다.



다각형

  • Polygon은 정점들의 배열 corners를 갖습니다.
  • draw 함수에서 가장 마지막 정점부터 이웃하는 정점으로 선을 그려 다각형을 그립니다.
struct Polygon: Drawable {

    func draw(renderer: Renderer) {
        renderer.moveTo(corners.last!)
        for p in corners {
            renderer.lineTo(p)
        }
    }
    var corners:[CGPoint] = []
}



  • Circle은 중점과 반지름을 정의합니다.
  • draw 함수에서 중점을 기준으로 0~360도 호를 그려 원을 그립니다.
let twoPi = CGFloat(M_PI * 2)

struct Circle: Drawable {

    func draw(renderer: Renderer) {
        renderer.arcAt(center, radius: radius, startAngle: 0.0, endAngle: twoPi)
    }
    var center:CGPoint
    var radius:CGFloat
}



다이어그램(Drawable 조합을 그릴수 있는 객체)

  • Diagram은 그릴수 있는 도형 리스트인 elements 를 갖습니다.
  • 도형리스트를 순회하며 도형들에게 draw 함수를 위임합니다.
struct Diagram:Drawable {
    func draw(renderer: Renderer) {
        for f in elements {
            f.draw(renderer)
        }
    }
    var elements:[Drawable] = []
}

지금까지 정의한 도형들을 실제로 그려보아야 겠지요? 첫번째로 컨솔에 단순히 좌표를 찍는 TestRenderer 를 정의해보겠습니다.



컨솔에 그리기

  • TestRenderer는 Renderer의 함수 인터페이스를 구현하며 단순히 파라미터들을 print 문으로 출력합니다.
struct TestRenderer:Renderer {
    func moveTo(p:CGPoint) { print("moveTo(\(p.x), \(p.y))") }

    func lineTo(p:CGPoint) { print("lineTo(\(p.x), \(p.y))") }

    func arcAt(center:CGPoint, radius:CGFloat, startAngle:CGFloat, endAngle:CGFloat) {
        print("arcAt:(\(center), radius: \(radius), startAngle: \(startAngle), endAngle:\(endAngle))")
    }
}

이름은 Renderer인데 컨솔에 좌표만을 출력하고 있으니 영 심심하죠? 이제 실제로 UI로 그리기 위한 Renderer를 구현해보겠습니다.



코어 그래픽으로 그리기

  • 재미있는 점은 CGContext에 그리는 CGContextRenderer를 구현하지 않고, 이미 존재하는 CGContext가 Renderer 프로토콜을 구현하도록 확장했다는 것입니다.
  • 이렇게 swift의 extension을 활용하면 Retroactive modeling 이 가능합니다.

    Retroactive modeling은 원래 코드를 수정하지 않고 타입을 확장할 수 있는 기능입니다.

extension CGContext:Renderer {

    func moveTo(p: CGPoint) {
        CGContextMoveToPoint(self, p.x, p.y)
    }
    func lineTo(p: CGPoint) {
        CGContextAddLineToPoint(self, p.x, p.y)
    }
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        let arc = CGPathCreateMutable()
        CGPathAddArc(arc, nil, center.x, center.y, radius, startAngle, endAngle, true)
        CGContextAddPath(self, arc)
        CGContextClosePath(self)
    }
}
  • extension의 또 한가지 재미있는 특징은 프로토콜의 디폴트 구현을 제공할 수 있다는 것입니다.

Renderer 프로토콜에 중점과 반지름만으로 원을 그리는 circleAt 함수를 추가해보겠습니다.

extension Renderer {
    func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
    }
}

프로토콜에 저장 속성을 정의할수 없다는 제약이 있지만, 글로벌 상수와 다른 멤버함수를 조합하여 디폴트 구현을 적용할수 있다는 것은 큰 장점이라 할수 있겠죠? 공통의 디폴트 구현을 제공하고, 특정 함수들만 프로토콜 구현체에서 제공하는 방식으로 코드량을 줄일 수 있기 때문입니다.



Playground에서 어떻게 그려지는지 볼까요?

  • 원과 삼각형을 정의합니다.
var circle = Circle(center: CGPoint(x: 187.5, y: 333.5), radius: 93.75)

var triangle = Polygon(corners: [
        CGPoint(x: 187.5,  y: 427.25),
        CGPoint(x: 268.69, y: 286.625),
        CGPoint(x: 106.31, y: 286.625)
    ])
  • RendererView를 정의하고, circle과 triangle을 Diagram으로 그룹핑합니다.
  • drawRect 함수에서 얻은 CGContext는 Renderer 프로토콜을 구현하기 때문에, draw 함수에 전달할 수 있습니다.
class RendererView:UIView {
    var diagram = Diagram(elements: [circle, triangle])

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.blackColor()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.backgroundColor = UIColor.blackColor()
    }
    override func drawRect(rect: CGRect) {
        let ctx = UIGraphicsGetCurrentContext()!

        UIColor.magentaColor().setStroke()
        CGContextSetLineWidth(ctx, 2)

        diagram.draw(ctx)

        CGContextDrawPath(ctx, CGPathDrawingMode.FillStroke)
    }
}




  • 자! 이제 결과를 볼까요?
import UIKit
import XCPlayground

...

var rendererView = RendererView(frame: CGRectMake(0, 0, 320, 568))

// deprecated
// XCPShowView("rendererView", view: rendererView)

XCPlaygroundPage.currentPage.liveView = rendererView




프로토콜 지향 프로그래밍에 대한 감이 조금 오셨나요? 그렇지 않았더라도 사고의 전환을 환기시키는 예제라는 생각이 드네요!


ProtocolOriented2.playground.zip





[swift2] 키워드로 보는 Swift

Swift 2015. 11. 30. 21:33

Swift 에서 사용하는 예약어를 마인드맵으로 그려보았습니다. 예약어들을 보고 얼마나 알고 있고, 또 설명할 수 있는지 시험해보세요.

예약어 목록은 The Swift Programming Language 문서를 참고하였습니다.





선언에 사용되는 키워드

  • import : 모듈(프레임워크)을 로드하여, 현재 파일에서 심볼을 접근할 수 있게함.
// UIKit 전체모듈 로드
import UIKit    

// UIKit 모듈의 NSText 서브모듈만 로드
import UIKit.NSText
  • class : 클래스 선언
  • protocol : 프로토콜 선언
  • extension : 클래스 확장
  • typealias : 기존타입에 별칭을 부여합니다.
 typealias AudioSample = UInt16
  • enum : 열거형 선언
  • struct 구조체 선언
  • init : 생성자
  • deinit : 소멸자
  • func : 함수 정의
  • var : 변수 정의해 사용하는 키워드
  • let : 상수 정의해 사용하는 키워드
  • static : 해당 타입의 모든 인스턴스가 공유하는 type 속성, 메소드 정의를 위한 키워드

    value(struct, enum) 타입에는 static, 클래스 타입에는 class 키워드를 사용합니다. 클래스 타입의 저장속성 정의에서는 class 키워드를 지원하지 않습니다.

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 10
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 10
    }
}
class SomeClass {
    // class stored properties not yet supported in classes
    // static var storedTypeProperty = "Some value."
    class var computedTypeProperty:Int {
        return 10
    }
}
  • subscript : 클래스, 구조체, 열거형의 속성을 인덱스 문법으로 접근하기 위한 인터페이스.
  • internal
  • public
  • private
  • inout : 함수 내부에서 변경된 인자값이 원래 변수값에 반영되야 할경우 사용하는 키워드

func swapTwoInts(inout a:Int, inout b:Int) {
    let tmp = a
    a = b
    b = tmp
}

var a = 5
var b = 10
swapTwoInts(&a, b: &b)

// a = 10, b = 5  
  • operator


문장에 사용되는 키워드

  • switch
  • case
  • break
  • fallthrough : 코드 제어흐름을 다음(next) case 문으로 이동.
let ch = "a"
switch ch {
    case "a":
        print("a", terminator: " ")
        fallthrough
    case "b":
        print("b", terminator: " ")
    default:
        print("unknown")
}
// a b 를 출력함 
  • default
  • continue
  • if
  • else
  • where
  • for
  • in
  • do
  • while
  • return
  • repeat : while 문과 조합하여 1번이상의 반복문을 수행. (기존의 do-while문)
var i = 0
repeat {
    i++
} while i < 10  
  • defer : 실행흐름이 현재 코드블럭을 떠나기 전에 특정 문장집합을 실행하고 싶을 때 사용.
if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // 라인 처리 
        }
        // 블럭을 빠져나가기 전 close(file)를 호출함.
    }  
  • guard : 에러조건이 발생하여 메서드를 조기 종료하고 에러를 보고하기 위해 사용함.
func vend(itemNamed name: String) throws {
        // 에러조건에서 미리 메소드를 종료하고, 에러를 보고
        guard var item = inventory[name] else {
            throw VendingMachineError.InvalidSelection
        }

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

        // 정상흐름의 로직 처리
        // ...
}


표현식과 타입

  • is
  • as
  • dynamicType : 인스턴스의 타입 (런타임 시점에 타입을 반환함)
var car = Car()

// 인스턴스의 타입
print(car.dynamicType)
// Car  

// self를 사용하여 타입을 알수도 있음.
print(Car.self)
  • super
  • self
  • Self : 프로토콜 선언에서 사용되며, 해당 프로토콜을 구현한 구현체의 타입을 의미함
protocol Drawable {
    func draw(drawable:Self)
    // func draw(drawable:Drawable)
}

// Drawable 프토토콜을 구현한 Circle 구조체에서 Self = Circle.
struct Circle : Drawable {
    var radus = 10
    func draw(drawable:Circle) {
        print("radus: \(self.radus)")
    }
}
  • __COLUMN__ : 시작열 번호를 나타내는 리터럴
  • __FILE__ : 파일의 이름을 나타내는 리터럴
  • __FUNCTION__ : 선언부의 이름을 나타내는 리터럴.
  • __LINE__ : 시작라인 번호를 나타내는 리터럴

  • try

  • throws
  • rethrows
  • catch


특정문맥 예약어

  • willSet
  • didSet
  • get
  • set
  • left
  • right
  • mutating
  • nonmutating
  • none
  • prefix : 피연산자의 앞에 붙는 연산자를 나타내는 키워드 예) -i
  • infix : 피연산자들 사이에 붙는 연산자를 나타내는 키워드 예) 1 + 2
  • postfix : 피연산자의 뒤에 붙는 연산자를 나타내느 키워드 예) i++
struct Vector2D {
    var x = 0.0, y = 0.0
}

prefix func - (vector:Vector2D) -> Vector2D {
    return Vector2D(x: -vector.x, y: -vector.y)
}

func + (left:Vector2D, right:Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}

postfix func ++ (inout vector:Vector2D) -> Vector2D {
    vector.x++
    vector.y++
    return vector
}

var left = Vector2D(x: 1, y: 1)
var right = Vector2D(x: 1, y: 1)

left = -left
// left 는 x=-1, y=-1

var sum = left + right
// sum 은 x=0, y=0

sum++
// sum 은 x=1, y=1
  • precedence : 연산자 우선순위.

    둘 이상의 연산자를 포함하는 식에서 연산자의 순서를 나타내는 값.

  • associativity : 결합성

    동일한 우선순위를 갖는 연산자들을 포함하는 식에서 피연산자가 왼쪽 또는 오른쪽의 연산자와 그룹화 되는지 나타내는 값.

  • override

  • unowned
  • weak
  • lazy : 프로퍼티를 처음 사용할 때까지, 초기화 되도록 함. (처음 사용할때까지 초기화를 지연시킴)
class DataImporter {
    var fileName = "data.txt"

    init() {
        print("DataImporter init")
    }
}

class DataManager {
    lazy var importer = DataImporter()
}

let manager = DataManager()

manager.importer
// 처음 lazy 속성에 접근할 때 초기화됨.
  • convenience : 클래스를 위한 convenience 생성자를 선언할때 사용함. convenience 생성자는 다른 convenience 생성자를 호출하거나, designated 생성자를 호출함 수 있음.
  • dynamic : Objective-C 로 표현할수 있는 클래스 멤버에 적용. 멤버에 접근할 때 항상 Objective-C 런타임을 사용하여 디스패치하도록 함.
  • final : 클래스에 적용하면 서브클래싱 할수 없으며, 클래스 멤버에 적용하면 서브클래스에서 오버라이드 할 수 없음.
  • indirect : enum의 연관값의 데이터 구조를 재귀적으로 정의할수 있게함.
enum Tree {
    case Empty
    indirect case Node(value:Int, left:Tree, right:Tree)
}
let tree = Tree.Node(value: 0, left: Tree.Empty, right: Tree.Node(value: 1, left: Tree.Empty, right: Tree.Empty))
  • required : required로 선언된 생성자는 반드시 하위클래스에서 convenience 생성자에서 호출되거나 또는 오버라이드 되야함.
class A {
    var num:Int

    required init(num:Int) {
        self.num = num
    }
}


class B:A {

}

class C:A {
    convenience init(str: String) {
        self.init(num:Int(str)!)
    }
}

class D: A {
    init(str:String) {
        super.init(num: Int(str)! )
    }
    // 컴파일 에러 - 커스텀 designated 생성자를 정의했다면
    // required init(num:Int)를 오버라이드 해야함.
}
  • Type




[swift2] Currying 함수

Swift 2015. 11. 25. 02:10

수학과 컴퓨터 과학에서, Currying은 여러 인자를 취하는 함수를 일련의 단일 인자를 받는 함수들로 변환하는 테크닉을 의미합니다.


두 개의 인자를 받고 그 합을 반환하는 함수를 정의해보겠습니다. 대부분 개발자들에게 익숙한 형태는 다음과 같습니다.

func add1(x:Int, y:Int) -> Int {
    return x + y
}

add1 함수는 2개의 정수를 받아 그 합을 반환합니다. 


Swift 에서는 결과는 동일하지만 다른 형태의 함수를 다음과 같이 정의할 수 있습니다.

func add2(x:Int) -> (Int -> Int) {
    return { y in return x + y }
}

add2 함수는 한개의 인자를 받고, 두번째 인자를 필요로 하는 클로저를 반환합니다. add2는 add1과는 다른 형태로 호출됩니다.

add1(1, 2)
add2(1)(2)


return 문을 생략하고 다음 형태로도 정의 할 수 있습니다.

func add3(x:Int) -> (Int -> Int) {
    return { y in x + y }
}


함수 화살표(->) 는 오른쪽으로 연관을 짓기 때문에, A -> B -> C는 A -> (B-> C)라고 읽을 수 있습니다.  (즉 A 인자를 받고, B->C 타입의 함수를 반환하는 함수)


위와 같이 여러 인자를 요구하는 함수를 하나의 인자를 받는 함수를 연쇄적으로 호출하는 방식으로  변환하는 과정을 논리학자 Haskell Curry 에서 유래하여 currying 이라고 합니다.

add2는 add1의 curried 버전이라고 합니다. Swift에서 curry 함수를 정의할수 있는 세 번째 방법이 있습니다.

func add4(x:Int)(_ y:Int) -> Int {
    return x + y
}

add4(1)(2)


currying의 흥미로운 점은 다른 함수에 인자로 함수를 넘겨야 경우에 알수 있습니다.  add1 같은 uncurried 함수만 정의했다면 함수에 두개의 파라미터를 전달해야합니다. 반면 add2와 같이 curried 함수들에서는 한개의 파라미터 또는 두 개의 파라미터를 전달할지 선택할 수 있습니다.  


// 2개의 파라미터를 전달하여 즉시 결과를 얻는다.
var result = add4(0)(4)
// 4 

// 1개의 파리미터를 전달하고, 결과함수를 보관하여 재활용한다.
var addFour = add4(4)

var array = [1, 2, 3, 4, 5, 6]

var addedArray = array.map { addFour($0) }
// [5, 6, 7, 8, 9, 10]
 




[swift2] Core Image Filter 예제

Swift 2015. 11. 10. 23:42

절차지향 또는 객체지향 프로그래밍에 익숙한 상태에서, 함수형 프로그래밍에 적응하기란 쉽지 않습니다. 다른 프로그래밍 방법론을 공부했을 때처럼 많은 함수형 프로그램 코드를 읽고 이해하고, 또 많은 예제를 실제로 작성해봐야만 숙달될 수 있습니다.

swift 함수형 프로그램의 예로 Core Image 필터 예제를 소개합니다. 이 예제는 objc.io 에서 출판된 Funtional Programming in Swift 에서 발췌하였습니다.

출처: Funtional Programming in Swift

  • 이 예제에서는 MaxOSX Core Image 프레임워크의 함수형 wrapper API 작성을 목표로 합니다.
  • 또한 고차원함수(high-order function)와 같은 함수형 프로그래밍의 실질적인 활용예를 보는 것입니다.


Core Image

  • Core Image는 강력한 이미지 프로세싱 프레임워크지만 사용하기가 불편합니다.
  • Core Image API는 키밸류 코딩을 사용해서 설정할 수 있는 느슨하게 타이핑된 API 입니다.
  • 파라미터 이름을 입력할 때 실수할 수 있는 여지가 많아 런타임 에러를 발생시킬 수 있습니다.
  • 이번에 개발할 API 는 안전하고, 모듈화되었으며, (오타로 인한)런타임 에러가 발생하지 않도록 타입을 장점을 활용합니다.


Filter Type

  • Core Image의 핵심클래스는 이미지 필터를 생성하는데 사용하는 CIFilter 클래스입니다.
  • CIFilter 객체 생성시 반드시 kCIInputImageKey 키에 해당하는 input image를 전달해야합니다.
  • kCIOutputImageKey 키를 사용해서 필터가 적용된 결과 이미지를 얻어올 수 있습니다.
  • 또한 이 결과 이미지는 다른 필터의 입력으로 사용할 수 있습니다.

CIImage 타입을 파라미터로 받아 CIImage 타입의 결과 이미지를 반환하는 함수타입 Filter를 정의합니다.

typealias Filter = CIImage -> CIImage


Blur 필터 함수

  • 이미지에 블러를 적용하는 blur 함수를 정의해보겠습니다.
  • 이 블러필터는 한개의 blur radius 파라미터를 받습니다.
func blur(radius:Double) -> Filter {
    return { image in
        let parameters = [
            kCIInputRadiusKey : radius,
            kCIInputImageKey  : image
        ]
        let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
        return filter!.outputImage!
    }
}
  • blur 함수는 CIImage 타입의 이미지를 인자로 받고, 결과 이미지(filter!.outputImage)를 리턴하는 Filter 함수를 반환합니다.


Color Overlay 필터 함수

  • 이미지 위에 특정 컬러 레이어로 덮는 오버레이 필터를 정의해보겠습니다.
  • CoreImage 에는 오버레이 필터가 없지만 이미 있는 필터들을 조합하여 만들것입니다.
  • 컬러를 생성하는 color generator 필터와 레이어를 얹는 source-over compositing 필터를 사용하겠습니다.


Color 레이어 생성필터

func colorGenerator(color:NSColor) -> Filter {
    return { image in
        let parameters = [kCIInputColorKey:CIColor(color: color)!]
        let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}   
  • blur 필터와 유사하지만 input 이미지가 필요하지 않다는 차이가 있습니다.
  • 생성된 컬러 레이어 이미지를 특정크기(인자로 받은 이미지의 크기)로 잘라주어야 합니다.
  • 다음으로 두 이미지를 복합하는 필터를 정의합니다.


레이어 오버필터

func compositeSourceOver(overlay:CIImage) -> Filter {
    return { image in
        let parameters = [
            kCIInputBackgroundImageKey: image,
            kCIInputImageKey: overlay
        ]
        let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}
  • 마지막에 input 이미지의 크기만큼 결과 이미지를 자릅니다.
  • 두 필터를 결합하여 color overlay 필터를 생성합니다.


Color Overlay 필터

func colorOverlay(color:NSColor) -> Filter {
    return { image in
        let overlay = colorGenerator(color)(image)
        return compositeSourceOver(overlay)(image)
    }
}
  • colorOverlay 함수는 컬러를 인자로 받아 필터함수를 반환합니다.
  • 이 필터함수에 이미지를 전달하면, 앞서 전달한 컬러로 오버레이된 이미지를 반환합니다.


Filter 복합하기

  • blur 필터와 color overlay 필터를 복합해서 사용해보겠습니다.
  • 원본 이미지 파일을 읽습니다.
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!



  • blur 필터를 적용합니다.
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)



  • 빨간색 레이어를 오버레이합니다.
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage =  colorOverlay(overlayColor)(blurredImage)



Function Composition

  • 위 예제처럼 두 필터함수를 한문장의 표현식으로 표현할 수 있습니다.
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
  • 하지만 여러 괄호들로 인해 조합된 필터의 수가 많아질 수록 가독성이 나타집니다.
  • 더 좋은 방법은 필터 조합을 위한 커스텀 연산자를 정의하는 것입니다.
  • 먼저 필터를 조합하는 함수를 정의해보겠습니다.
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
    return { image in filter2(filter1(image)) }
}
  • 위 함수는 두개의 필터를 인자로 받아 조합하여 새로운 필터를 정의합니다.
  • 반환된 필터 함수는 CIImage 타입의 image 인자를 filter1과 filter2에 순차적으로 전달합니다.
  • 아래와 같이 사용할 수 있습니다.
let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)
  • 이제 필터 조합을 위한 연산자를 정의하여 좀더 가독성 있게 만들어보겠습니다.
  • 모든 곳에 자체 연산자를 정의할 필요는 없지만, 필터 조합은 이미지 처리 라이브러리에서 반복해서 발생하는 작업이기 때문에 그 용도가 적합합니다.
infix operator >>> { associativity left }

func >>> (filter1:Filter, filter2:Filter) -> Filter {
    return { img in filter2(filter1(img)) }
}
  • 이제 >>> 연산자를 이전에 정의했던 composeFilters 함수처럼 사용할 수 있습니다.
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
  • >>> 연산자를 left-associativity 로 정의했기 때문에, 유닉스 파이프처럼 왼쪽에서 오른쪽으로 필터가 적용됩니다.
  • 우리가 정의한 필터 조합은 Function Composition 의 한 예입니다.


전체소스

import Cocoa
import XCPlayground


// define Filter Type
typealias Filter = CIImage -> CIImage

// blur function
func blur(radius:Double) -> Filter {
    return { image in
        let parameters = [
            kCIInputRadiusKey : radius,
            kCIInputImageKey  : image
        ]
        let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
        return filter!.outputImage!
    }
}


// color layer function
func colorGenerator(color:NSColor) -> Filter {
    return { image in
        let parameters = [kCIInputColorKey:CIColor(color: color)!]
        let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}

// overlay function
func compositeSourceOver(overlay:CIImage) -> Filter {
    return { image in
        let parameters = [
            kCIInputBackgroundImageKey: image,
            kCIInputImageKey: overlay
        ]
        let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}

// color overlay function
func colorOverlay(color:NSColor) -> Filter {
    return { image in
        let overlay = colorGenerator(color)(image)
        return compositeSourceOver(overlay)(image)
    }
}

// 원본 이미지 읽기
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!

// apply blur
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)

// apply color overlay
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage =  colorOverlay(overlayColor)(blurredImage)


// define composition function
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
    return { image in filter2(filter1(image)) }
}

let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)


// define composition operator
infix operator >>> { associativity left }

func >>> (filter1:Filter, filter2:Filter) -> Filter {
    return { img in filter2(filter1(img)) }
}

let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)





'Swift' 카테고리의 다른 글

[swift2] 키워드로 보는 Swift  (0) 2015.11.30
[swift2] Currying 함수  (0) 2015.11.25
[swift2] Core Image Filter 예제  (0) 2015.11.10
[swift2.1] Xcode7.1 Playground 변경사항  (0) 2015.10.27
[swift2] Functor 와 Monad  (0) 2015.10.25
[swift2] 타입변환 연산자 (is, as, as?, as!)  (0) 2015.10.18

[swift2.1] Xcode7.1 Playground 변경사항

Swift 2015. 10. 27. 21:19

Xcode7.1 에서 변경된 Playground 기능에 대해 알아보겠습니다.


1. 파일, 이미지, 컬러를 editor로 드래그해서 객체 리터럴을 생성할 수 있습니다.

  • 리터럴은 플랫폼 특정 타입으로 아래와 같이 변환됩니다.
  • Color 리터럴 -> NSColor 또는 UIColor
  • File 리터럴 -> NSURL
  • Image 리터럴 -> NSImage 또는 UIImage


이미지 리터럴

  • Resources 폴더에서 이미지 파일을 에디터로 드래그 하면 그림과 같은 리터럴 형태로 추가됩니다.

UIImage 객체의 imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate) 메소드는 템플릿 형태로 렌더링 된 UIImage를 반환합니다. 검정-투명 으로만 이루어진 이미지에서 black 영역을 UIImageView 의 tintColor 속성을 사용해서 원하는 색으로 변경할수 있습니다.




컬러 리터럴

  • Editor > Insert Color Literal 메뉴를 선택하면 컬러 선택창이 나타나 컬러를 변경할 수 있습니다.




파일 리터럴

  • Resources 폴더의 파일을 드래그하면, 아래와 같이 파일 리터럴 형태로 추가됩니다.






2. XCPlayground 가 제공하는 API가 상당수 변경되었습니다.

  • API들이 전역함수에서 XCPlaygroundPage 클래스의 메소드 형태로 변경되었습니다.

API 변경사항으로 XCPCaptureValue, XCPShowView, XCPSetExecutionShouldContinueIndefinitely, XCPExecutionShouldContinueIndefinitely 함수와 XCPSharedDataDirectoryPath 전역상수는 deprecated 되었음. Xcode 이후 버전에서 제거될 예정.


1) 현재 페이지에 대한 참조는 XCPlaygroundPage.currentPage 를 사용합니다.

import XCPlayground

XCPlaygroundPage.currentPage 


2) 타임라인으로 값을 캡처하려면XCPlaygroundPage.captureValue(_:withIdentifier:) 을 사용합니다.

// XCPCaptureValue("식별자", value: i) : deprecated

for i in 1...10 {
    XCPlaygroundPage.currentPage.captureValue(i, withIdentifier: "식별자")
}  




3) 비동기 코드를 포함할 경우 XCPlaygroundPage.needsIndefiniteExecution 을 사용하세요.

  • XCPlaygroundPage.needsIndefiniteExecution 를 true로 설정하면, 실행흐름이 playground 파일 끝에 도달해도 실행을 중단하지 않고, 대기합니다.
// XCPSetExecutionShouldContinueIndefinitely(true) : deprecated

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true




4) XCPlaygroundPage.liveView 를 설정하면 타임라인에 노출됩니다.

  • liveView 속성에 nil이 아닌 값을 설정하면 .needsIndefiniteExecution 가 true 로 설정됩니다.
  • StackView 를 타임라인에 노출하는 예제입니다.

class StackView: UIView {
    var kElementHeight = CGFloat(50)
    var elements:[Int] = []

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.blackColor()
        super.layer.borderColor = UIColor.blueColor().CGColor
        super.layer.borderWidth = 1
        self.layout()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func layout() {
        self.subviews.map { $0.removeFromSuperview() }

        for i in 0..<self.elements.count {
            let value = self.elements[i]

            let elementLabel = UILabel(frame:CGRectZero)
            elementLabel.backgroundColor = UIColor.greenColor()
            elementLabel.text = "\(value)"
            elementLabel.textAlignment = NSTextAlignment.Center
            elementLabel.font = UIFont.systemFontOfSize(15)
            elementLabel.textColor = UIColor.whiteColor()
            self.addSubview(elementLabel)

            elementLabel.frame = CGRectMake(8, self.frame.size.height-(8+kElementHeight)*CGFloat(i+1), self.frame.size.width-8*2, kElementHeight)
        }
    }

    func push(element:Int) {
        self.elements.append(element)
        self.layout()
    }
    func pop() -> Int? {
        if self.elements.count > 0 {
            let topElement = self.elements.removeLast()
            self.layout()
            return topElement
        } else {
            return nil
        }
    }
}

let bounds = CGRectMake(0, 0, 320, 400)
let view = UIView(frame: bounds)
view.backgroundColor = UIColor.whiteColor()

// liveView 속성을 설정하면, Timeline 에 표시됩니다.
// XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 가 자동으로 설정됩니다.

XCPlaygroundPage.currentPage.liveView = view

let stackView = StackView(frame: CGRectInset(view.bounds, 30, 30))
view.addSubview(stackView)

stackView.push(1)
stackView.push(2)
stackView.push(3)  




5) NSURL 객체인 XCPlaygroundSharedDataDirectoryURL 전역상수가 추가되었습니다.

플랫폼 설정이 OS X 인 경우에만 유효한 상수입니다.

  • XCPlaygroundSharedDataDirectoryURL 는 playground 공유 데이터 디렉토리 경로를 나타냅니다.
  • OS X 에서는 ~/Document 경로에 Shared Playground Data 디렉토리를 수동으로 생성해야 합니다.
  • iOS 플랫폼에서는 playground 파일마다 경로가 다르기 때문에 데이터를 공유할 수 없습니다.






3. XCPlayground는 라이브 뷰로 뷰컨트롤러를 지원합니다.

LiveView 는 XCPlaygroundLiveViewable 프로토콜을 구현하는 객체입니다.

  • UIView, UIViewController 는 디폴트로 XCPlaygroundLiveViewable 프로토콜을 구현합니다.
  • XCPlaygroundLiveViewable 프로토콜 명세는 아래와 같습니다.
public protocol XCPlaygroundLiveViewable {
    public func playgroundLiveViewRepresentation() -> XCPlayground.XCPlaygroundLiveViewRepresentation
}

public enum XCPlaygroundLiveViewRepresentation {
    // 루트뷰를 갖지 않는 최상위뷰 
    case View(UIView)

    // 루트 뷰컨트롤러를 갖지 않는 최상위 뷰컨트롤러 
    case ViewController(UIViewController)
}
  • 위에서 구현했던 StackView 를 활용하여 네비게이션 컨트롤러를 라이브 뷰로 설정하는 예제입니다.
let stackView = StackView(frame: CGRectMake(0, 0, 200, 400))
stackView.push(1)
stackView.push(2)
stackView.push(3)

let contentViewController = UIViewController()
contentViewController.title = "StackView"
contentViewController.view.addSubview(stackView)

let liveViewController = UINavigationController(rootViewController: contentViewController)

XCPlaygroundPage.currentPage.liveView = liveViewController






4. XCPlaygroundPage.currentPage.finishExecution()를 호출해서 프로그래밍적으로 playground 실행을 중단 할 수 있습니다.

  • 이 메서드는 Xcode가 현재 playground 페이지 실행을 중단하도록 지시하여, 적절히 클린업을 수행하고 종료할 수 있도록 합니다.
XCPlaygroundPage.currentPage.finishExecution()




[swift2] 타입변환 연산자 (is, as, as?, as!)

Swift 2015. 10. 18. 02:52

이 글은 애플 Language Reference 문서를 참고하여 작성하였습니다.

  • 4개의 타입캐스팅 연산자가 있습니다.
  • is 연산자, as 연산자, as? 연산자, as! 연산자.


is 연산자

  • 런타임에 표현식이 특정 타입으로 다운캐스팅 될수 있는지 검사합니다.
  • 특정 타입으로 다운캐스팅이 가능하면true, 그렇지 않으면 false를 반환합니다.


as 연산자

  • 컴파일 타임에 업캐스팅 또는 브리징과 같이 타입변환이 항상 성공하는 경우에 타입변환을 수행합니다.
  • 업캐스팅(upcasting)은 중간변수 없이 표현식을 타입의 수퍼타입 인스턴스처럼 사용할수 있게합니다.
func f(any: Any) { print("Function for Any") }
func f(int: Int) { print("Function for Int") }
let x = 10
f(x)
// "Function for Int" 출력

let y: Any = x
f(y)
// "Function for Any" 출력

f(x as Any)
// "Function for Any" 출력
  • 브리징(bridging)은 새 인스턴스를 생서하지 않고도, String과 같은 표준라이브러리 타입 표현식을 NSString과 같은 파운데이션 타입으로 사용할 수 있게합니다.


as? 연산자

  • 표현식을 특정타입으로 조건부 타입변환을 수행합니다.
  • as? 연산자는 특정 타입의 옵셔널을 반환합니다.
  • 타입변환이 성공하면 표현식의 값은 옵셔널 타입으로 랩핑되어 반환됩니다. 실패하면 nil을 반환합니다.
  • 특정 타입으로 타입변환이 실패 또는 성공으로 보장되면 컴파일 타임 에러가 발생합니다.


as! 연산자

  • 표현식을 특정 타입으로 강제 타입변환을 수행합니다.
  • as! 연산자는 옵셔널 타입이 아닌 특정 타입의 값을 반환합니다.
  • 타입변환이 실패하면, 런타임 에러가 발생합니다.
  • x as! T 는 (x as? T)! 과 동일하게 동작합니다.




[swift] Swift 코딩환경 Playground

Swift 2015. 10. 5. 02:12

Playground는 xcode6 부터 지원된 인터랙티브한 swift 코딩환경입니다. 애플 공식문서에서는 Playground 를 다음과 같이 소개하고 있습니다.


  • 프로젝트 전체를 컴파일하지 않고도 코드를 실행하고 결과를 보여주는 인터랙티브한 스위프트 코딩환경.
  • Swift 언어를 학습하고, 앱의 일부분을 프로토타이핑하고, 다른 사람들을 위한 학습환경을 만드는데 활용가능.
  • 알고리즘과 시스템 API를 실험하고 커스텀뷰 구현이 가능함.
  • 학습결과에 리치 코멘트로 노트와 가이드 작성가능.


참고

  • 이글은 Playground_Help 문서를 참고하여 작성했습니다.
  • Playground_Help


1. Playground 에디터 구성

playground 에디터는 아래 그림과 같이 구성되어 있습니다.



  • Source editor 에 타이핑한 결과는 즉시 Results sidebar 에서 확인할수 있습니다.
  • Result sidebar의 Quick Look  버튼을 누르면 팝업으로 결과를 확인할수 있습니다. 



  • Result  버튼을 누르면 Source editor 영역에 결과를 인라인으로 삽입할 수 있습니다. 



  • Console 영역에는 print, NSLog 함수가 출력한 문자열을 확인할수 있습니다.

    View > Show Debug Area 메뉴를 통해 표시.



  • Timeline 영역은 XCPlayground 모듈의 렌더링 결과를 표시합니다.

    View > Assitant Editor > Show Assistant Editor 메뉴를 통해 표시. 



2. 결과뷰 Display 방식 변경하기

  • 인라인으로 삽입된 결과뷰의 Display 방식을 변경할 수 있습니다.
  • 삽입된 결과뷰를 선택하고 오른쪽 클릭을 하면 지원하는 display 방식을 표시합니다.

Lasest Value: 최종 결과만 표시.
Value History: 결과를 리스트로 출력.
Graph: 결과를 그래프로 출력.


3. 보조파일 추가하기

  • playground 파일의 Sources 폴더에 보조코드 파일을 추가 할수 있습니다.
  • Sources 폴더의 swift 파일들은 framework 로 미리 컴파일되어 메인 playground 파일에 module 로 자동으로 import 됩니다.
  • 주의할 점으로 playground 메인파일에서 접근할 수 있도록 클래스, 메서드, 함수, 변수, 프로토콜들은 public 으로 선언되어여야합니다.



4. 리소스 추가하기

  • playground 또는 page 에 이미지, 정적데이터 파일, 사운드와 같은 리소스를 포함할수 있습니다.



5. 커스텀 프레임워크 임포트

  • UIKit 과 같은 시스템 프레임워크 외에 커스텀 프레임워크의 import도 가능합니다.
  • 커스텀 프레임워크를 import 하기 위해서는 프로젝트와 playground 파일이 동일한 워크스페이스에 존재해야합니다.
  • 프로젝트의 워크스페이스가 존재하지 않으면 워크스페이스를 생성하고, playground 파일을 워크스페이스에 포함시킵니다.
  • playground 에서 접근하기 위해서 프로젝트의 클래스, 메서드, 함수, 변수, 프로토콜들은 public 으로 선언되어여야합니다.



6. UIView 렌더링 

  • playground의 멋진 기능 중의 하나로 UIView 를 구현하고 그 렌더링 결과를 확인할 수 있습니다.
let view = UIView(frame: CGRectMake(0,0,320,320))
view.backgroundColor = UIColor.magentaColor()

let label = UILabel(frame: CGRectMake(100, 100, 100, 30))
label.textAlignment = .Center
label.text = "Welcome!"
view.addSubview(label)

code 에 렌더링 결과를 삽입하거나, quick look 으로도 확인이 가능합니다.



7. 비동기 처리

  • playground는 디폴트로 위에서 아래로 코드를 실행하며 흐름을 종료합니다.
  • 비동기 api 통신의 경우 결과를 출력하지 못하고 바로 실행을 종료하게 됩니다.
  • XCPSetExecutionShouldContinueIndefinitely(true)를 상위해서 호출하면 비동기 요청이 완료할 때까지 playground 를 종료하지 않고 대기시킬 수 있습니다.
  • XCPSetExecutionShouldContinueIndefinitely 함수를 사용하려면 XCPlayground 프레임워크를 import 해야합니다.
import XCPlayground

// 비동기 처리하기 

XCPSetExecutionShouldContinueIndefinitely(true)

var imageSearch = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=girl"

let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var task = session.dataTaskWithURL(NSURL(string: imageSearch)!) {
    (data:NSData?, response:NSURLResponse?, error:NSError?) in
    if let res = response as? NSHTTPURLResponse {
        print("\(res.statusCode)", terminator:"\n")
        print("\(res.allHeaderFields)", terminator:"\n")
    }
    if let data = data {
        if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) {
            print("\(json)")
        }
    }
}
task.resume()


8. 마크다운 사용하기

  • 마크다운 문법을 지원하여 rich한 playground 문서를 작성할 수 있습니다.
  • 코드의 실행결과도 삽입할 수 있기 때문에 시각적인 컨텐츠를 작성할 수 있습니다.
  • swift 주석문 // 과 /* */ 구문에 콜론을 사용하면 마크다운 문법으로 인식합니다.
  • Editor > Show Rendered Markup 메뉴를 통해 마크다운 렌더링 결과를 확인할수 있습니다.
// 1. 마크다운 사용하기

//: This line will have **bold** and *italic* text.

/*:
## Headers of All Sizes

### Lists of Links

- [NSHipster](http://nshipster.com)
- [ASCIIwwdc](http://asciiwwdc.com)
- [SwiftDoc](http://swiftdoc.org)

### Images, Too

![Remote Image](http://nshipster.s3.amazonaws.com/alert.gif)


*Images in the Resources directory can be referenced locally*
*/

렌더링 결과를 확인해 볼까요?



8. 페이지 추가하기

  • playground에 페이지를 추가하여 모듈화된 학습환경을 만들 수 있습니다.

    File > New > Playground Page 메뉴.

  • Page 마다 Sources, Resources 폴더가 생성됩니다.
  • 가장 위의 Page 부터 빠른 순서를 갖으며, 페이지간 네비게이션을 추가할 때 의미를 가지게 됩니다.


9. 페이지간 네비게이션 추가하기

  • rich comment를 사용하여 페이지 간의 네비게이션을 추가할 수 있습니다.
  • 네비게이션은 rich comment 렌더링 모드에서 페이지 링크로 표시됩니다.
  • 3가지 종류의 네비게이션, 다음 페이지, 이전 페이지, 특정 페이지로 이동이 존재하며 아래와 같이 표현합니다.
// 다음 페이지로 이동하며, 마지막 페이지에서는 무시됩니다.
//: [Go to Next Page](@next)

// 이전 페이지로 이동하며, 첫 페이지에서는 무시됩니다.
//: [Go to Previous Page](@previous)

// 특정 페이지로 이동합니다. 공백은 %20으로 표기합니다.  
//: [Go to The End](Page%20Third)  
  • Page Second 에 네비게이션을 추가하고


  • 렌디링 모드에서 다음과 같은 결과를 확인할 수 있습니다.





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

Swift 2015. 8. 27. 01:48


Swift Language Enhancements and Changes


1. try? 키워드가 추가되었습니다.

  • try? 구문은 에러를 던질수도 있는 연산을 수행합니다.
  • 연산이 성공하면 결과는 optional로 랩핑되어 반환되고, 에러가 throw 되면 결과는 nil를 반환하고 error는 버려집니다.
  • try?는 if let과 guard 문과 함께 사용될 때 특히 유용합니다.
    func produceGizmoUsingTechnology() throws -> Gizmo { … }
    func produceGizmoUsingMagic() throws -> Gizmo { … }

    if let result = try? produceGizmoUsingTechnology() { return result }
    if let result = try? produceGizmoUsingMagic() { return result }
    print("warning: failed to produce a Gizmo in any way")
    return nil
  • try?에 의해 평가되는 표현식의 결과타입에 항상 optional이 추가된다는 것을 주목하세요.
  • throwing 함수의 반환타입이 Int?이면, try?와 함께 함수를 호출한 결과는 Int?? 또는 'Optional<Optional>' 이 됩니다.


2. Xcode는 . 문법을 사용할 때, 문맥을 인식한 enum 엘리먼트와 option sets의 코드완성 기능을 제공합니다.


3. 프로토콜 extension Static 계산속성을 정의할 수 있습니다.


4. 함수 또는 생성자 파라미터 리스트 어떤 위치에도 가변인자가 올 수 있습니다.

func doSomethingToValues(values: Int..., options: MyOptions = [], fn: (Int) ->
Void) { 
    // function body 
}


5. Objective-C와 호환되지 않는 타입을 포함하는 컬렉션은 더이상 Objective-C 호환타입으로 간주되지 않습니다.

  • 이전에는 Array 구문은 @objc가 마킹된 프로퍼티로 허용되었지만, 그러한 케이스는 더이상 허용되지 않습니다.


6. C typdefs으로 선언된 block은 이제 Swift 클로져 typealias로 임포트됩니다.

  • BOOL 타입 파라미터가 사용된 typedefs 블럭은 Bool 타입 파라미터를 가진 클로져로 임포트됩니다.(이전 베타5에서는 ObjCBool 파리미터로 되었음.)
  • 이것은 블럭 파라미터의 행동과 임포트된 Objective-C 메서드를 매칭시킵니다.


7. 타입체커가 생성한 에러메시지는 구체적이고 유용하게 개선되고 있습니다.

  • 진단시에, type alias를 사용했다면 별칭(aka)을 출력합니다.


Swift Standard Library Enhancements and Changes


1. print()와 debugPrint()가 개선되었습니다.

  • 가변인자 지원하여, 한번의 호출로 여러개의 아이템을 출력할수 있습니다.
  • separator: String = " " 가 추가되어, 어떻게 아이템이 구분되는지 제어할 수 있습니다.
  • appendNewline:bool = true 는 terminator:String = "\n"로 교체되었습니다.
  • output stream을 인자로 받는 print 함수에는 stream 인자에 toStream 레이블을 추가했습니다.


2. RangeReplaceableCollectionType.extend() 메소드가 appendContentsOf()로 이름이 변경되었습니다.

  • splice() 메서드는 insertContentsOf()로 이름이 변경되었습니다.


3. 클로져와 @autoclosure 인자를 갖는 대부분의 표준라이브러리 API은 이제 rethrows를 사용합니다.

  • map과 filter 같은 메서드의 클로져 파라미터들이 에러를 throw할 수 있게 허용합니다.
  • 에러를 발생시킬 수 있는 표현식과 &&, ||, ?? 같은 short-circuiting 연산자를 함께 사용할수 있습니다.


4. 모든 CollectionType은 이제 sliceable 합니다.

  • 다음 sequence spllitting/slicing 함수들은 SequenceType 프로토콜 요구사항에 맞게 삭제되거나 프로토콜 extension 디폴트 구현으로 대체되었습니다.
/// Returns the first `maxLength` elements of `self`,
/// or all the elements if `self` has fewer than `maxLength` elements.
prefix(maxLength: Int) -> SubSequence

/// Returns the last `maxLength` elements of `self`,
/// or all the elements if `self` has fewer than `maxLength` elements.
suffix(maxLength: Int) -> SubSequence

/// Returns all but the first `n` elements of `self`.
dropFirst(n: Int) -> SubSequence

/// Returns all but the last `n` elements of `self`.
dropLast(n: Int) -> SubSequence

/// Returns the maximal `SubSequence`s of `self`, in order, that
/// don't contain elements satisfying the predicate `isSeparator`.
split(maxSplits maxSplits: Int, allowEmptySlices: Bool, @noescape isSeparator:
(Generator.Element) -> Bool) -> [SubSequence]
  • split를 위해 다음 convenience extension을 제공합니다.
split(separator: Generator.Element, maxSplit: Int, allowEmptySlices: Bool) ->
[SubSequence]  
  • 또한 새로운 프로토콜 요구사항과 컬렉션 타입의 디폴트 구현이 사용가능합니다.
/// Returns `self[startIndex..<end]`
prefixUpTo(end: Index) -> SubSequence

/// Returns `self[start..<endIndex]`
suffixFrom(start: Index) -> SubSequence

/// Returns `prefixUpTo(position.successor())`
prefixThrough(position: Index) -> SubSequence





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

Swift 2015. 8. 14. 02:14


Linker

  • bitcode를 활성화하면 이제 weak frameworks와 weak libraries가 지원됩니다. linker는 더이상 -weak_framework, -weak-l 또는 -weak_librarys가 -bitcode_bundle 과 함께 사용될수 없다고 경고하지 않습니다.


Swift Language Changes

1. 구조체, 클래스도 이제 ErrorType 프로토콜을 구현할수 있습니다.


2. 실패가능한 initializer에 위임하거나 체이닝할 때 !를 사용하여 force-unwrap 할 수 있습니다.

  • self.init(...) 또는 super.init(...)가 실패가능한 경우 아래코드와 같이 force-unwrap 할 수 있습니다.
extension UIImage {
    enum AssetIdentifier: String {
        case Isabella
        case William
        case Olivia
    }
    convenience init(assetIdentifier: AssetIdentifier) {
        self.init(named: assetIdentifier.rawValue)!
    }
} 
  • 위와같이 실패가능한 initializer에 체이닝한 non-optional initializer 정의가 가능해집니다.


3. 성능향샹을 위한 -Onone 빌드 옵션

  • -Onone 옵션 (debug) 빌드를 사용하면 표준라이브러리 제네릭 타입에 특화된 인스턴스를 사용하여 빌드성능을 향상시킬 수 있습니다.
  • 많은 경우에 컴파일 타임에 영향없이 debug 빌드에서 상당히 빠른 실행파일을 생성합니다.


4. Objective-C 클래스의 제네릭 서브클래스와 이것을 상속하는 non-제네릭 클래스들은 runtime metadata 인스턴스화가 요구됩니다.

  • Objective-C 코드에서 직접적으로 네이밍을 지정할수 없습니다.
  • Beta4에 Objective-C 클래스들의 제네릭 서브클래스 정의가 가능해졌지만, 생성된 Objective-C 브리징 헤더에는 그러한 클래스들을 에러항목으로 리스팅합니다. 이로인해 잘못된 런타임 행동과 컴파일 타임에러를 발생시키는 오류를 수정하였습니다.
  • 클래스에 대한 @objc 속성의 행동이 좀더 명확해졌습니다.
  • 브리징헤더에 나타날 수 없는 클래스에 @objc를 적용하면 이제 에러입니다.
  • 클래스의 어떤 수퍼클래스라도 @objc 클래스라면, 그 클래스의 멤버들은 암묵적으로@objc 이며, 모든 @objc 클래스들은 NSObject 를 상속해야함. 따라서 유효한 코드에 대한 어떤 변경을 발생시키지는 않습니다.


5. MacTypes.h 에 정의된 Boolean은 Swift와 Objective-C 타입 브리징 상황에서 Bool로 임포트됩니다.


6. NSManaged 속성을 CoreData가 자동생성한 키밸류코딩 호환 to-many 접근자에 접근하기 위한 메서드와 프로퍼티에 사용할 수 있습니다.

@NSManaged var employees: NSSet

@NSManaged func addEmployeesObject(employee: Employee)
@NSManaged func removeEmployeesObject(employee: Employee)
@NSManaged func addEmployees(employees: NSSet)
@NSManaged func removeEmployees(employees: NSSet)
  • 이것들은 NSManagedObject 서브클래스에 선언되어야 합니다.


7. 타입 이름들과 enum cases들은 이제 디폴트로 자격없이(프로토콜 구현없이) print하고 String으로 변환할수 있습니다. debugPrint와 String(refelcting:)은 fully-qualified 이름을 얻기 위해 사용할 수 있습니다.

enum Fruit { case Apple, Banana, Strawberry }
print(Fruit.Apple)           // "Apple"
debugPrint(Fruit.Apple)      // "Fruit.Apple"


8. print()와 Mirrors의한 reflection은 current case와 멀티 payload 타입을 갖는 모든 enums payload를 report 할수 있습니다.

  • 유일하게 reflection이 지원되지 않고 남아있는 enum 타입들은 @objc enums와 C에서 임포트된 enums 입니다.


Swift Standard Library Enhancements and Changes

1. SequenceType 프로토콜 구현체에 .forEach 구문을 사용할 수 있습니다.

(0..<10).forEach {
    print($0)
}    

위 구문은 아래구문과 유사합니다.


for x in 0..<10 {
    print(x)
}

하지만 다음과 같은 차이점이 있습니다.

  • for-in 루프문과 다르게 break, continute를 사용하여 현재 클로져 몸체 호출을 종료하거나 일련의 호출을 건너뛸수
    없습니다.

  • 클로져 몸체에 return문을 사용하면 외부 스콥이 아닌 현재 호출만 종료할수 있으며, 이어지는 일련의 호출을 건너뛸수 없습니다.

  • 이러한 제약때문에 forEach 멤버는 함수형 알고리즘에 일련의 체이닝을 적용하고, 몸체가 적을때만 사용하도록 권장합니다.

foo.map {...}.filter {...}.forEach {...}
  • 다른 경우에는 for..in 문 사용을 권장합니다.


3. SIMD: simd 모듈의 정수형 벡터타입은 &+, &-, & 연산자를 사용하여 감싸진 unchecked 산술식만 지원합니다. +,-, 연산자들은 정수형 벡터에는 사용 불가능하고 Xcode가 자동으로 wrapping 연산자로 교체하라고 제안할 것입니다.


4. SIMD: smid 모듈의 정수형 벡터타입 코드생성은 벡터하드웨어를 더 잘 사용할 수 있도록 개선되어 대부분의 경우에 극적으로 성능이 향상되었습니다.


5. Dictionary의 removeAtIndex(_:) 메서드는 key,value 쌍을 제거하여 두 엘리먼트를 튜플로 반환합니다.

  • 유사하게 Set의 removeAtIndex(_:)은 삭제된 엘리멘트를 반환합니다.


6. Word와 UWord 타입은 표준라이브러리에서 삭제되었습니다. 대신 UInt를 사용하세요.