기어가더라도 제대로

[Swift-기초] class - 100 days of swiftUI 본문

Swift - 기초

[Swift-기초] class - 100 days of swiftUI

Damagucci-juice 2022. 9. 30. 12:18

목차

1. class 생성 방법
2. 상속하는 방법
3. class 에 initializer 추가하기
4. class copy 
5. class deinitializer
6. class의 property 가 어떻게 변하는가?

등을 알아봅시다. 

class  VS. struct (기초)

  • 생성방법이 struct 와 비슷, 중요한 점에서 차이 "class" 키워드를 붙이느냐, "struct" 키워드를 붙이느냐 차이
  • 공통점
    • 타입을 생성하고 이름 부여
    • 속성, 메서드, 프로퍼티 감시자, 접근 제어를 할 수 있음
    • 원하는 방식으로 initializer를 커스텀하게 구현가능
  • 차이점
    • 바탕이 되는 class의 기능위에 또다른 class 를 만들 수 있다. 혹은 선택적으로 override 할 수도 있다. 
    • 완벽하게 따라가는게 아니기 때문에, super의 생성자를 sub 에서 자동으로 생성해주지는 않는다. initialize 는 각자 해야한다. 
    • class의 인스턴스를 복사하게 되면 그안에 프로퍼티와 같은 데이터도 같이 복사된다. 복사본의 프로퍼티를 변경하면 원본도 변경
    • class 인스턴스의 마지막 복사본이 파괴되면, Swift 가 선택적으로 deinitializer 라는 메서드를 호출한다. 
    • class 자체를 상수로 선언하더라도, 프로퍼티가 변수로 선언되어있으면 변경가능하다. 

 

class 가 사용되는 이유(struct와 차이점 심화)

  • class A 가 class B 를 상속받고 class B 는 클래스 C를 상속받고, 클래스 C는 클래스 D를 상속받는 것이 가능
  • 상속관계를 유지하는 클래스간에 공통된 initializer 가 없는 이유는, 특정 클래스 X 에서 프로퍼티를 하나라도 추가하면 모든 초기화 생성자를 깨트릴 것이기 때문
  • 원본과 복사본의 관계에서, 한 인스턴스의 속성값을 변경하면 모든 복사본의 속성값이 변경됩니다. 
  • class의 인스턴스가 어디서든 참조될 수 있으므로 최종 복사본이 파괴되었는지가 중요해집니다. 

 

 클래스가 클래스를 상속하는 방법

  • 상속
    • 존재하는 클래스를 기반으로 해서 새로운 클래스를 생성하는 절차
    • 물려주면 부모, 혹은 super // 물려 받으면 child class 혹은 subclass
    • 약간의 수정만 해주면 하위 클래스에서 상속받을 수 있음

이런 클래스가 를 상속받고 싶다면 이 클래스를 상속받으려는 클래스를 선언할 때 ":(콜론)"  뒤에 부모 클래스를 쓰면됨

Employee 를 상속받는 클래스들은 hours 프로퍼티나 initializer 를 별도의 선언 없이 사용할 수 있음

  • hours 를 공유할 뿐만 아니라 Employee에서 메서드도 공유할 수 있다. 

물려받은 메서드도 하위 클래스에서 입맛에 맞게 변경할 수 있는데, "override" 키워드를 사용한다. 

override func printSummary() {
    print("I'm a developer who will sometimes work \(hours) hours a day, but other times spend hours arguing about whether code should be indented using tabs or spaces.")
}

Developer 클래스에서만 재정의 해준 모습

  • 클래스를 상속시키지 않을 것이라면 class 앞에 "final" 키워드를 붙이면 된다. 

 

클래스 initializer를 생성하는 방법

  • struct initializer 보다 조금 까다롭긴한데
  • "자식 클래스가 자신의 프로퍼티를 모두 초기화한 후에 부모의 initializer를 호출해야한다"
  • 클래스는 공통된 initializer가 없음
  • 클래스마다 initializer 를 만들어주어야 함(혹은 모든 프로퍼티를 디폴트 값으로 초기화 하거나)

  • isElectric  이라는 bool 변수 하나 가 있으니 초기화 하기위해 생성자에서도 bool 타입의 인자를 받아야함 
  • 생성자 안에서 self는 자기 자신의 프로퍼티를 의미한다.

  • 추가로 bool 타입 변수인 inConvertible 을 생성
  • 생성자에서 이 변수를 위한 매개변수가 추가된 모습
  • 근데 여기서 마무리 하면 부모 클래스의 초기화를 할 수 없어서 에러가 날 것임

  • isElectric, isConvertible 생성자로 두개의 파라미터를 받음
  • 하나는 자신의 프로퍼티 초기화에, 다른 하나는 부모 클래스의 프로퍼티 초기화에 사용
  • super
    • self 키워드랑 비슷
    • 부모클래스의 생성자, 메서드, 프로퍼티에 접근할 수 있도록  Swift에서 자동으로 제공

좋은 차를 타시네요.

성공한 모습. 만약 자식 클래스에서 별다른 프로퍼티를 초기화하지 않을 경우엔 부모 클래스의 생성자를 자동으로 물려받아 쓴다.

 

class 복사

  • 복사한 클래스간에는 같은 데이터를 공유
    • 한 복사본의 프로퍼티가 업데이트되면 다른 원본 및 복사본에 영향
  • class 가 참조 타입(reference type)이기 때문에 발생하는 현상
  • 클래스의 모든 복사본은 기저에 같은 데이터를 기반으로함

  • class 복사를 알아보기 위한 간단한 실험
  • 원본 user1 을 복사본 user2에 복사한 후, user2 의 프로퍼티를 업데이트하고나서 원래의 프로퍼티를 보는 실험

"Taylor" 가 두번찍힌다. 

바꾸지않은 인스턴스의 값도 바뀌는 현상이 버그 같지만 기능이다. 
이 기능은 공통의 데이터를 앱 전체에서 공유할 수 있도록 해주기 때문이다. SwiftUI 는 Data 의 타입의 경우 class에 많이 의존하는데 공유가 매우 쉽기 때문이다. 

복사는 하지만 따로 데이터를 들고 싶다면 DeepCopy 라는 것을 이용해야한다. 

  • 코드를 보면 copy() 메서드가 User를 리턴하는 것을 볼 수 있는데,
  • 이는 공유되는 user가 아니라 프로퍼티의 값만 동일한 새로운 User 를 생성하는 것이다.
  • 추후에 복사본의 값을 바꾸면 변경된 값이 공유되지 않을 것이다. 

deinitializer 생성

  • class 가 생성될 때 호출되는 initializer 의 반대 개념
  • 의무적이진 않음
  • 고유함 - func 로 선언 X
  • 파라메터, 리턴값, () X 
  • 마지막 복사본이 파괴되면 자동 호출
    • 절대 직접 deinitializer 를 호출 X, 시스템이 호출
  • struct 는 deinitializer X, 복사본이 없기 때문(하나하나가 원본)

간단한 실험

  • Scope 의 개념
    • if 문 안에 선언한 변수는 밖에서 접근 불가
    • for 문 마찬가지 
    • 함수 안에 선언한 변수 밖에서 접근 불가
  • 이번엔 for문의 Scope 를 이용해서 deinitailzer 실험

User 클래스 선언
For 문 안에서 생성

  • Scope 의 지역성에 의해서 For 문이 끝나는 동시에 생성한 각각의 User 인스턴스는 deinitializer 를 호출 할 것 임

  • for 문이 끝나는 시점이 아닌, 배열의 값이 clear 되는 순간 User 인스턴스들이 deinitializer 될것임

 

var 변수가 class 내부에서 동작하는 방법

  • class의 복사본들은 이정표처럼 원본의 데이터를 가리킴
  • 복사본 중 하나가 값을 변경할 경우 모두 그 변경된 데이터를 참조함
  • 이를 이해 하기 위해 클래스가 var  변수를 어떻게 대하는지 알 필요가 있음

  • 인스턴스의 var 변수 변경 O 
  • let 으로 선언된 user 인스턴스 변경 X 
  • 비유하자면, user 라는 표지판을 새웠는데 그 표지판에 이름만 바꿔 단 것임
  • 표지판이 향하고 있는 방향은 여전히 그대로고, 그 내부의 정보만 바뀜
  • 즉, let 으로 선언된 user 가 바뀐 것은 아님 - 내부 데이터는 바뀜
  • name 을 let으로 선언하면 ? 지워지지 않는 잉크로 표지판에 새겼기 때문에 안바뀜
  • 인스턴스도 var, 변수도 var 로 선언하면 어떻게 될까?

  • 프린트 결과 Paul 이 찍힐 것임
  • name 은 "Taylor" 로 바꿧지만, user 객체를 새로운 인스턴스로 갈아끼웠기 때문에 다시 "Paul" 로 초기화 됨
  • 인스턴스 var - name let 으로 하면, 인스턴스는 갈아낄 수 있는데 값은 변경 못함

발로 그린 테이블

  • 프로퍼티가 var 면
    • 변경 가능
    • 언젠가 변경 될 수 있음을 허용 
      • 인스턴스가 상수이든 변수이든 변경에 열려있음
  • 프로퍼티가 let 이면
    • 변경 불가
    • 변경의 가능성 X 
  • struct 와 차이 
    • struct 는 mutating 을 붙여야 함수가 var 프로퍼티 변경 가능
    • class 는 그런 것 없음
      •  클래스는 변수의 인스턴스가 let 이든 var 이든 프로퍼티를 변경 가능 
      • struct 는 내부의 data 를 바꾸려고 하면 암시적으로는 struct 의 인스턴스 자체를 바꾼다. 

이 실험은 user1 을 Person 스트럭트로 만들고, user2 에 복사를 하고 한번 출력

user2 의 name 을 변경하고 다시 출력 한 실험이다.
복사만 하였을 땐 user1 과 user2가 같다고 표시를 했지만, 
user2의 값을 변경하였을 땐 서로 다른 객채라고 한다. 

Person 의 name 프로퍼티가 변경되면 인스턴스 자체가 변경된 것과 같다. 

결론은 class 는 내부의 값이 달라져도 그 인스턴스의 identity 에는 영향을 주지 않지만, 
struct 는 내부의 값이 달라지면 그 자체로 다른 인스턴스가 되어버린다. 

Comments