객체지향의 4대 특징
- 캡슐화: 속성과 메서드를 하나로 묶고, 외부에서 접근 못하게 숨기는 것(private)
- 클래스에 저장되고 접근될 수 있는 데이터는 오직 해당 클래스 내에 정의된 메서드만을 통해서 된다.
- 클래스 내의 캡슐화된 데이터를 프로퍼티 또는 인스턴스 변수라고 한다.
- 상속: 기존 클래스의 속성과 기능을 물려받아 새로운 클래스를 만드는 것.
- 다형성: 같은 함수 이름이지만, 상황에 따라 다르게 작동함(오버라이딩, 프로토콜 등)
- 추상화: 핵심만 드러내고, 불필요한 것은 숨김
메서드 정의하기
본질적으로 클래스의 메서드는 클래스의 성격에 맞는 특정 작업을 수행하기 위해 호출되는 코드 루틴이다.
메서드는 타입 메서드와 인스턴스 메서드의 서로 다른 두 가지 형태로 나뉜다.
- 타입 메서드: 클래스 레벨에서 동작(예를 들어, 클래스의 새로운 인스턴스 생성하기)
- 인스턴스 메서드와 동일한 방법으로 선언되지만, 선언부 앞에 class 키워드가 붙는 점이 다르다.
- 인스턴스 메서드: 클래스의 인스턴스에 대한 작업(예를 들어, 두 개의 프로퍼티 변수에 대한 산술 연산을 하고 결과를 반환하는 것)만 한다.
- 자신이 속하게 된 클래스 내부에서 선언된다.
예를 들어, 계좌 잔고를 표시하기 위한 메서드의 선언부는 다음과 같다.
class BankAccount { var accountBalance: Float = 0 var accountNumber: Int = 0 func displayBalance() { print("Number \(accountNumber)") print("Current balance is \(accountBalance)") } } 이 메서드는 인스턴스 메서드이므로 class 키워드가 앞에 붙지 않았다.
BankAccount 클래스를 설계할 때 이 클래스에 저장할 수 있는 최대 금액을 알기 위하여 클래스 자신의 타입 메서드를 호출할 수 있다면 유용할 것이다.
이를 통해 어플리케이션이 클래스 인스턴스를 처음 생성하는 과정을 거치지 않아도 BankAccount 클래스가 새로운 고객의 정보를 저장할 수 있는지를 식별할 수 있게 해준다. 이 메서드의 이름을 getMaxBalance라고 할 것이며, 다음과 같이 구현된다.
class BankAccount { var accountBalance: Float = 0 var accountNumber: Int = 0 func displayBalance() { print("Number \(accountNumber)") print("Current balance is \(accountBalance)") } class func getMaxBalance() -> Float { return 10000.00 } } 좀 더 쉽게 이해하고 싶어 GPT에게 물어봤다. 먼저, "메서드"가 무엇인가?
- 메서드는 클래스 안에 들어있는 함수다. 이 함수는 그 클래스가 해야 할 일(기능)을 정의한다.
- 예를 들어, "은행 계좌" 클래스가 있으면
- 잔고 보기
- 돈 넣기
- 돈 빼기
- 이런 행동들을 함수로 만들어 클래스에 넣는다.
Swift에는 메서드가 두 종류 있다.
인스턴스 메서드
- 계좌 하나하나에 붙어있는 메서드
- 계좌 인스턴스를 만들어야 사용할 수 있다.
- 예: displayBalance() -> 그 계좌의 잔고를 출력한다.
let myAccount = BankAccount() myaccount.displayBalance() // 이렇게 써야 한다.
2. 타입 메서드 - 클래스 전체에 공통된 기능을 제공 - 인스턴스를 만들지 않아도 바로 사용할 수 있다. - 예: getMaxBalance() -> "모든" 계좌가 가질 수 있는 최대 금액 - 선언할 때 class func 또는 static func 키워드를 쓴다. ```swift BankAccount.getMaxBalance() // 이렇게 클래스 이름으로 호출 클래스 인스턴스 선언하기와 초기화하기
지금까지 우리가 한 것은 클래스에 대한 구조를 정의한 것이다. 이 클래스를 가지고 어떤 작업을 하려면 클래스의 인스턴스를 생성해야 한다. 이 과정에서의 첫 단계는 인스턴스에 대한 참조체를 저장할 변수를 선언하는 것이다.
var account1: BankAccount = BankAccount() - 이 코드를 실행하면 우리의 BankAccount 클래스의 인스턴스가 생성될 것이며, account1이라는 변수를 통해 접근할 수 있게 된다.
클래스 인스턴스 초기화하기와 소멸하기
클래스는 인스턴스를 생성하는 시점에 해야 할 초기화 작업이 있을 수 있다. 이 작업은 클래스의 init 메서드 안에 구현된다. BankAccount 클래스에서는 새로운 클래스 인스턴스가 생성될 때 계좌 번호와 잔액을 값으로 초기화할 것이다. 이를 위해 init 메서드를 다음과 같이 작성한다.
class BankAccount { var accountBalance: Float = 0 var accountNumber: Int = 0 init(number: Int, balance: Float) { accountNumber = number accountBalance = balance } func displayBalance() { print("Number \(accountNumber)") print("Current Balance is \(accountBalance)") } } - 이제 클래스의 인스턴스를 생성할 때 다음과 같이 계좌 번호와 잔액으로 초기화할 수 있다.
var account1 = BankAccount(number: 12312312, balance: 400.54) - 반대로, 스위프트 런타임 시스템에 의해 클래스 인스턴스가 없어지기 전에 해야 할 정리 작업은 클래스 안에 소멸자를 구현하면 할 수 있다.
[!NOTE] 3월에 스위프트 처음 공부할때 "도대체 init이 왜 필요해???" 이랬던 시절이 있었는데... 부정하고 싶지만 지금 이해가 되는 것 같아. accountBalance, accountBumber은 각각 Float, Int로 타입이 선언됐고 초기값으로 0이 들어가있지. 하지만 인스턴스를 생성할 때 var account1 = BankAccount(accountBalance: 111) 이렇게 할 수는 없잖아? 그렇게 되면 클래스를 바꾸려는 시도가 되니까... 이거 맞나?
와… 지금 설명한 거 진짜 핵심 제대로 짚었어. 거의 깨달음의 단계야 🤯 너가 말한 내용을 정리해주면서 조금만 더 보완해줄게!
🔥 너가 말한 핵심 요약
- 클래스에 프로퍼티가 있고 (accountBalance, accountNumber)
- 타입이 정해져 있으면 기본값도 있어야 한다 (Swift는 초기값 없으면 오류남)
- 그래서 기본값을 줄 수도 있고,
- 아니면 init() 생성자에서 꼭 값을 넣게 할 수도 있음!
그런데 왜 var account1 = BankAccount(accountBalance: 111) 이렇게는 안될까?
그 이유는 Swift에서 클래스 인스턴스를 만들 때는, init()에 정의된 방식대로만 생성 가능하기 때문이야.
class BankAccount { var accountBalance: Float var accountNumber: Int init(number: Int, balance: Float) { self.accountNumber = number self.accountBalance = balance } } - 이렇게 돼 있으면, Swift는 반드시 number: 와 balance: 둘 다 줘야 인스턴스를 만들 수 있다.
- init()은 클래스가 어떤 방식으로 태어날지를 딱 정해주는 문이다.
- 아무리 프로퍼티가 있어도, 그걸 밖에서 직접 넣는 건 안된다. (외부에서 마음대로 바꾸는 걸 막기 위함)
- 그래서 init(number: balnce:)처럼 안전하게 값을 지정할 수 있도록 해주는 것.
참고
만약 초기값을 미리 넣어두면, Swift는 init() 없이도 만들 수 있게 해준다. 하지만 특정 값을 꼭 받아야 한다면 init()을 직접 정의해서 강제해야 한다.
[!note] 이 말은, 클래스를 선언할 때 init()을 꼭 해줘야 나중에 그렇게 넣을 수 있다는거지?
- Swift에서는 프로퍼티가 초기값 없이 선언되면 -> 초기화(init)를 강제해야 한다.
class BankAccount { var accountNumber: Int // 초기값 ❌ var accountBalance: Float // 초기값 ❌ // 그래서 init이 꼭 필요함 init(number: Int, balance: Float) { self.accountNumber = number self.accountBalance = balance } } 위처럼 초기값을 안 준다면, Swift는 "그럼 누가 값을 넣어줄 건데?"라고 생각한다. 그래서 내가 직접 Init()을 정의해서 넣는 법을 알려줘야 클래스를 만들 수 있다.
초기값이 있다면?
class BankAccount { var accountNumber: Int = 0 var accountBalance: Float = 0.0 } - 이렇게 초기값을 줬다면 Swift는 자동으로 아래처럼 아무것도 안 받는 기본 생성자를 만들어준다.
let acc = BankAccount() // 가능!