나만의 공부 노트
5강 정리 본문
학습 내용
Error Handling
기본적으로 error를 정의할 때는 enum을 사용하고, Error 프로토콜을 준수합니다. (iOS에서 주는 error는 NSError입니다.)
오류를 던지는 함수는 func save() throws -> Int 와 같이 -> 이전에 throws 키워드를 넣으면 됩니다.
함수 내부에서는 오류를 발생시키는 구간에서 throw (정의한 error)를 통해 에러를 발생시키면 됩니다.
다시 이러한 함수는 do-catch구문과 함께, 그리고 do안에서 try로 코드를 실행해야합니다.
catch는 switch문과 유사하게 enum의 error를 받을 수 있습니다.
또는 위와 같이 error를 받고, 나중에 switch 문으로 구분해서 사용도 가능합니다.
심각하지 않은 error의 경우에는 do-catch 구문을 제외하고 try! , try? 만을 사용할 수 있습니다.
Any, AnyObject
Objective-C API와의 호환으로 인해 Any 타입을 사용합니다.(AnyObject는 클래스 타입의 Any입니다.)
Swift언어는 매우 강력한 type 언어입니다.
그래서 any상태에서는 어떠한 메소드를 호출 할 수 없습니다.
명시적으로 이를 바꿔줘야 하며 이때 사용하는 키워드는 as입니다.
또는 is 키워드를 통해 타입이 맞는지 bool을 얻을 수 있습니다.
4가지 클래스
UIKit은 기본적으로 objc를 바탕으로 제작되었습니다.
따라서 UIKit의 모든 요소가 NSObject라는 클래스를 상속받고 있습니다.
objc에서는 숫자에 원시 타입의 int, double과 그리고 NSNumber라는 클래스 타입을 사용합니다.
NSNumber는 객체 지향적이며, 모든 숫자를 표현, 전달이 가능합니다.
objc를 기반으로 하는 api일지라도, NSNumber를 자동으로 swift의 타입으로 변환시켜주는 bridging 기법을 사용하고 있습니다.
Data는 비트를 담는 클래스입니다.
후에 형변환을 통해서 다양한 타입의 파일로 사용할 수 있습니다.
Views
앱의 최상위 층에는 UIWindow라는 UIView가 있습니다.
기본적으로 Mac과 달리 앱은 하나의 화면만 구성하므로 UIWindow는 하나입니다.
외부 화면에 View를 띄울 때 사용되며, 자주 사용되지는 않습니다.
view안의 subviews는 clip이 된다면 영역 밖의 뷰는 표시되지 않습니다.(default값은 clip하지 않습니다.)
특이하게도 subview를 추가할 때는 superview에게 요청하지만 제거할 때는 제거될 view에게 명령을 내립니다.
func addSubView(_ view: UIView)
func removeFromSuperview()
override func awakeFromNib()는 인터페이스 빌더가 nib파일을 객체화(초기화)시킨 후에 곧바로 호출됩니다.
단 초기화 순서는 보장된 것이 아니기 때문에, IBOutlet같은 외부 뷰들에 접근할 수 없습니다.(초기화가 됬는지 아직 모릅니다.)
Drawing
CGFloat의 Core Graphic에서 2차원 좌표계를 담당합니다.
화면의 시작은 왼쪽 상단입니다. x의 값이 증가하면 오른쪽으로, y값이 증가하면 아래로 내려갑니다.
Mac 환경에서는 왼쪽 아래가 화면의 시작이며 약간 다르니 이를 인지하고 있어야 합니다.
단위는 point이며, pixel이 아닙니다.
Pixels은 여러분의 디바이스가 그릴 수 있는 최소 단위이며,
Points는 coordinate system에서의 단위입니다.
대부분의 경우 point당 2pixel이 있지만 1, 3이 있을 수도 있습니다.
해당 값을 알고 싶다면 UIView의 var contentScaleFactor: CGFloat를 통해 확인 할 수 있습니다.
func draw(_ rect: CGRect)
오직 이 함수 안에서 드로잉이 가능합니다.
인수인 rect은 최적화를 위해 필요한 인자입니다.(rect안에만 그림을 그립니다.)
단, 주의해야할 점은 draw를 구현하고 실제로 코드에서 이 함수를 호출하면 안됩니다.
draw가 필요한 시점마다 자동으로 시스템이 이 함수를 호출하므로 따로 이 함수를 사용하면 안됩니다.
만약 뷰를 다시 그릴 필요가 있다면
setNeedsDispaly()
setNeedsDisplay(_ rect: CGRect)
이 함수들을 호출합니다.(화면에 표시되는 프레임보다 더 많이 그릴 필요도 없고, 새로 갱신해야하는 draw를 쌓아놓고 한 번에 그리면 효율적이므로 다시 그릴 필요가 있다고 시스템에게 알려만 줍니다.)
여기서 rect는 변경된 부분만 다시 그릴 수 있는 최적화 인자입니다.(예를 들어 뷰안의 서브뷰가 사라졌다면, 그 서브뷰 자리만 다시 그립니다.)
draw 구현
두 가지의 방법이 존재합니다.
1. 현재 context를 가져온 다음에 드로잉을 올려놓는 방법입니다.
2. UIBezierPath 클래스를 이용하여(자동으로 현재 context를 가져옵니다.) 드로잉 객체를 만들고 반복적으로 context위에 드로잉을 올려놓을 수 있습니다.
* 컨텍스트는 여러 개가 존재할 수 있습니다.(old, new context 전환하기 위해, 출력 context 등등)
1번의 방법
let context = UIGraphicsGetCurrentContext()
context.addLine()
...
등과 같이 사용 가능합니다.
2번의 방법
let path = UIBezierPath()
1번과 유사하지만 context를 얻는 대신, 베지에 경로를 생성하면 됩니다.(덕분에 반복해서 그릴 수 있는 이점이 있습니다.)
context는 자동으로 현재 context위에 그려줍니다.
draw 색상
경로의 색상은 UIColor를 통해서 설정합니다.
UIColor.green.setFill() <- 아마 UIColor의 전역 변수를 context나 bezierpath가 가져다 그리는 것 같습니다.
또는 literal color를 사용해도 됩니다.(CGColor는 설정이 안됩니다. 아마 접두사로 UI인 것을 보아 UIKit에서 관리하기 때문에 그런 것 같습니다. 후에 CGColor으로 변환 될 것 같은데.. 왜 지원을 안하는지는 모르겠습니다.)
bezierPath의 clip
bezierPath에 addClip()함수를 호출하면 이후에 그려지는 경로는 모두 addClip 경로 안에만 그려지게 됩니다.
불투명도
alpha : 0.0~1.0범위의 값으로, 0.0은 완전하게 투명, 1.0은 완전하게 불투명입니다. (전체 뷰의 드로잉의 투명도를 결정합니다.)
isOpaque : view가 투명한지 불투명한지 결정하는 Bool값. true로 설정하면 drawing 시스템이 view를 완전히 불투명하게 처리합니다. => 성능을 향상시킬 수 있습니다.(기본적으로 인터페이스 빌더에서 만든 뷰는 false = 투명, 코드에서 만든 뷰는 true = 불투명 입니다)
* 애플은 뷰안의 모든 드로잉의 alpha값이 1.0이면 isOpaque를 true로, 하나라도 alpha값이 1.0보다 작다면 isOpaque를 false로 설정하라고 합니다.
* 그렇지 않을 경우, 결과를 예측할 수 없다고 합니다.
opacity : 불투명도. 0.0(투명)~1.0(불투명)범위내에 있어야 하며, 해당 범위를 벗어나는 값은 최소값 또는 최대값으로 고정됩니다. 이 속성의 기본값은 1.0이며, alpha와 차이는 alpha는 UIView에, opacity는 CALayer의 프로퍼티라는 것입니다.
CALayer
CA는 Core Animation의 약자로 CG와는 다른 시스템이며, CALayer란 스크린에 시각적 컨텐츠를 그려내는 사각형의 Class입니다.
모든 UIView는 내부에 CALayer를 기본적으로 포함하고 있으며, 이를 이용해 화면에 드로잉을 할 수 있습니다.
또한, CALayer에는 다양한 Property를 제공하기 때문에 매우 사용하기 편리합니다.
레이어의 위치와 크기, 배경색, + 컨텐츠(이미지를 출력하거나 혹은 Core Graphic를 통해 그려진 그래픽 등) 등등이 layer 변수를 통해 제어 가능합니다.
여기서 색상은 CGColor 또는 literal color만 이용할 수 있습니다.
그 이유는 UIColor는 UIKit level의 클래스이고, CALayer는 코어 그래픽스 레이어 계층에서 이루어지기 때문에 상위 계층인 UIColor 클래스를 쓸 수 없습니다.
Drawing Text
텍스트를 나타내고 싶다면 서브뷰로 UILabel을 배치하는게 가장 쉽습니다.(다양한 기능을 제공하기 때문입니다.)
그러나 만약 텍스트를 직접 그리고 싶다면 func draw() 함수 안에 NSAttributedString.draw(at: aCGPoint)를 호출하면 됩니다.
* NSAttributedString은 objc NSString 클래스를 사용하므로 swift String과 인덱싱이 조금 다를 수 있습니다.
* 자동으로 bridging을 해주긴 하지만 인덱싱을 사용할 땐 주의해야합니다.
* NSString을 인덱싱으로 접근할 때에는 objc의 NSRange를 이용합니다.
* 참고로 NSAttributedString은 변형 불가이기 때문에 NSMutableAttributedString을 사용하여 변형 가능 string을 사용합니다.
* 사용하는 방법은 일반적인 swift의 방법으로 인덱스를 구한다음, NSRange(swift의 range, in: String)을 통해서 NSRange를 얻을 수 있습니다.
Fonts
preferred font는 미리 지정된 폰트이며 title, body, caption 등이 있습니다.
static func preferredFont(forTextStyle: UIFontTextStyle) -> UIFont로 사용이 가능하며 UIFontTextStyle에는 .headline, .body, .footnote를 인자로 넣어주면 해당 폰트를 반환합니다.
이를 NSAttributedString 속성에 넣어주면 됩니다.
기본적으로 preferred font는 설정에서 폰트의 크기를 바꾸면 함께 크기가 변합니다.
하지만 UIFont(name: "example", size: 36.0)처럼 새로운 커스텀 폰트를 불러오면 사이즈가 고정이 되어있습니다.
불러온 커스텀 폰트도 크기를 설정값에 따라 변화하게 만들고 싶다면, UIFontMetrics를 이용합니다.
기본적으로 바탕이 되는 preferred font를 사용해서 UIFontMetrics를 만들고, scaledFont를 이용해 커스텀 폰트를 적용하여 사용합니다.
UIImageView
UIImage를 서브뷰로 가지며, UIImage를 드로잉하는 뷰입니다.
또는, 일반적인 View에 func draw() 함수를 통해 UIImage.draw()를 이용하면 바로 그릴 수 있습니다.(패턴을 이용한 드로잉도 가능합니다.)
UIImage를 가져오는 방법은
1. File System을 이용하는 방법
2. Asset을 이용하는 방법
3. 네트워킹을 이용하는 방법
4. UIGraphicsBeginImageContext()라는 전역함수를 통해 context 안에 그림을 그리고 이미지로 캡쳐하는 방법
이 있습니다.
Redraw
기본적으로 bound가 바뀌어도 새로 draw를 하지 않습니다.
contentMode는 비트를 어떻게 조작할 지에 대해 설정할 수 있고, 오로지 redraw 옵션만이 새로 드로잉을 합니다.
기본적으로 bound가 바뀌게 된다면 서브뷰를 재배치하는 방법은
1. Autolayout을 적용
2. bound가 바뀌면 자동으로 func layoutSubviews()이 호출되고, 이 함수 내에서 코드를 구현하여 재배치하는 방법
입니다.
Enum rawValue
enum에 rawValue를 할당할 수 있습니다.(하위 호환성으로 인해 지원합니다.)
네임 옆에 :Type을 붙이면 해당 타입을 원시값으로 사용할 수 있습니다.
Int는 0부터, String은 해당 변수의 이름 그대로 rawValue가 설정이 되고, 또는 직접 케이스마다 = 을 이용해서 rawValue를 지정해 줄 수 있습니다.
Print Object
기본적으로 print는 어떤 Object라도 출력을 해줄 수 있습니다.
하지만 읽기엔 좋은 구조가 아니므로, CustomStringConvertable 프로토콜을 채택함으로써 print의 가독성을 높일 수 있습니다.
유일하게 var description: String 프로퍼티만 구현하면 됩니다.
Reading 3
Checking API Availability
if #available(iOS 10, macOS 10.12, *) {}
else {}
과 같이 버전을 확인할 수 있습니다.
Autoclosure
함수의 인자로 @autoclosure를 받고, 클로저 형태로 만들어 줍니다.
예로 assert(@autoclosure 조건, "String입니다") 함수도 첫 번째 인자를 {} 클로저 형태로 넣는 것이 아닌, 단순히 bool 을 계산하는 코드만 넣어줍니다.
`()`가 아닌 `(Int)` 등 인자를 받고자 하려고 한다면 컴파일러가 `Autoclosure argument type must be '()'`로 에러를 보여줍니다.
따라서 인자를 추가할 수 없습니다.
Recursive Enumerations
enum에 재귀적으로 연관 데이터에 자기 자신을 넣을 수 있습니다.
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
재귀를 사용한다면 indirect 키워드를 사용해야 합니다.
Subscript
커스텀 객체에 subscript(first: Int, second: Int, third:Int, ...) -> Type 을 정의해놓는다면 object[1,2,3,..]과 같이 인덱스로 접근이 가능해진다.
단, 계산 프로퍼티와 동일한 문법으로 구성해야한다.
Bit Operator
접두사에 0b = 8bit, 0x = 32bit을 붙여 사용합니다.
기본적으로 c언어에서 비트 연산자와 동일합니다.
연산자 오버로딩
연산자의 종류에 따라 4가지의 종류가 있으며 다음과 같습니다.
- infix : 더하기 연산자 처럼, 두 값 사이에서 사용됩니다(예, 1 + 1).
- prefix : 음수 연산자 처럼, 값의 앞에 추가됩니다(예, -3).
- postfix : 강제 언래핑(force-unwrap) 연산자처럼, 값 뒤에 추가 됩니다(예, mayBeNil!).
- ternary : 3개의 값 사이에 삽입된 두 개의 기호입니다. Swift에서, 사용자 정의 삼항 연산자는 지원되지 않고 내장된 삼항 연산자는 하나있으며, 애플 문서(Apple’s documentation)에서 읽을수 있습니다.
먼저 커스텀 연산자를 만들고 싶다면
prefix operator +++
의 전역 형태로 선언을 합니다.
그 다음에 사용하고자 하는 객체에 다음과 같이 static함수 형태로 선언합니다.
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
+++vector2D