Swift - 클래스와 구조체

임성빈·2022년 3월 13일
0

Swift

목록 보기
9/26
post-thumbnail
post-custom-banner

클래스와 구조체는 프로그램의 코드를 조직화하기 위해 일반적으로 사용한다.
interface 파일과 implementation 파일을 분리해서 만들지 않아도 된다. 하나의 파일에 클래스와 구조체를 정의하면, Swift가 자동으로 해당 클래스와 구조체를 사용할 수 있는 인터페이스를 생성한다.


클래스와 구조체의 비교

Swift에서 클래스와 구조체는 많은 공통점이 있다.

  • 값을 저장하기 위한 프로퍼티 정의
  • 기능을 제공하기 위한 메소드 정의
  • subscript 문법을 이용해 특정 값을 접근할 수 있는 subscript 정의
  • 초기 상태를 설정할 수 있는 initializer 정의
  • 기본 구현에서 기능 확장
  • 특정한 종류의 표준 기능을 제공하기 위한 프로토콜 순응

구조체로는 가능하지 않고 클래스만 가능한 기능

  • 상속 (inheritance): 클래스의 여러 속성을 다른 클래스에 물려줌
  • 타입 캐스팅 (Type casting): 런타임에 클래스 인스턴스의 타입을 확인
  • 소멸자 (Deinitializers): 할당된 자원을 해제시킴
  • 참조 카운트 (Reference counting): 클래스 인스턴스에 하나 이상의 참조가 가능

구조체는 다른 코드로 전달될 때 항상 복사되서 전달되고, 참조 카운트를 사용하지 않는다.

선언 문법

클래스와 구조체 둘 다 비슷한 선언 문법을 갖고 있다.
클래스는 class 키워드, 구조체는 struct 키워드를 이름 앞에 적어 선언할 수 있다.

class SomeClass {
    // 클래스 내용
}

struct SomeStructure {
    // 구조체 내용
}

새로운 클래스나 구조체를 선언할 때마다 Swift 에서 완전 새로운 타입을 선언하는 것이다. 그래서 이름을 다른 표준 Swift 타입(String, Int, Bool)과 같이 UpperCamelCase 이름으로 선언한다. 반대로 프로퍼티나 메소드는 lowerCamelCase 이름으로 선언한다.

클래스와 구조체 선언의 예시

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

class VideoMode {
    var resolution = Resolution() 
    // 위 Resolution 구조체를 값으로 사용
    
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

구조체 Resolution의 프로퍼티 widthheight 는 초기값으로 0을 할당 했기 때문에 Swift의 타입추론에 자동으로 Int 타입을 갖게 된다.

클래스와 구조체 인스턴스

클래스와 구조체 이름 뒤에 빈 괄호를 적으면 각각의 인스턴스를 생성할 수 있다.

let someResolution = Resolution()
// 구조체 인스턴스 생성

let someVidroMode = VideoMode()
// 클래스 인스턴스 생성

프로퍼티 접근

점(.) 문법을 통해 클래스와 구조체 인스턴스의 프로퍼티에 접근할 수 있다.

print("The width of someResolution is \(someResolution.width)")
// "The width of someResolution is 0"

점문법을 이용해 값을 할당할 수 있다.

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// "The width of someVideoMode is now 1280"

Swift에서는 하위레벨의 구조체 프로퍼티도 직접 설정할 수 있다.

구조체 타입의 맴버 초기화

모든 구조체가 초기화될 때 프로퍼티를 선언할 수 있는 초기자를 자동으로 생성해 제공한다.
아래와 같은 맴버의 초기화는 구조체 안에 widthheigth 프로퍼티만 정의했다면 자동으로 사용 가능하다는 의미이다.

let fhp = Resolution(width: 1280, height: 960)

구조체와 열거형은 값 타입

값 타입이라는 뜻은, 이것이 함수에서 상수나 변수에 전달될 때 그 값이 복사되서 전달 된다는 의미이다. Swift에서 모든 구조체와 열거형 타입은 값 타입이다.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

위 코드의 첫 줄에서 Resolution 구조체의 인스턴스 hd 를 선언한다. 그리고 hdcinema 라는 변수에 할당한다. 그러면 이 cinemahd 를 할당하는 순간 복사하는 것이 때문에 같지 않고 완전히 다른 인스턴스가 된다.

열거형에서의 동작도 동일하다.

enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"

클래스는 참조 타입

값 타입과 달리 참조 타입은 변수나 상수에 값을 할당을 하거나 함수에 인자로 전달할 때 그 값이 복사되지 않고 참조된다.
참조의 의미는 그 값을 갖고 있는 메모리를 바라보고 있다는 뜻이다.

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

tenEighty 라는 VideoMode 클래스 인스턴스를 생성하고 각 프로퍼티에 값을 할당한다.

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

alsoTenEighty 라는 상수를 만들고 위에서 선언한 tenEighty 클래스 인스터스를 할당한다. 그리고 alsoTenEightyframeRate 값을 30으로 변경한다.

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

결과가 30이 나오는 이유는 값을 복사한 것이 아니라 참조한 것이기 때문이다.
그래서 실제로 alsoTenEightytenEighty 는 동일한 메모리 주소를 바라보고 참조하는 것이다.
의아하게 생각할 수 있는 것은 let alsoTenEight = tenEighty 로 상수로 선언했는데 어떻게 값을 변경할 수 있었는지 인데, 이것은 alsoTenEighty 자체를 변경하는 것이 아니라 그것이 바라보는 값을 변경하는 것이기 때문에 가능하다.

식별 연산자

클래스는 참조 타입이기 때문에 여러 상수와 변수에서 같은 인스턴스를 참조할 수 있다. 상수와 변수가 같은 인스턴스를 참조하고 있는지 비교하기 위해 식별 연산자를 사용한다.

  • === : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 true
  • ! == : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 true
if tenEighty === alsoTenEighty {
	print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
} 
// "tenEighty and alsoTenEighty refer to the same VideoMode instance."

식별 연산자(===)는 비교 연산자(==)와 같지 않다.
식별 연산자는 참조를 비교하는 것이고, 비교 연산자는 값을 비교한다.


클래스와 구조체의 선택

클래스와 구조체 모두 프로그램의 코드를 조직화 하고 특정 타입을 선언하는데 사용된다. 그리고 앞서 설명했던 것처럼 클래스 인스턴스가 인자로 사용될 때는 참조가 넘어가고 구조체는 값이 넘어간다.

언제 클래스와 구조체를 사용해야 할까?

일반적으로 다음 조건 중 1개 이상을 만족하면 구조체를 사용하는 것을 고려해 볼 수 있다.

  • 구조체의 주 목적이 관계된 간간한 값을 캡슐화 하기 위한 것인 경우
  • 구조체의 인스턴스가 참조되기 보다 복사되기를 기대하는 경우
  • 구조체에 의해 저장된 어떠한 프로퍼티가 참조되기 보다 복사되기를 기대하는 경우
  • 구조체가 프로퍼티나 메소드 등을 상속할 필요가 없는 경우

위에 기술된 경우를 제외한 다른 모든 경우에는 구조체가 아니라 클래스를 사용하면 된다.


String, Array, Dictionary의 할당과 복사 동작

Swift에서는 String, Array, Dictionary 같은 기본 데이터 타입이 구조체로 구현 되어있다. 그렇다는 의미는 이 값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 이 값이 복사 된다는 것이다. 반면 FoundationNSString, NSArray, NSDictionary는 클래스로 구현 되어있다. 그래서 이 데이터들은 항상 할당 되거나 전달될 때 복사 되지 않고 참조가 사용된다.

profile
iOS 앱개발
post-custom-banner

0개의 댓글