검색결과 리스트
2015/12/09에 해당되는 글 1건
- 2015.12.09 [swift2.1] Swift 프로토콜 지향 프로그래밍 2
글
[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
'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 |