[Swift] 객체지향 프로그래밍과 스위프트 - 구조체와 클래스

koi·2022년 10월 7일
0
post-thumbnail

스위프트는 함수형 프로그래밍 패러다임을 강조하지만 못지않게 객체지향 프로그래밍 패러다임도 매우 중요합니다.
애플의 프레임워크는 대부분 객체지향 프로그래밍 패러다임에 근간을 두기에 스위프트에서 객체지향 프로그래밍 패러다임을 배제하기는 어려웠을 것입니다.

구조체와 클래스

  • 데이터를 용도에 맞게 묶어 표현하고자 할 때 유용
  • 프로퍼티와 메서드를 사용하여 구조화된 데이터와 기능을 가질 수 있음
  • 새로운 사용자 정의 데이터 타입을 만드는 것과 같음
    → 대문자 카멜케이스를 사용하여 이름을 지음
  • 스위프트는 구조체와 클래스의 모습과 문법이 거의 흡사함
  • 구조체의 인스턴스는 값 타입
  • 클래스의 인스턴스는 참조 타입
    (데이터 타입과 열거형은 모두 값 타입)
  • 소스 파일 하나에 여러 개의 구조체와 여러 개의 클래스를 정의하고 구현할 수 있음
  • 중첩 타입의 정의 및 선언이 가능함

구조체

구조체 정의

  • struct 키워드를 사용함
struct BasicInformation {
	var name: Stirng
    var age: Int
}

구조체 인스턴스의 생성 및 초기화

  • 구조체 정의를 마친 후 인스턴스를 생성하고 초기화하고자 할 때는 기본적으로 생성되는 멤버와이즈 이니셜라이저를 사용함
  • 구조체에 기본 생성된 이니셜라이저의 매개변수는 구조체의 프로퍼티 이름으로 자동 지정됨
  • 인스턴스가 생성되고 초기화된 후 프로퍼티 값에 접근하고 싶다면 마침표 (.) 를 사용하면 됨
  • 사용자 정의 이니셜라이저도 구현 가능함
  • 구조체를 상수로 선언하면 인스턴스 내부의 프로퍼티 값을 변경할 수 없음
  • 변수로 선언하면 내부 프로퍼티가 var로 선언된 경우 값을 변경할 수 있음
// 프로퍼티 이름(name, age)으로 자동 생성된 이니셜라이저를 사용하여 구조체를 생성합니다.
var tomaInfo: BasicInformation = BasicInformation(name: "toma", age: 2)
tomaInfo.age = 100
tomaInfo.name = "mato"

let tomaInfo: BasicInformation = BasicInformation(name: "toma", age: 2)
tomaInfo.age = 20 // error
tomaInfo.name = "mato" // error

클래스

클래스 정의

  • 클래스를 정의할 때는 class 키워드를 사용함
class Person {
	var height: Float = 0.0
    var weight: Float = 0.0
}

클래스 인스턴스의 생성과 초기화

  • 클래스를 정의한 후 인스턴스를 생성하고 초기화고자 할 때는 기본적인 이니셜라이저를 사용함
  • 사용자 정의 이니셜라이저도 구현 가능함
  • 구조체와 달리 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있음
var toma: Person = Person()
toma.height = 190
toma.weight = 40

let mato: Person = Person()
mato.height = 190
toma.weight = 40

클래스 인스턴스의 소멸

  • 클래스의 인스턴스는 참조 타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제됨
    → 이 과정을 소멸이라고 함
  • 소멸되기 직전 deinit(디이니셜라이저)이라는 메서드가 호출됨 (클래스 내부에 구현 가능)
  • deinit은 클래스당 하나만 구현할 수 있으며, 매개변수와 반환값을 가질 수 없음
    → 매개변수를 위한 소괄호도 적지않음
  • 예를 들어 인스턴스 소멸 전에 데이터를 저장한다거나 다른 객체에게 인스턴스 소멸을 알려야 할 때 deinit 메서드를 구현하면 됨
class Person {
	var height: Float = 0.0
    var weight: Float = 0.0
    
    deinit {
    	print("Person 클래스의 인스턴스가 소멸됩니다")
    }
}

var yagom: Person? = Person()
yagom = nil //  Person 클래스의 인스턴스가 소멸됩니다"

구조체와 클래스의 차이

같은 점

  • 값을 저장하기 위해 프로퍼티를 정의할 수 있음
  • 기능 실행을 위해 메서드를 정의할 수 있음
  • 서브스크립트 문법을 통해 구조체 또는 클래스가 갖는 값(프로퍼티)에 접근하도록 서브스크립트를 정의할 수 있음
  • 초기화될 때의 상태를 지정하기 위해 이니셜라이저를 정의할 수 있음
  • 초기구현과 더불어 새로운 기능 추가를 위해 익스텐션을 통해 확장할 수 있음
  • 특정 기능을 실행하기 위해 특정 프로토콜을 준수할 수 있음

다른 점

  • 구조체는 상속할 수 없음
  • 타입캐스팅은 클래스의 인스턴스에만 허용됨
  • 디이니셜라이저는 클래스의 인스턴스에만 활용할 수 있음
  • 참조 횟수 계산은 클래스의 인스턴스에만 적용됨

가장 큰 차이 점은 값 타입과 참조 타입이라는 것입니다.

값 타입과 참조 타입

  • 구조체는 값 타입
  • 클래스는 참조 타입
  • 값 타입참조 타입의 가장 큰 차이는 무엇이 전달되느냐
    • 어떤 함수의 전달인자로 값 타입의 값을 넘기면
      → 전달될 값이 복사되어 전달
    • 참조 타입이 전달인자로 전달되면
      → 값을 복사하지 않고 참조(주소)가 전달
  • 타 언어들처럼 참조라는 것을 표현해주기 위해 *를 사용하지는 않음
  • 참조 타입의 경우 할당될 때도 참조(주소)가 할당됨

구조체 예시

struct BasicInformation {
	let name: String
    var age: Int
}

var tomaInfo: BasicInformation = BasicInformation(name: "toma", age: 200)
tomaInfo.age = 20

// tomaInfo 값을 복사하여 friendInfo에 할당!
var friendInfo: BasicInformation = tomaInfo

print("toma's age: \(tomaInfo.age)") // 20
print("friend's age: \(friendInfo.age)") // 20

friendInfo.age = 999

print("toma's age: \(tomaInfo.age)") // 20
print("friend's age: \(friendInfo.age)") // 999
// tomaInfo의 값을 복사했기 때문에 별개의 값을 가짐

func changeBasicInfo(_ info: BasicInformation){
	var copiedInfo: BasicInformation = info
	copiedInfo.age = 1
}

changeBasicInfo(tomaInfo)
print("toma's age: \(tomaInfo.age)") // 20
//call by value 이기 때문에 따로 값이 변경되지 않음 

클래스 예시

class Person {
    var height: Float = 0.0
    var weight: Float = 0.0
}

var toma: Person = Person()
var friend: Person = toma // toma의 참조를 할당

print("toma's height: \(toma.height)") // 0.0
print("friend's height: \(friend.height)") // 0.0

friend.height = 185.5
print("toma's height: \(toma.height)") // 185.5
// friend는 toma를 참조하기 때문에 값이 같이 변함
print("friend는's height: \(friend.height)") // 185.5

func changePersonInfo(_ info: Person){
	info.height = 155.3
}

changePersonInfo(toma)
print("yagom height: \(toma.height)") // 155.3
//Call by reference 이기 때문에 실제 값이 변화되어 155.3 출력

식별 연산자의 사용

클래스의 인스턴스끼리 참조가 같은지 확인할 때는 식별 연산자를 사용

var toma: Person = Person()
let friend: Person = toma
let anotherFriend: Person = Person()

print(toma === friend)  // true
print(toma === anotherFriend) // false
print(friend !== anotherFriend) // true

스위프트의 기본 데이터 타입은 모두 구조체

  • 스위프트 표준 라이브러리에 포함되어있는 기본 타입(String, Bool, Int, Array, Dictionary, Set 등등)은 모두 구조체로 구현되어 있음
  • 기본 데이터 타입은 모두 값 타입이라는 뜻
  • 전달인자를 통해 데이터를 전달하면 모두 값이 복사되어 전달될 뿐, 함수 내부에서 아무리 전달된 값을 변경해도 기존 변수, 상수에는 영향을 끼치지 못함
  • 스위프트의 전달인자는 모두 상수로 취급되어 전달됨

구조체와 클래스 선택해서 사용하기

  • 구조체와 클래스는 새로운 데이터 타입을 정의하고 기능을 추가한다는 점이 같음.
  • 하지만 구조체의 인스턴스는 항상 값 타입이고, 클래스의 인스턴스는 참조 타입
  • 둘의 용도는 다름

💡 애플 가이드라인에서 다음 조건 중 하나 이상에 해당한다면 구조체를 사용하는 것을 권장한다.

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

스위프트의 기본 데이터 타입이 모두 구조체이기 때문에 모든 데이터 타입의 값을 복사하고 이용할 때 메모리를 비효율적으로 사용한다고 오해할 수 있다.
하지만 스위프트는 꼭 필요한 경우에만 진짜 복사를 한다.

컴파일러의 판단으로 복사가 필요없을 경우, 요소가 많은 큰 배열을 함수의 전달인자를 넘겨준다고 해서 꼭 모든 값을 메모리의 다른 공간에 복사해서 넣지 않을 수도 있다.

profile
Don't think, just do 🎸

0개의 댓글