21. 10. 18 구조체와 클래스

15058 단어 TIL일일회고TIL

Structures and Classes

  • 구조체와 클래스의 공통점
    • 값을 저장할 프로퍼티를 정의할 수 있다.
    • 기능을 제공하는 메소드를 정의할 수 있다.
    • subscripts 문법을 통해 프로퍼티에 접근하도록 subscripts를 정의할 수 있다.
    • 초기화 상태를 설정하기 위한 이니셜라이저를 정의할 수 있다.
    • 기본 구현 이상으로 기능을 확장하도록 extension을 사용할 수 있다.
    • 특정 종류의 표준 기능을 제공하는 프로토콜을 준수할 수 있다.
  • 구조체와 클래스의 차이점
    • 클래스에는 구조체에 없는 추가 기능이 있다.
    • 상속을 통해 한 클래스가 다른 클래스의 특성을 상속할 수 있다. (구조체는 안됨)
    • 타입 캐스팅을 사용하면 런타임에 클래스 인스턴스의 타입을 확인하고 해석할 수 있다.
    • Deinitializer는 클래스의 인스턴스가 할당된 리소스를 해제할 수 있도록 한다.
    • 참조 카운팅은 클래스 인스턴스에 대한 둘 이상의 참조를 허용한다.
  • 클래스가 지원하는 추가 기능은 복잡성이 증가한다.
  • 일반적으로 구조체는 추론하기 쉽기 때문에 선호되고 필요할 때 클래스를 사용한다. 대부분의 사용자 정의 데이터 타입은 구조체 및 열거형으로 되어있다.
/* Resolution과 VideoMode의 모양만 설명하는 것.
특정한 실체를 만들려면 구조체나 클래스의 인스턴스를 만들어야 한다. */

struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}
// 클래스 초기화
  • VideoMode 클래스에는 4개의 가변 저장 프로퍼티가 있다.
  • var resolution = Resolution()은 구조체 Resolution의 프로퍼티 타입을 유추하는 새로운 Resolution 구조 인스턴스로 초기화 된다.
  • name은 옵셔널 타입이기 때문에 기본값으로 nil 또는 "이름 값 없음"이 자동으로 지정된다.

Structure and Class Instances

// 인스턴스 만드는 방법

let someResolution = Resolution()
let someVideoMode = VideoMode()
  • 구조체와 클래스는 모두 새 인스턴스에 대해 이니셜라이저 구문을 사용해 모든 프로퍼티가 기본값으로 초기화된 구조체 또는 클래스의 인스턴스를 생성한다.
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"
// 이렇게도 써볼 수 있음

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
// 프로퍼티에 새로운 값 할당해서도 사용 가능
  • 인스턴스의 프로퍼티를 사용하려면 someResolution.width 와 같은 방법으로 접근할 수 있다.
  • 이 예제에서 someResolution.widthsomeResolution의 너비 프로퍼티를 참조하고 기본 초기 값인 0을 반환한다.

Memberwise Initializers for Structure Types

  • 모든 구조체에는 자동으로 생성된 멤버별 이니셜라이저가 있어 새 구조체 인스턴스의 멤버 속성을 초기화하는데 사용할 수 있다. 새 인스턴스의 속성에 대한 초기 값은 이름으로 멤버별 이니셜라이저에 전달할 수 있다.
let vga = Resolution(width: 640, height: 480)
  • 구조체와 달리 클래스 인스턴스는 기본 멤버별 이니셜라이저를 받지 않는다.

구조체와 열거형은 값 타입이다.

  • 값 타입은 변수나 상수에 할당되거나 함수에 전달될 때 값이 복사되는 타입이다.
  • Swift의 모든 기본 타입(integers, floating-point numbers, Booleans, strings, arrays and dictionaries)은 값 타입이며 구조체로 구현되어 있다.
  • 생성한 모든 구조체 및 열거형 인스턴스와 프로퍼티로 포함된 모든 값 타입은 코드에서 전달될 때 항상 복사된다는 의미.
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

cinema.width = 2048
// 이렇게 cinema의 넓이 값을 변경 가능

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
// cinema의 넓이 값이 실제로 바뀌었지만,

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"
// hd의 넓이는 변하지 않고 그대로!
  • Resolution은 구조체이기 때문에 기존 인스턴스의 복사본이 만들어지고 이 새 복사본이 cinema에 할당 된다. hdcinema의 너비와 높이가 동일하지만 완전히 다른 두 인스턴스이다.
  • 열거형에서도 똑같이 적용된다.

클래스는 참조 타입이다.

  • 값 타입과 달리 참조 타입은 변수나 상수에 할당되거나 함수에 전달될 때 복사되지 않는다. 복사본 대신 동일한 기존 인스턴스에 대한 참조가 사용된다.
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

  • 이 예제는 참조 타입에 대해 추론하기가 더 어려울 수 있음을 보여준다. 프로그램 코드에서 tenEightyalsoTenEighty가 멀리 떨어져 있으면 비디오 모드가 변경되는 모든 방법을 찾기 어려울 수 있다. 또한, tenEighty를 사용할 때마다 alsoTenEighty를 사용하는 코드에 대해서도 생각해야 하고 그 반대의 경우도 마찬가지이다. 이와 반대로, 값 타입은 동일한 값과 상호작용하는 모든 코드가 소스파일에서 가까이 있기 때문에 추론하기가 더 쉽다.
  • tenEightyalsoTenEighty는 상수로 선언되어 있지만 상수 자체의 값은 실제로 변경되지 않기 때문에 여전히 tenEighty.frameRatealsoTenEighty.frameRate를 변경할 수 있다. tenEightyalsoTenEighty 자체는 VideoMode 인스턴스를 "저장"하지 않는다. 대신 둘 다 뒤에서 VideoMode 인스턴스를 참조한다. 변경된 것은 해당 VideoMode에 대한 상수 참조 값이 아니라 기본 VideoModeframeRate 속성이다.

디이니셜라이저(Deinitializer)

  • 클래스의 인스턴스는 참조 타입이어서 더이상 참조할 필요가 없을 때 메모리에서 소멸된다. 이 때 소멸 직전에 deinit이라는 메서드가 호출되는데, 클래스 내부에 deinit 메서드를 구현하면 소멸되기 직전에 해당 메서드가 호출된다. deinit 메서드는 클래스당 하나만 구현할 수 있고, 매개변수와 반환 값을 가질 수 없다.
  • deinit에는 인스턴스가 메모리에서 소멸되기 직전에 처리할ㄹ 코드를 작성한다. (ex. 인스턴스 소멸 전에 데이터를 저장하거나 다른 객체에 소멸 사실을 알려야 할 때, deinit 메서드를 사용해야한다!)
class Coffee {
	var size: String
	var brand: String
	deinit {
		print("Coffee 클래스의 ㅇ니스턴스가 소멸됩니다.")
	}
}

var americano: Coffee? = Coffee()
americano = nil

Identity Operators

  • 클래스는 참조 타입이기 때문에 여러 상수와 변수가 뒤에서 클래스의 동일한 단일 인스턴스를 참조할 수 있다. (구조체와 열거형은 상수나 변수에 할당되거나 함수에 전달될 때 항상 복사되기 때문에 안됨)
  • 두 개의 상수 또는 변수가 클래스의 정확히 동일한 인스턴스를 참조하는지 여부를 확인하기 위해 Swift는 두가지 연산자를 제공한다.
    • 동일함 === / 동일하지 않음 !==

      if tenEighty === alsoTenEighty {
          print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
      }
      // Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
  • ===는 ==와 동일 한 것을 의미하지 않는다. ===은 클래스 타입의 두 상수 또는 변수가 정확히 동일한 클래스 인스턴스를 참조한다는 것을 의미한다. ==는 타입 디저이너가 정의한 같음의 적절한 의미에 대해 두 인스턴스가 값이 같거나 동등한 것으로 간주됨을 의미한다.

다음 조건 중 하나 이상에 해당된다면 구조체를 사용하면 좋다 (feat. apple guideline)

  • 연관된 간단한 값의 집합을 캡슐화 하는 것만이 목적일 때
  • 캡슐화된 값이 참조되는 것보다 복사되는 것이 합당할 때
  • 구조체에 저장된 프로퍼티가 값 타입이며 참조되는 것보다 복사되는 것이 합당할 때
  • 다른 타입으로부터 상속받거나 자신이 상속될 필요가 없을 때

이런 몇 가지 상황을 제외하면 클래스로 정의하여 사용한다. 대다수 사용자정의 데이터 타입은 클래스로 구현할 일이 더 많을 수 있다.

참고자료

Structures and Classes - The Swift Programming Language (Swift 5.5)

Swift - 구조체 클래스 - yagom's blog

Swift) 프로퍼티 정복하기 (2/4) - 연산 프로퍼티(Computed Property)

좋은 웹페이지 즐겨찾기