검색결과 리스트
Swift에 해당되는 글 32건
- 2016.03.24 [swift2.2] Xcode 7.3 Swift 언어 변경사항
- 2015.12.29 [swift2.1] Swiftris - swift로 만드는 테트리스 (1)
- 2015.12.22 [swift2.1] Array와 Dictionary 기초
- 2015.12.09 [swift2.1] Swift 프로토콜 지향 프로그래밍 2
- 2015.11.30 [swift2] 키워드로 보는 Swift
- 2015.11.25 [swift2] Currying 함수
- 2015.11.10 [swift2] Core Image Filter 예제
- 2015.10.27 [swift2.1] Xcode7.1 Playground 변경사항
- 2015.10.25 [swift2] Functor 와 Monad
- 2015.10.18 [swift2] 타입변환 연산자 (is, as, as?, as!)
글
[swift2.2] Xcode 7.3 Swift 언어 변경사항
Xcode7.3 Release Notes
Playground
- iOS, OS X의 Live view는 사용자 인터랙션을 지원합니다.
- rich comment가 인라인 video display를 지원하도록 개선되었습니다. Video 태그를 사용해 비디오 파일을 읽어올 수 있으며, 대체 텍스트, 커스텀 poster 프레임, width, height 스펙에 대한 옵션을 제공합니다. Markup Formatting Reference. 의 Video 챕터 참고.
- XCPlaygroundPage.captureValue(_:withIdentifier:) 가 deprecated 되었습니다.
Swift
- 새롭게 지원하는 #if swift(>=x.y) 구문을 사용해서 빌드 환경을 구분할수 있습니다. inactive 분기문에 있는 코드는 파싱되지 않고, 문법진단에서 제외되기 때문에 같은 파일에서 다른 버전의 swift 소스코드를 혼합하여 사용할 수 있습니다.
- 모든 slice type은 removeFirst() 와 removeLast() 메서드를 갖습니다.
C 의 익명구조체들은 swift의 중첩 구조체 타입으로 임포트 됩니다.
3개의 문서 코멘트 필드가 추가되어, 사용자가 더 효과적으로 코드완성 엔진과 상호동작하도록 지원합니다.
- keyword:
- recommeded:
- recommendedover:
클래스 내부에서 failable(init?) 또는 예외를 던지는(init() throws) designated initializer 는 모든 저장속성을 초기화 하기 전에 또는 super.init() 을 호출하기 전에 종료할 수 있습니다. 이 동작은 designated initializers 가 convenience initializers 와 더 일관적이게 합니다. convenience initializers 역시 self.init() 을 수행하기 전에 실패할 수 있습니다.
- Curried 함수 문법이 deprecated 되었으며, Swift3에서 삭제된다고 합니다.
- ++, -- 연산자들도 deprecated 되었으며, Swift3에서 삭제된다고 합니다. 대신 정수와 부동소수에서는 x += 1, Index 타입에서는 x = x.successor() 을 사용하세요.
- 프로토콜의 연관타입은 typealias를 대체하여 새로운 associatedtype 을 사용할 수 있습니다. typealias 키워드는 swift2.2에서는 여전히 지원하지만 deprecated 되었으며 warning을 생성합니다. 이 warning은 Swift3에서는 에러가 될 것입니다.
protocol P {
associatedtype Ty
}- 함수 또는 생성자를 참조할때, 인자 레이블을 포함한 완전한 이름을 사용할 수 있습니다.
- #file, #line, #column, #function 표현식이 추가되어 _FILE_, _LIN__, _COLUMN, _FUNCTION_ 를 대체합니다. FILE__ 스타일의 심볼은 deprecated 되었으며 Swift3에서 삭제될 예정입니다.
- 글로벌 anyGenerator() 함수는 좀더 직관적이고 자연스로운 API 스럽게 AnyGenerator 구조체의 생성자로 변경되었습니다. anyGenerator() 는 Swift2.2에서 deprecated 되었으며, Swift3에서 삭제될 예정입니다.
생성된 Objective-C 선언에서 rename 을 지원하도록, @objc(SomeName) 속성을 enum 과 enum case에도 사용할수 있습니다.
Swift 메서드의 Objective-C 셀렉터는 #selector 표현식을 사용하여 직접적으로 설정할 수 있습니다. 기존의 string 리터럴을 셀렉터로 사용하는 방식은 deprecated 되었습니다.
let sel = #selector(insertSubview(_:aboveSubview:)) // sel은 Selector 타입
let sel: Selector = "insertSubview:aboveSubview:" // deprecated
string 리터럴을 사용하는 방식은 #selector를 사용한 방식으로 대체되어야 하며, 컴파일러가 #selector로 변경되도록 수정을 지원합니다. 다음의 방법으로 직접 셀렉터를 생성하는 것도 유효합니다.
let sel = Selector("propertyName")
'Swift' 카테고리의 다른 글
| [swift2.2] Xcode 7.3 Swift 언어 변경사항 (0) | 2016.03.24 |
|---|---|
| [swift2.1] Swiftris - swift로 만드는 테트리스 (1) | 2015.12.29 |
| [swift2.1] Array와 Dictionary 기초 (0) | 2015.12.22 |
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
| [swift2] 키워드로 보는 Swift (0) | 2015.11.30 |
| [swift2] Currying 함수 (0) | 2015.11.25 |
설정
트랙백
댓글
글
[swift2.1] Swiftris - swift로 만드는 테트리스
Swiftris는 Swift 언어 버전의 iOS 테트리스 게임입니다. Swift 언어 학습을 위해 개발되었으며 완전한 소스는 GitHub 페이지에서 확인하실 수 있습니다. 간단하지만 임팩트 있는 앱을 만들면서 Swift를 재미있게 공부해보세요.
1. Swiftris 영상
2. 게임조작
- Play: play 버튼 터치.
- Pause: pause 버튼 터치.
- Stop: stop 버튼 터치.
- Move Left: 벽돌의 왼쪽 영역 터치.
- Move Right: 벽돌의 오른쪽 영역 터치.
- Rotate : 벽돌의 상단 영역 터치.
- Drop: 화면 롱 터치.
3. 소스코드 저장소
4. 클래스 구조
- ViewController.swift
- Swiftris.swift
- GameView.swift
- GameBoard.swift
- GameScore.swift
- NextBrick.swift
- Brick.swift
- GameTimer.swift
- SoundManager.swift
GameBoard
- GameBoard는 22행 x 10열로 구성된 UIColor의 이차원 배열입니다.
class GameBoard: UIView {
static let rows = 22
static let cols = 10
...
var board = [[UIColor]]()
...
}
Brick
- 벽돌은 모양을 본따 I, J, L, T, Z, S, O 로 7가지 종류가 있습니다.
- 벽돌종류는 고유의 색을 가진 enum 타입으로 정의됩니다.
enum BrickType {
case I(UIColor)
case J(UIColor)
case L(UIColor)
case T(UIColor)
case Z(UIColor)
case S(UIColor)
case O(UIColor)
}
class Brick: NSObject {
...
static var bricks = [
BrickType.I(UIColor(red:0.40, green:0.64, blue:0.93, alpha:1.0)),
BrickType.J(UIColor(red:0.31, green:0.42, blue:0.80, alpha:1.0)),
BrickType.L(UIColor(red:0.81, green:0.47, blue:0.19, alpha:1.0)),
BrickType.T(UIColor(red:0.67, green:0.45, blue:0.78, alpha:1.0)),
BrickType.Z(UIColor(red:0.80, green:0.31, blue:0.38, alpha:1.0)),
BrickType.S(UIColor(red:0.61, green:0.75, blue:0.31, alpha:1.0)),
BrickType.O(UIColor(red:0.88, green:0.69, blue:0.25, alpha:1.0))
]
...
}
Swiftris
- 게임로직과 인터렉션 처리.
class Swiftris: NSObject {
...
// 터치 - 벽돌 왼쪽,오른쪽 이동, 벽돌 드랍
func touch(touch:UITouch!) {
guard self.gameState == GameState.PLAY else { return }
guard let curretBrick = self.gameView.gameBoard.currentBrick else { return }
let p = touch.locationInView(self.gameView.gameBoard)
let half = self.gameView.gameBoard.centerX
let top = curretBrick.top()
let topY = CGFloat( (Int(top.y) + curretBrick.ty) * GameBoard.brickSize )
if p.y > topY {
if p.x > half {
self.gameView.gameBoard.updateX(1)
} else if p.x < half {
self.gameView.gameBoard.updateX(-1)
}
} else {
self.gameView.gameBoard.rotateBrick()
}
}
// 롱프레스 - 벽돌 드랍
func longPressed(longpressGesture:UILongPressGestureRecognizer!) {
if self.gameState == GameState.PLAY {
if longpressGesture.state == UIGestureRecognizerState.Began {
self.gameView.gameBoard.dropBrick()
}
}
}
}
GameScore
- 게임점수는 클리어되는 라인수에 따라 10점, 30점, 60점, 100점이 지급됩니다.
- 예제에서는 레벨클리어 기능을 제공하지 않았지만, 자신만의 레벨클리어 기능을 구현해보세요.
class GameScore: UIView {
...
var scores = [0, 10, 30, 60, 100]
func lineClear(noti:NSNotification!) {
if let userInfo = noti.userInfo as? [String:NSNumber] {
if let lineCount = userInfo["lineCount"] {
self.lineClearCount += lineCount.integerValue
self.gameScore += self.scores[lineCount.integerValue]
self.update()
}
}
}
...
}NextBrick
- 게임 play중 다음으로 사용할 수 있는 벽돌 3개를 미리 볼수 있습니다.
class Brick: NSObject {
...
static var nextBricks = [Brick]()
static var nextBrickCount = 3
// 벽돌 대기열에서 첫번째(index 0) 벽돌을 사용하고, 대기열을 3개로 채웁니다.
static func generate() -> Brick! {
while self.nextBricks.count < self.nextBrickCount {
self.nextBricks.append(self.newBrick())
}
let brick = self.nextBricks.removeAtIndex(0)
self.nextBricks.append(self.newBrick())
return brick
}
...
}
SoundManager
- 게임의 생생함을 더하기 위해 몇가지 효과음을 제공합니다.
- 게임 배경음, 벽돌 떨어지는음, 게임오버음.
class SoundManager: NSObject {
var bgmPlayer:AVAudioPlayer?
var effectPlayer:AVAudioPlayer?
var gameOverPlayer:AVAudioPlayer?
...
}
5. 피드백
'Swift' 카테고리의 다른 글
| [swift2.2] Xcode 7.3 Swift 언어 변경사항 (0) | 2016.03.24 |
|---|---|
| [swift2.1] Swiftris - swift로 만드는 테트리스 (1) | 2015.12.29 |
| [swift2.1] Array와 Dictionary 기초 (0) | 2015.12.22 |
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
| [swift2] 키워드로 보는 Swift (0) | 2015.11.30 |
| [swift2] Currying 함수 (0) | 2015.11.25 |
설정
트랙백
댓글
글
[swift2.1] Array와 Dictionary 기초
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에 대해 정리해보겠습니다.
'Swift' 카테고리의 다른 글
| [swift2.2] Xcode 7.3 Swift 언어 변경사항 (0) | 2016.03.24 |
|---|---|
| [swift2.1] Swiftris - swift로 만드는 테트리스 (1) | 2015.12.29 |
| [swift2.1] Array와 Dictionary 기초 (0) | 2015.12.22 |
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
| [swift2] 키워드로 보는 Swift (0) | 2015.11.30 |
| [swift2] Currying 함수 (0) | 2015.11.25 |
설정
트랙백
댓글
글
[swift2.1] Swift 프로토콜 지향 프로그래밍 2
이 글은 아래의 자료를 참고하여 작성하였습니다.
지난 시간에 이어, 프로토콜 지향 프로그래밍에 또다른 예제를 살펴보겠습니다. 프로토콜과 구조체가 어떻게 클래스를 대체할 수 있는지 좀더 자세히 알아보기 위해, 도형 그리기를 목적으로 설계된 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
'Swift' 카테고리의 다른 글
| [swift2.1] Swiftris - swift로 만드는 테트리스 (1) | 2015.12.29 |
|---|---|
| [swift2.1] Array와 Dictionary 기초 (0) | 2015.12.22 |
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
| [swift2] 키워드로 보는 Swift (0) | 2015.11.30 |
| [swift2] Currying 함수 (0) | 2015.11.25 |
| [swift2] Core Image Filter 예제 (0) | 2015.11.10 |
설정
트랙백
댓글
글
[swift2] 키워드로 보는 Swift
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
'Swift' 카테고리의 다른 글
| [swift2.1] Array와 Dictionary 기초 (0) | 2015.12.22 |
|---|---|
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
| [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] Currying 함수
수학과 컴퓨터 과학에서, 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]
'Swift' 카테고리의 다른 글
| [swift2.1] Swift 프로토콜 지향 프로그래밍 2 (0) | 2015.12.09 |
|---|---|
| [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] Core Image Filter 예제
절차지향 또는 객체지향 프로그래밍에 익숙한 상태에서, 함수형 프로그래밍에 적응하기란 쉽지 않습니다. 다른 프로그래밍 방법론을 공부했을 때처럼 많은 함수형 프로그램 코드를 읽고 이해하고, 또 많은 예제를 실제로 작성해봐야만 숙달될 수 있습니다.
swift 함수형 프로그램의 예로 Core Image 필터 예제를 소개합니다. 이 예제는 objc.io 에서 출판된 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 변경사항
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()
'Swift' 카테고리의 다른 글
| [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 |
| [swift] Swift 코딩환경 Playground (0) | 2015.10.05 |
설정
트랙백
댓글
글
[swift2] Functor 와 Monad
이 포스트는 아래 원문을 참고하여, Functor 와 Monad 에 대해 설명합니다.
1. Functor 란?
Functor 란 map 함수를 지원하는 컨테이너 타입입니다.
- Array 타입은 map 함수를 지원하기 때문에 Functor 입니다.
- Array 엘리먼트들을 2 배로 곱하는 예제를 살펴보겠습니다.
var doubledImperative:[Int] = [1, 2, 3]
for number in numbers {
doubledImperative.append(number * 2)
}
print(doubledImperative) // 2, 4, 6
- 단순히 루프문을 사용하여 배열 엘리먼트를 2배하여 새 배열에 담습니다.
Map
- map 함수를 사용하면 다음과 같이 처리할 수 있습니다.
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map { $0*2 }
print(doubledNumbers) // 2, 4, 6
- 예제에서처럼, map 을 사용하면 Array 엘리먼트를 변환하는 작업의도를 더 명확하게 표현할 수 있습니다.
- 즉, 어떻게가 아닌 무엇을 달성하려는지 를 더 잘 표현합니다.
- 함수형 프로그래밍의 장점 중 하나라고도 할수 있습니다.
Optional도 map 적용이 가능한 컨테이너 타입입니다.
- map은 Array 뿐만 아니라 어떤 컨테이너 타입에도 구현할수 있는 고차원 함수입니다.
- 값이 있거나 또는 없음을 포장하는 Optional 타입도 해당합니다.
고차원 함수: 파라미터 또는 반환값으로 함수를 전달할수 있는 함수.
let number:Int? = 815 // Optional(815)과 동일함
let transformedNumber = number.map{ $0*2 }.map{ $0%2 == 0 }
print(nilNumber.map{ $0*2 }.map{ $0%2 == 0 }) // Optional(true)
Optional.map 은 우리를 대신에 nil 을 처리해줍니다.
- Optional 타입에 map을 사용했을 때 장점은 우리를 대신해 nil 값을 처리준다는 것입니다.
- 원래 값이 nil 이었다면 map을 적용해도 nil을 반환합니다.
- 이로인해 연산의 중간과정에서 중첩된 if let을 사용해야하는 번거로움을 피할수 있습니다.
// 원래값이 nil인 경우
let nilNumber:Int? = nil
let transformedNilNumber = nilNumber.map{ $0*2 }.map{ $0%2 == 0 }
print(transformedNilNumber) // nil
- map을 사용하지 않았다면 다음과 같이 처리했을 것입니다.
let nilNumber:Int? = nil
var result = false
if let number = nilNumber {
result = ((number*2) % 2) == 0
}
print(result)
컨테이너 타입에 따라 map은 조금 다르게 동작합니다.
- map은 컨테이너 타입에 따라, 의미적으로 조금 다르게 동작한다는 것을 알 수 있습니다.
- T 타입의 값을 포함하는 컨테이너 타입을 구현할 때, map 메서드의 일반적인 시그니처는 다음과 같습니다.
func map<U>(transformFunction: T -> U) -> Container<U>
- T 는 현재 컨테이너가 포함하는 엘리먼트 타입.
- U 는 반환될 컨테이너의 엘리먼트 타입.
커스텀 컨테이너 Container 구조체에 map을 구현해보겠습니다.
- 예제로, Container 라는 커스텀 컨테이너 타입을 구현해보겠습니다.
struct Container<T> {
var value:T
func map<U>(transform: T -> U) -> Container<U> {
let newContainer = Container<U>(value: transform(self.value))
return newContainer
}
}
- 우리는 T 타입을 파라미터로 받아, U 타입을 반환하는 함수(transform)를 map 에 제공합니다.
- map은 내부값에 변환함수를 적용하고 교체된 값을 갖는 다른 컨테이너 인스턴스를 생성하여 반환합니다.
- 아래와 같이 사용할 수 있습니다.
var container = Container<Int>(value: 5)
var resultContainer = container.map { "\($0 * 10)" }
print(resultContainer.value) // "50"
또다른 커스텀 컨테이너 Result 타입을 구현해봅시다.
- Result enum 은 요즘 오픈소스 swift 코드에서 많이 볼수 있는 패턴입니다.
enum Result<T> {
case Value(T)
case Error(NSError)
}
- 몇몇 프로그래밍 언어에서 Either 라고 알려진 타입의 구현입니다.
- case Error 연관값은 제네릭 대신 NSError 타입으로 연산의 결과를 보고하는데 사용합니다.
- 개념적으로 Result는 값이 있을 수도 있고, 없을 수도 있는 임의의 타입의 값을 포장하는 Optional과 유사합니다.
- 이 경우에는 추가적으로 왜 그 값이 없는지도(NSError) 알려줍니다.
- 아래와 같이 파일에서 컨텐츠를 읽고 Result 객체로 반환하는 함수를 구현해보겠습니다.
func dataWithContentsOfFile(file:String, encoding:NSStringEncoding) -> Result<NSData> {
do {
let data = try NSData(contentsOfFile: NSBundle.mainBundle().pathForResource(file, ofType: nil)!, options: NSDataReadingOptions.DataReadingMapped)
return Result.Value(data)
} catch {
return Result.Error(error as NSError)
}
}
- 이 함수는 NSData 값을 담은 Result.Value를 반환하거나, (파일을 읽을 수 없는 경우에는) NSError를 갖는 Result.Error를 반환합니다.
텍스트 파일을 읽어 대문자로 변환하는 작업을 구현해봅시다.
var stringContents:String?
switch data {
case let .Value(value):
stringContents = NSString(data: value, encoding: NSUTF8StringEncoding) as? String
case let .Error(error):
break
}
let uppercasedContents:String? = stringContents?.uppercaseString
- 매 단계마다 값이 있는지 검색해야 하기 때문에, 중첩된 if let 와 switch 문을 사용하게 됩니다.
map을 사용하면 이렇게 처리할 수 있습니다.
- NSData 를 받아서, String 으로 변환하고, 그리고 다시 대문자로(String) 변경하는 과정을 수행합니다.
NSData -> String -> String
- 일련의 map 변환을 적용하여 아래와 같이 처리할 수 있습니다. (map의 구현에 대해서는 아래에서 다룹니다.)
let data:Result<NSData> = dataWithContentsOfFile("test.txt", encoding: NSUTF8StringEncoding)
let uppercaseContents:Result<String> = data.map { NSString(data: $0, encoding: NSUTF8StringEncoding)! }.map { $0.uppercaseString }
- Array 에 map을 적용했던 이전 예제와 유사하게 이 코드는 훨씬더 의도가 명확합니다.
어떻게 Result.map 을 구현했을까요?
extension Result {
func map<U>(f: T -> U) -> Result<U> {
switch self {
case let .Value(value):
return Result<U>.Value(f(value))
case let .Error(error):
return Result<U>.Error(error)
}
}
}
- 변환함수 f는 타입 T (NSData)의 값을 받고, 타입 U (String)을 반환합니다.
- 값이 있으면 단순히 value를 파라미터로 f를 호출하고, 값이 없으면 동일한 error를 갖는 다른 Result.Error를 반환합니다.
- Result라는 껍질(컨테이너)이 포장하고 있는 알맹이에 f함수를 젹용한다고 생각하면 쉽게 이해할수 있을 것입니다.
요약하면
- Optional, Array 와 같은 컨테이너 타입에 구현된 map 함수가 무엇을 하는지 알아보았습니다.
- map 은 변환함수에 의해 변경된 값을 갖는 새 컨테이너를 반환합니다. (알맹이만 변경합니다.)
- Functor는 map 을 구현하는 타입입니다.
- Dictionary, 클로져와 같은 타입도 Functor 라고 할 수 있고, map 함수가 어떻게 동작하는지 대략 예측해볼 수 있습니다.
2. Optional 은 Monad 입니다.
- Monad는 값이 있을 수도 있고 없을 수도 있는 상태를 포장하는 타입입니다. (swift 에서는 Optional)
- Monad 는 Functor 의 한 유형입니다.
map 의 변환함수의 반환타입이 Result 라면 어떤일이 발생할까요?
- 위 예에서 변환함수를 다른 값으로 변환하는데 사용했습니다.
- 만일 변환함수가 새 Result 객체를 반환하게 되면 어떻게 될까요?
- 마찬가지로 변환함수가 새 Error 에러를 반환하면 어떻게 될까요?
- 먼저 map 함수 시그니처를 통해 알아봅시다.
func map<U>(f: T -> U) -> Result<U>
- T는 NSData 이고 U는 Result
입니다. 반환타입 Result를 시그니처에 대입해보면
func map(f: NSData -> Result<String>) -> Result<Result<String>>
- 반환 값에 Result 가 중첩 되었다는 것에 주목하세요. 우리가 원했던 결과는 아닙니다.
중첩된 Result를 받아 단순 Result로 flatten 하는 함수를 구현해봅시다.
extension Result {
static func flatten<T>(result:Result<Result<T>>) -> Result<T> {
switch result {
case let .Value(innerResult):
return innerResult
case let .Error(error):
return Result<T>.Error(error)
}
}
}
- flatten 함수는 중첩된 내부타입 T를 갖는 Result를 받아, 단순히 Value와 Error의 연관값을 추출해서 단일 Result
를 반환합니다. flatten 함수는 다른 문맥에서도 찾을수 있는데, 그중 하나로 배열의 배열을 일차원 배열로 flatten 하는 예입니다.
map, flatten 을 결합해서 우리의 Result
-> Result 변환에 적용해봅시다.
let stringResult = Result<String>.flatten(data.map { (data:NSData) -> (Result<String>) in
if let string = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
return Result<String>.Value(string)
} else {
return Result<String>.Error(NSError(domain: "error", code: -1, userInfo: nil))
}
})
일반적으로 map 함수에 flatten 과정을 적용해 flapMap 또는 flattenMap이라 불립니다.
extension Result {
func flapMap<U>(f:T -> Result<U>) -> Result<U> {
return Result.flatten(map(f))
}
}
Monad 타입은 flapMap 함수와 찰떡궁합 입니다.
- map이 동작하는 타입은 여기서 보았던 것과 유사한 시그니처를 갖는 flatMap 함수를 구현합니다.
- 다음에 Functor와 Monad라는 단어를 들으면 두려워하지 맙시다.
- Functor와 Monad는 단순히 컨테이너 타입에 적용할수 있는 공통 연산을 기술하기 위한 디자인 패턴입니다.
'Swift' 카테고리의 다른 글
| [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 |
| [swift] Swift 코딩환경 Playground (0) | 2015.10.05 |
| [swift2] Swift Guard (0) | 2015.09.11 |
설정
트랙백
댓글
글
[swift2] 타입변환 연산자 (is, as, as?, as!)
이 글은 애플 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' 카테고리의 다른 글
| [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 |
| [swift] Swift 코딩환경 Playground (0) | 2015.10.05 |
| [swift2] Swift Guard (0) | 2015.09.11 |
| [swift2] Swift2에서의 문자열(strings) (0) | 2015.09.02 |