iOS/swift

[Swift 공식문서 정리] - 클래스와 구조체 (Classes and Structures)

skyiOS 2022. 2. 20. 02:59
반응형

클래스와 구조체(Classes and Structures)

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

반응형

클래스와 구조체의 비교( Comparing Classes and Structures)

swift의 클래스와 구조체는 많은 공통점이 있습니다.

1. 변수나 상수를 사용하여 값을 저장하기 위한 프로퍼티를 정의 할 수 있다.
2. 함수를 사용하여 기능을 제공하기 위한 메소드를 정의할 수 있다.
3. subscript 문법을 이용해 특정 값을 접근할 수 있는 서브스크립트(subscript)를 정의할 수 있다.
4. 객체를 원하는 초기 상태로 설정할 수 있는 초기화(initializer)를 정의할 수 있다.
5. 객체의 기본 구현에서 기능을 추가하는 확장(extends) 구문을 사용할 수 있다. 
6. 특정한 종류의 표준 기능을 제공하기 위한 프로토콜을  구현할 수 있다.

구조체는 없고, 클래스에서만 가능한 기능이 있습니다.

1. 상속(Ingeritance) : 클래스의 여러 속성을 다른 클래스에 물려줄 수 있다.
2. 타입 캐스팅 (Type casting) : 실행 시 컴파일러가 클래스의 인스턴스의 타입을 미리 파악하고 검사할 수 있다.
3. 소멸자(Deinitializers) : 할당된 자원을 해제(free up) 시킴
4. 참조 카운트(Reference countiong) : 클래스 인스턴스에 하나 이상의 참조가 가능
(구조체는 다른 코드로 전달될 때 항상 복사되서 전달되고, 참조 카운트( Reference counting)를 사용하지 않습니다.
클래스만 지원하는 기능들은 복잡성을 증가시킨다.
보통은 구조체를 선호하고 이러한 기능이 필요할 때 클래스를 사용한다고 합니다.
자세한 것은 클래스와 구조체의 선택에서 다룹니다.

선언 문법 (Definition Syntax)

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

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()  // 위 Resolution 구조체를 값으로 사용
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}
구조체나 클래스를 선언할 때는 Swift 에서 정한 네이밍 룰(Naming Rule)을 지켜주는 것이 좋다.
클래스나 구조체는 대문자로 시작하는 UpperCamelCase표기법을 사용하여 (SomeClass, SomeStructure 등)으로 선언합니다.
프로퍼티나 메소드는 소문자로 시작하는 lowerCamelCase표기법을 사용하여(frameRate, incrementCount 등)으로 선언합니다.

클래스와 구조체 인스턴스(Class and Structure Instances)

정의된 클래스나 구조체를 실제로 사용하려면 이처럼 인스턴스를 만들고, 인스턴스를 이용하여 값을 저장하거나 처리해야 합니다.

let someResolution = Resolution()    // 구조체 인스턴스 생성
let someVideoMode = VideoMode()    // 클래스 인스턴스 생성

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

프로퍼티 접근(Accessing Properties)

만들어진 구조체,클래스 인스턴스의 프로퍼티에 접근을 하기 위해서는 ( . ) 을 사용하면 됩니다.

점 앞이나 뒤에는 공백이 없어야 합니다.
print("The width of someResolution is \(someResolution.width)")
// "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)")
// "The width of someVideoMode is now 1280" 이 출력

구조체형의 멤버 초기화 (Memberwise Initializers for Structure Types)

모든 구조체는 초기화시 프로퍼티를 선언할 수 있는 초기자를 자동으로 생성해 제공합니다.

let vga = Resolution(width: 640, height: 480)

구조체와 열거형은 값 타입(Structures and Enumerations Are Value Types)

값 타입(Value Type)은 함수에서 상수나 변수에 전달될 때 그 값이 복사되서 전달 된다는 의미입니다.
Swift의 모든 기본유형(Int,Int, Double, Float, Bool, String, Array, Dictionary)은 값 타입이며 구조체로 구현되어있습니다.
모든 구조체와 열거형은 값 타입이며, 생성한 모든 구조체 및 열거형의 인스턴스와 속성으로 포함된 모든 값 타입은 항상 복사되어 전달됩니다.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print("cinema is now \(cinema.width) pixels wide")
// "cinema is now 2048 pixels wide" 출력
print("hd is still \(hd.width) pixels wide")
// "hd is still 1920 pixels wide" 출력
1. 위에서 선언한 Resolution 구조체의 인스턴스로 hd를 선언합니다.
2. 하나의 변수 cinema를 선언해서 hd 인스턴스를 할당합니다. 그런 뒤 cinema의 width 프로퍼티를 수정합니다.
3. 구조체는 값 타입이기 때문에 cinema에 hd 인스턴스를 할당할 때 복사가 일어납니다. (cinema와 hd는 서로 다른 인스턴스)
4. cinema의 프로퍼티 값을 수정해도 hd에는 아무런 영향이 없다.

위의 현상을 그림으로 표현하면 아래와 같다.

 

enum CompassPoint {
    case north, south, east, west
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection = .east
if rememberedDirection == .west {
    print("The remembered direction is still .west")
}
// "The remembered direction is still .west" 출력

열거형에서도 똑같습니다.

현재 방향 currentDirection west를 할당하고 기억한 방향 rememberedDirection에 현재 방향 currentDirection을 저장합니다. 현재 방향 currentDirection을 east로 변경합니다.그리고 나서 기억하고 있는 인스턴스 rememberedDirection를 살펴보면 여전히 원본을 복사할 때의 값 west를 갖고 있는 것을 확인할 수 있습니다.

즉, 다른 인스턴스의 변화는 그 인스턴스에만 영향을 끼치고 그것과 가른 인스턴스에는 아무런 영향도 없다는 것을 알 수 있습니다.

클래스는 참조 타입 (Classes Are reference Types)

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

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)")
// "The frameRate property of tenEighty is now 30.0" 출력
1. tenEighty 라는 VideoMode 클래스 인스턴스를 생성하고 각 프로퍼티에 값을 할당합니다.
2. alsoTenEighty 라는 상수를 만들고 그것에 위에서 선언한 tenEighty 클래스 인스턴스를 할당한 후 값을 30으로 변경합니다.
3. 값을 확인해 보면 alsoTenEighty의 프로퍼티 값도 수정되고, tenEighty의 값도 수정됩니다.

 

alsoTenEighty 자체를 변경하는 것이 아니라 그것이 바라보는 값을 변경하는 것이다.

식별 연산자 (Identity Operators)

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

 

  • === : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 참
  • !== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 참

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

 

if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// "tenEighty and alsoTenEighty refer to the same VideoMode instance." 출력

포인터 (Pointers)

C, C++ 혹은 Objective-C를 사용해 보신 분이라면 이 참조라는 것이 포인터라고 생각하실 수 있습니다. Swift에서 상수나 변수가 특정 타입의 인스턴스를 참조하고 있다는 것은 위 포인터와 유사합니다. 하지만 표현에는 다른 점이 있는데요 C, C++, Objective-C에서 포인터는 실제 메모리를 직접 가르키고 있고 키워드(*)로 표시하지만 Swift는 참조를 가르키기 위해 를 사용하지 않고 대신 다른 상수와 변수처럼 정의해 사용합니다.


클래스와 구조체의 선택 (Choosing Between Classes and Structures)

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

 

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

 


String, Array, Dictionary의 할당과 복사 동작 (Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)

Swift에서는 String, Array, Dictionary 같은 기본 데이터 타입이 구조체로 구현 돼 있습니다. 값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 이 값이 복사 된다는 것입니다.(하지만 실제로 Swift에서는 최적의 성능을 위해 실제 필요할 때만 데이터가 복사됩니다.)

Foundation NSString, NSArray, NSDictionary는 클래스로 구현 돼 있습니다. 그래서 이 데이터들은 항상 할당 되거나 전달될 때 복사 되지 않고 참조가 사용됩니다.(표준 라이브러리는 포인터와 직접 상호 작용해야 하는 경우 사용할 수 있는 포인터 및 버퍼 유형을 제공합니다)

반응형