iOS/Swift

[Swift] 구조체 Struct / 클래스 Class

YEN_ 2023. 11. 16. 16:38

구조체

struct 구조체이름 {

}

 

클래스

class 클래스이름 {

}

 

- Camel Case

  • 구조체 이름의 첫 글자는 대문자로 시작한다
  • 언더바는 사용하지 않는게 좋다 ❌

 

Properties

  • 내부에서 정의된 변수, 상수
  • 특정 값을 저장하기 위함

Method

  • 내부에서 정의된 특정 기능을 담당하는 함수

 

- Properties& Method 구조체 EX

struct Resolution {
    var width = 0 
    var height = 0
    
    func desc() -> String {
        return "Resolution 구조체"
    }
}
  • 저장 프로퍼티 - width, height
  • 0으로 초기화 되었으므로 타입추론규칙에 의해 Int 데이터 타입으로 추론된다

 

- Properties& Method 클래스 EX

class VideoMode {
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    func desc() -> String {
        return "VideoMode 클래스"
    }
}
  • name은 초기값 할당되지 않음 + 옵셔널 타입 프로퍼티
  • 이런 경우에는 nil 값으로 초기화된다 (값이 존재하지 않음)

 

Instance

  • 클래스로부터 만들어진 객체
  • 실제로 메모리에 할당되어서 동작할 수 있는 모양을 갖춘 것 
// Resolution 구조체에 대한 인스턴스를 생성하고 상수 insRes에 할당
let insRes = Resolution()

// VideoMode 클래스에 대한 인스턴스를 생성하고 상수 insVMode에 할당
let insVMode = VideoMode()
  • 구조체/클래스 뒤에 오는 소괄호는 인스턴스 생성 연산자
  • . 점 구문(Dot Syntax)를 이용해서 인스턴스 하위 객체(혹은 프로퍼티의 값)에 접근 / 할당 할 수 있다
<인스턴스 이름>.<프로퍼티 이름>

// 연속해서 사용할 수 있다
vMode.res.width
  • 점 구문을 연속으로 연결해서 사용할 수 있다
  • 이것을 체인 Chain 방식이라고 한다

 

Initialize

  • 명시적인 초기화
    • 프로퍼티를 선언하는 동시에 초기값 지정
    • 초기화 메서드 안에서 프로퍼티의 초기값 지정
  • 모든 프로퍼티는 인스턴스가 생성되는 시점까지는 반드시 초기화되어야 한다 ⭐ 
  • 명시적인 초기화의 경우가 아니라면, 옵셔널 타입으로 선언해서 자동으로 nil을 할당받게 만들어주어야 한다

 

- 구조체 Initialize

  • 멤버와이즈 초기화 구문 Memberwise Initialiser
    • 모든 프로퍼티의 값을 인자값으로 입력받아 초기화하는 기본 초기화 구문
    • 인스턴스를 생성하는 형식 정의
    • 입력된 인자값을 이용해서 프로퍼티를 초기화
// width와 height를 매개변수로 하여 Resolution 인스턴스를 생성
let defaultRes = Resolution(width: 1024, height: 768)

print("width : \(defaultRes.width), height : \(defaultRes.height)")

// 실행 결과
width : 1024, height : 768
  • 빈 괄호 초기화 구문
    • 아무런 인자값을 입력받지 않음
    • 어떤 프로퍼티도 초기화하지 않음
    • 단순히 구조체의 인스턴스를 생성하기만 한다
    • 사용하려면, 모든 프로퍼티는 선언과 동시에 초기화가 되어 있어야 한다

 

- 클래스 Initialize

  • 빈 괄호 초기화 구문 사용
  • 클래스의 초기화 원칙
    1. 모든 프로퍼티는 정의할 때 초기화를 한다 OR 옵셔널 타입으로 선언한다
    2. 인스턴스를 생성할 때는 클래스 명 뒤에 소괄호() - 인스턴스 생성 연산자 - 를 붙여준다

 

구조체의 값 전달 Value Type

  • 복사에 의한 전달
  • 생성하는 모든 구조체 인스턴스들이 상수나 변수에 할당될 때 / 함수의 인자값으로 사용될 때 복사해서 사용된다
  • 정수, 문자열, 배열 또는 딕셔너리 등 기본 자료형들은 모두 복사를 통해 값이 전달된다
    • 해당 자료형이 구조체로 구현되었기 때문에
  • 변수에 대입된 인스턴스와 기존의 인스턴스는 서로 독립적
    • 구조체 인스턴스를 변수에 대입하면 기존의 인스턴스가 그대로 대입되는 것이 아니라 이를 복사한 새로운 값이 대입
    • 서로에게 전혀 영향을 미치지 않는다
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048

#1
print("cinema 인스턴스의 width 값은 \(cinema.width)입니다")

// 실행 결과
cinema 인스턴스의 width 값은 2048입니다 

#2
print("hd 인스턴스의 width 값은 \(hd.width)입니다")

// 실행 결과
hd 인스턴스의 width 값은 1920입니다

 

클래스의 값 전달 Reference Type

  • 참조에 의한 전달
  • 참조
    • 인스턴스가 저장된 메모리 주소 정보가 전달
    • 항상 메모리 주소를 사용해 객체 자체를 전달
    • 단순히 값을 넘긴다고 생각해도 된다
  • 클래스는 참조 타입이기 때문에, 한 곳에서 수정할 경우 다른 곳에서도 적용된다
  • 다양한 인자값에서 동시에 참조할 수 있어서, 메모리에 대한 이슈가 있다

 

- 클래스의 값 전달 Reference Type EX

let video = VideoMode()
video.name = "Original Video Instance"

print("video 인스턴스의 name 값은 \(video.name!)입니다.")
// video 인스턴스의 name 값은 Original Video Instance입니다.


let dvd = video
dvd.name = "DVD Video Instance"

print("video 인스턴스의 name 값은 \(video.name!)입니다")
// video 인스턴스의 name 값은 DVD Video Instance입니다
  • 상수 video 는 클래스 VideoMode를 초기화하여 생성한 인스턴스이다
  • 인스턴스 video의 name 값을 입력해주었고, 설정되었다
  • 상수 dvd 는 인스턴스 video를 할당받았다
  • dvd의 속성값을 변경하였더니 video 의 속성값(프로퍼티)도 변경되었다
func changeName(v: VideoMode) {
    v.name = "Function Video Instance"
}

changeName(v: video)
print("video 인스턴스의 name 값은 \(video.name!)입니다")
// video 인스턴스의 name 값은 Function Video Instance입니다
  • changeName 함수가 인자값으로 video 인스턴스를 전달받아서 프로퍼티 값을 변경하였다
  • inout 키워드가 없지만, 클래스 타입이기 때문에 원본 인스턴스의 참조가 전달되었다

 

ARC

  • Auto Reference counter
  • 지금 클래스 인스턴스를 참조하는 곳이 모두 몇 군데인지 자동으로 카운트해주는 객체
    • 인스턴스를 모니터링하며 변수나 상수, 함수의 인자값으로 할당되면 카운트를 1 증가
    • 해당 변수나 상수들이 종료되면 카운트를 1 감소
    • 위의 두가지 계산을 통해 인스턴스의 참조 수를 파악
    • 인스턴스의 참조 카운트가 0이 되면 메모리 해제 대상으로 간주하여 적절히 메모리에서 해제

 

클래스 인스턴스의 비교 연산자

  • 동일 인스턴스인지 비교 : ===
  • 동인 인스턴스가 아닌지 비교 : !==
if (video === dvd) {
    print("video와 dvd는 동일한 VideoMode 인스턴스를 참조하고 있군요")
} else {
    print("video와 dvd는 서로 다른 VideoMode 인스턴스를 참조하고 있군요")
}

// 실행 결과
video와 dvd는 동일한 VideoMode 인스턴스를 참조하고 있군요
 
  • video는 VideoMode 클래스의 인스턴스를 참조받았다
  • dvd는 video 인스턴스를 참조받았다
  • 두 상수는 동일한 클래스 인스턴스를 참조한다
  • 비교 연산 true
let vs = VideoMode()
let ds = VideoMode()

if (vs === ds) {
    print("vs와 ds는 동일한 VideoMode 인스턴스를 참조하고 있습니다")
} else {
    print("vs와 ds는 서로 다른 VideoMode 인스턴스를 참조하고 있습니다")
}

// 실행 결과
vs와 ds는 서로 다른 VideoMode 인스턴스를 참조하고 있습니다
  • vs 인스턴스와 ds 인스턴스는 각각 VideoMode 클래스의 인스턴스를 새롭게 생성하여 참조받았다
  • 동일한 타입의 인스턴스이지만, 같은 메모리 주소를 참조하는 것은 아니다
  • 비교 연산 false

 

구조체를 사용하는 경우

  1. 서로 연관된 몇 개의 기본 데이터 타입들을 캡슐화하여 묶는 것이 목적일 때
  2. 캡슐화된 데이터에 상속이 필요하지 않을 때
  3. 캡슐화된 데이터를 전달하거나 할당하는 과정에서 참조 방식보다는 값이 복사되는 것이 합리적일 때
  4. 캡슐화된 원본 데이터를 보존해야 할 때
  • 위의 4가지에 해당하지 않는다면 클래스를 정의해서 사용하는 게 좋다