Animation

UIView.animate(...) 를 사용해서 애니메이팅을 구현할 수 있다.

 

animation(애니메이션) 사전적 의미

동작이나 모양이 조금씩 다른 많은 그림이나 인형을 한 장면씩 촬영하여 영사하였을 때에 화상이 연속하여 움직이는 것처럼 보이게 하는 것

어렸을 때, 책 가장자리에 조금조금씩 움직임을 표현하여 100p를 그린 뒤, 책을 빠르게 넘겨 촤르르르륵~ 하는 것이 애니메이션이었다..!

 

iOS에서의 Animation

Changes to several view properties can be animated—that is, changing the property creates an animation starting at the current value and ending at the new value that you specify. The following properties of the 
UIView
 class are animatable:

뷰의 여러 속성을 변경하여 애니메이팅을 할 수 있다. 즉, 속성을 변경하는 것은 현재 값에서 새로운 값으로의 애니메이션을 생성한다.

볼드체가 핵심이다..! 위의 애니메이션 정의만 생각하면 View를 조금조금씩 그려 촤라라라락하는 것처럼 해야하기 때문에, 직접 자잘한 움직임과 위치, 크기 등의 속성을 일일이 지정해줘야한다.

 

B.U.T.하지만 현재 값에서 새로운 값으로의 애니메이션을 생성한다!

 

애니메이팅할 수 있는 UIView의 속성은 다음과 같다.

 

먼저 Frame값을 변경해보자!!

이러한 화면이 있다고 하자!

 

UIView.animate(withDuration: 2.0) {
	self.playPauseButton.frame = CGRect(x: 0, y: 0, width: self.playPauseButton.frame.size.width, height: self.playPauseButton.frame.size.height)
}

위에서 얘기한대로 난 최종 상태값만을 지정해줬을 뿐인데!! 자연스럽게 이동한다!!!

 

다음으로 backgroundColor를 변경해보자

UIView.animate(withDuration: 2.0) {
	self.view.backgroundColor = .black
}

 

 

이 또한 흰색에서 검은색으로 변경한다고 지정했을 뿐인데! 흰색 -> 회색 -> 검은색 으로 자연스럽게 변환된다.!!

 


  • UIView.animate 를 사용한 애니메이팅은 자동으로 MainQueue(UIQueue)에서 동작한다
    • 따라서 따로 DispatchQueue.main.async를 사용하지 않아도 된다.
  • origin값을 변경하는 것이 아니라, Transform을 변경하는 것이다.
    • Transform.identity를 이용하여 원래값을 얻을 수 있다
  • 애니메이팅이 끝난 후, 동작하는 completionHandler가 존재한다.
    • Animation이 중간에 cancel되는 경우가 있어서 파라미터로 Bool 변수를 받는다.

 

Codable이란?


A type that can convert itself into and out of an external representation.

자신을 변환하거나 외부 표현(JSON 같은 데이터를 주고 받는 형식)으로 변환할 수 있는 타입이다.

 

또한

Codable은 Decodable과 Encodable을 동시에 채택한 타입이다.

 

Decodable : 자신을 외부표현(external representation)에서 디코딩 할 수 있는 타입

Encodable : 자신을 외부표현(external representation)으로 인코딩 할 수 있는 타입

 

따라서, Codable은 자신을 외부표현으로 디코딩 & 인코딩이 가능한 타입 이라고 생각하시면 될 것 같다

 

Codable은 프로토콜

Codable은 프로토콜이므로 Class, Struct, Enum 에서 모두 사용할 수 있습니다. (이전 Protocol 포스팅에 있음)

그리고 타입에서 Codable을 채택했다는 것은 해당 타입을 serialize / deserialize 할 수 있다는 것

 

struct SomeType{
	var name: String
    var id: Int
}

이라는 구조체가 있다고 가정하자.

해당 타입에 Codable을 채택해보자

struct SomeType: Codable {
	var name: String
    var id: Int
}

이제 SomeType이라는 구조체는 외부표현(JSON형식)으로 변환이 가능한 타입입니다.

 

Encode

JSON으로 만드는 방법

//인코더 생성
let encoder = JSONEncoder()

//인스턴스 생성
let someInstance = SomeType(name: "First", id: 1)

//인코딩해서 jsonData에 저장 
// 1. encode메소드의 리턴타입은 Data
// 2. encode메소드는 throws -> try? 사용 -> try? 사용으로 jsonData는 옵셔널
let jsonData = try? encoder.encode(someInstance)

//Data 형식을 보기 좋게 String으로 변환
if let jsonData = jsonData, let jsonString = String(data: jsonData, encoding: .utf8){
	print(jsonString) // {"name" : "First", "id" : 1}
}

 

주로 주고받을 데이터 형식을 약속할 때, JSON을 사용한다. 하지만 어느 곳을 가도 {"name":"First","id":1} 처럼 가로로 쭉 늘어놓은 데이터로 약속을 하지 않을 것이다. 보통 봐온 JSON형식은 아래와 같다

{
	"name" : "First",
    "id" : 1
}

위처럼 JSONString을 이쁘게 만들 수 있다

//위와 같이 이쁘게 만들어준다
encoder.outputFormatting = .prettyPrinted

//key값을 기준으로 오름차순으로 정렬한다
encoder.outputFormatting = .sortedKeys

//그렇다면 나는 key값을 기준으로 오름차순으로 정렬한 이쁜 데이터를 받고싶어!
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

 

Decode

JSON을 내가 만든 타입으로 변환하기

주로 서버에 있는 데이터(JSON)을 받아서 내가 저장하고 싶은 타입의 모델에 저장하는 작업을 자주하게 된다.

이때 사용할 수 있는 것이 Codable이다.

 

//디코더 생성
let decoder = JSONDecoder()

//위에서 만든, Encode된 JSON을 Data로 변환
var data = jsonString.data(using: .utf8)

//Data 형식을 내가 만든 모델인 SomeType의 인스턴스로 변환
if let data = data, let someInstance = try? decoder.decode(SomeType.self, from: data){
	print(someInstance.name) // First
    print(someInstance.id) // 1
}

 

 

CodingKey

음... 쉽게 예를 들어보자

struct SomeType: Codable{
	var name: String
    var id: Int
}

위와 같은 타입에 Codable을 채택했다.

근데 넘어오는 데이터 형식이

{
	"name" : "First",
    "identifier" : 1
}

이렇게 넘어온다고 하자

 

내가 생성한 모델의 id와 넘어오는 JSON의 identifier의 "Key"가 다르다 -> 이렇게 되면 

No Value associated with key id 라고 나온다.

 

해결 방법은 2가지가 있다.

  1. JSON형식에 맞춰 모델의 프로퍼티 명을 작성하는 방법
    1. 오류는 없겠지만, 가독성 측면에서 좋지 않다
  2. CodingKey 사용!

CodingKey 사용

struct SomeType: Codable{
	var name: String
    var id: Int
    
    enum CodingKeys: String, CodingKey{
    	case name
        case id = "identifier" // identifier라는 키는 이제부터 id프로퍼티가 담당할거야!
    }
}

 

 

지금까지 Swift에서 모델에서 JSON으로 인코딩, JSON에서 모델로 디코딩 방법과

key값이 다를 때 사용하는 CodingKey에 대해 알아보았다.

 

 

 

 

 

 

 

 

NotificationCenter

애플 공식 문서에는 이렇게 적혀있다

A notification dispatch mechanism that enables the broadcast of information to registered observers.

등록된 Observer들에게 정보를 BroadCast할 수 있는 알림 발송 메커니즘

 

이전에 사용했을 때는 주로 키보드가 올라올 때, 내려갈 때 TextField가 가려지는 문제를 해결하기 위해서 사용한 경험이 있다.

오늘은 좀 더 자세히 알아보자


출처 : https://baked-corn.tistory.com/42

어떤 객체가 Post를 하면 Observe하고 있던 Observer들에게 BroadCast를 한다.

(....? Delegate랑 똑같은 거 아냐?)

맞다. 어떤 객체가 다른 객체와 소통/송수신 한다는 메커니즘은 같다

 

Delegate 패턴의 사용 예

protocol SomeDelegate{
	func sayName(name: String)
}

class FirstView: UIView{
	// 순환 참조 사이클을 막기 위해 강한 참조가 아닌 약한 참조로 선언한다
    // 이벤트를 발생할(책임을 전가하는) 타입에서는 Delegate를 프로퍼티로 선언한다
	weak var delegate: SomeDelegate?
    
    func tappedButton(){
    	delegate?.sayName(name: "tree")
    }
}

// 동작을 수행할 타입에는 Delegate를 채택한다
class SomeController: SomeDelegate{
	var view: SomeView = SomeView()
    
    init(){
    	view?.delegate = self
    }
    
    // SomeDelegate 채택 후, 준수
    func sayName(name: String){
    	print(name) // tree
    }
    
} 

크게 5가지만 생각하면 된다

  1. Delegate 패턴을 위한 Protocol 선언
  2. 책임을 전가할 타입(위임자)에서 delegate를 프로퍼티로 선언 -> 이때, 순환 참조 사이클을 막기 위해 약한 참조 사용
  3. 동작을 수행할 타입(대리자)에서 delegate를 채택
  4. 위임자에 있는 delegate를 대리자의 객체로 초기화
  5. 대리자에서 프로토콜을 준수

장점 : 코드를 이해하며 따라가기 쉬움

단점 : 많은 객체들에게 이벤트를 알려주는 것이 어렵고 비효율적

 

 

Notification

- NotificationCenter 라는 싱글톤 객체를 통해 이벤트들의 발생 여부를 옵저버에게 알림.

- Notification.Name 이라는 Key 값을 통해 Post/Observe 가능

 

class PostVC: UIViewController{
	@IBOutlet var button: UIButton!
    
    @IBAction func sendNotification(_ sender: UIButton){
    	NotificationCenter.default.post(name: Notification.Name("buyEmoticon"), object: nil)
    }
}


class ViewController: UIViewController{
	override func viewDidLoad(){
    	super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(sayHello(noti:)), name: Notification.Name("buyEmoticon"), object: nil)
        
    }
    
    deinit{
    	NotificationCenter.default.removeObserver(self)
    }
    
    @objc func sayHello(noti: Notification){
    	print("Hello") // Hello
    }
}

 

장점

  • 많은 줄의 코드가 필요없이 이벤트/동작을 구현할 수 있다
  • 다수의 객체들에게 동시에 이벤트의 발생을 알려줄 수 있음

단점

  • 추적이 쉽지 않음
  • post 이후 정보를 받을 수 없음

 

결론

  1. 가능하다면 Delegate로 구현하되, 하나의 이벤트로 다수의 객체에서 어떠한 동작을 해야하는 경우에만 NotificationCenter를 사용
  2. NotificationCenter를 사용해야 하는 이벤트들의 경우 NotificationCenter 사용

 

 

상속

- 클래스는 메서드나 프로퍼티 등을 다른 클래스로부터 상속받을 수 있습니다.

- Super/Sub Class로 구분된다

- Swift의 Struct는 상속을 받을 수 없다

- Java에서의 상속의 개념과 같다

 

메서드 재정의

//func aa() 라는 메소드를 재정의
override func aa(){
	//...
}

 

프로퍼티 재정의

- 부모로부터 상속받은 인스턴스 프로퍼티나 타입 프로퍼티를 재정의할 수 있다

- 프로퍼티를 재정의하는 것은 프로퍼티 자체가 아니라, Getter/Setter/감시자 등을 재정의 하는 것을 의미함

- 부모 클래스에서 읽기전용이었어도 자식 클래스에서는 R/W 로 재정의 가능

  - B.U.T. R/W -> 읽기전용은 불가능

 

프로퍼티 감시자 재정의

- willSet / didSet 도 재정의 가능 -> 연산/저장 프로퍼티 가능

  - B.U.T. 상수 저장 / 읽기 연산 전용 프로퍼티는 재정의할 수 없다

    -> 상수 저장 프로퍼티나 읽기 전용 연산 프로퍼티는 값을 설정할 수 없으므로!

 

서브스크립트도 재정의가 가능하다

재정의 방지

- 부모 클래스를 상속받는 자식클래스에서 몇몇 특성을 재정의할 수 없도록 제한하기 위함

- final 키워드를 사용 (final var, final func, final class func ....)

 

클래스의 이니셜라이저 - 상속과 재정의

- 값 타입의 init 은 구분할 필요가 없지만 class에서는 지정/편의 init으로 구분된다

 

지정/편의 Init

지정 init

- 클래스의 주요 이니셜라이저

- 필요에 따라 부모클래스의 init을 호출할 수 있으며, class의 모든 프로퍼티를 초기화해야하는 임무를 맡음

- 클래스당 하나 이상 정의됨 -> 지정하지 않으면 기본 지정 init 사용

- if) 부모 클래스의 지정 init이 자식 클래스의 지정 init 역할이 가능하다면, 자식 클래스에서 지정 init 새성하지 않아도 됨

 

편의 init

- 초기화를 손쉽게 도와주는 역할

- 자신의 내부에서 지정 init을 호출함

- 지정 init의 매개변수가 많아, 외부에서 일일이 전달하기 어렵거나, 특정 목적에 사용하기 위해 사용

 

지정/편의 init 관계(클래스의 초기화 위임)

  1. 자식의 지정 init은 부모의 지정 init을 반드시 호출해야 함
  2. 편의 init은 자신을 정의한 클래스의 다른 init(편의/지정)을 반드시 호출
  3. 편의 init은 궁극적으로 지정 init을 반드시 호출

2단계 초기화

- 스위프트 컴파일러는 2단계 초기화를 오류없이 처리하기 위해 4가지 안전확인을 실행한다

  1. 자식의 지정 init이 부모의 init을 호출하기 전에 자신의 프로퍼티 모두 초기화 확인
  2. 자식의 지정 init은 상속받은 프로퍼티에 값을 할당하기 전에 반드시 부모의 init을 호출
  3. 편의 init은 자신의 프로퍼티를 포함하여, 어떤 프로퍼티라도 값을 할당하기 전에 다른 init을 호출
  4. 초기화 1단계를 마치기 전까지는 init은 인스턴스 메서드를 호출 불가
    1. 또, 인스턴스 프로퍼티의 값을 읽을 수도 없음
    2. self 프로퍼티를 자신의 인스턴스를 나타내는 값으로 활용 불가

- 클래스의 인스턴스는 초기화 1단계를 마치기전까지는 유효하지 않다

- 1단계를 거쳤을 때 비로소 유효한 인스턴스가 됨

 

  • 1단계
    • 클래스가 지정/편의 init을 호출
    • 클래스의 새로운 인스턴스를 위한 메모리가 할당 -> 아직 초기화되지 않은 상태
    • 지정 init은 클래스에 정의된 모든 저장 프로퍼티에 값이 있는지 확인
    • 지정 init은 부모의 init이 같은 동작을 행할 수 있도록 초기화를 양도
    • 부모는 상속 체인을 따라 최상위 클래스에 도달할 때까지 이 작업을 반복
    • 최상위 클래스에 도달했을 때, 모든 저장 프로퍼티에 값이 있다고 확인하면 인스턴스의 메모리가 초기화
  • 2단계
    • 최상위 클래스로부터 최하위 클래스까지 상속 체인을 따라 내려오면서 지정 init들이 인스턴스를 제각각 정의
      • 이 단계에서는 self를 통해 프로퍼티 값을 수정 가능
      • 인스턴스 메서드를 호출 가능
    • 마지막으로 각각의 편의 init을 통해 self. 를 통한 사용자 정의 작업을 진행 가능

 

 

+ Recent posts