Swift
의 클래스 선언과 메서드 사용법을 학습할 예정이에요.init
, method overloading
, failable initializer
등의 내용도 다룰 예정이에요.클래스
→ 상속
→ method overloading
→ failable initializer
클래스는 코드의 중복을 없애고 다양한 기능을 정의하고 사용할 수 있게 하는 기능이에요.
클래스를 이해하기 위해 클래스와 관련해서 헷갈릴 수 있는 부분들을 다시 한 번 정리하고 넘어갈게요.
클래스와 객체, 인스턴스의 경우는 언어에 상관없이 일관된 개념이기 때문에 이해하고 있으면 좋아요.
설계도인 클래스로부터 만들어진 실체를 객체라 하고 실제로 사용 중인 객체를 인스턴스라고 해요.
위 개념은 다른 객체지향 언어에서도 공통되는 개념이지만, 각 언어별로 지칭하는 용어는 다를 수 있어요.
어떻게 나눠지는지 확인해볼게요.
이렇게 각 언어별로 불리는 이름이 달라요.
Swift
에서는 Data
를 프로퍼티(property
)라고 지칭하고 있어요.
자동차를 클래스에 비교해서 쉽게 설명하면 다음과 같이 나타낼 수 있어요.
자동차라는 클래스에서 가지는 요소인 '문', '핸들', '바퀴' 등을 Swift
에서는 Property
라고 하며, 자동차가 가지는 행동 또는 조작 가능한 부분인 '움직인다', '정차한다', '감속한다' 등을 Swift
에서는 Method
라고 해요.
이제 위 내용을 실제 코드에서 어떻게 구현할 수 있는지 예제를 살펴보고 다른 언어와 비교하여 어떤 차이점이 있는 확인해볼게요.
Swift
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func introduce() {
print("안녕하세요, 저는 \(name)이고 \(age)살입니다.")
}
}
// 객체 생성
let person = Person(name: "홍길동", age: 30)
person.introduce()
위 코드에서는 Person
클래스를 생성하고 Property
인 name
과 age
을 가지고 있어요.
그리고 Method
인 init()
와 introduce()
함수가 정의된 것을 확인할 수 있어요.
여기서 init()
은 조금 더 정확하게는 이니셜라이저 또는 초기화 메서드이지만, 이 내용은 아래에서 다시 알아볼게요.
마지막으로 해당 클래스 생성된 상수 person
에 값을 전달하고 Method
를 호출하고 있어요.
다른 언어와 어떤 차이가 있는지 확인해보기 위해 주요 언어의 생성 방식도 확인해볼게요.
Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void introduce() {
System.out.println("안녕하세요, 저는 " + name + "이고 " + age + "살입니다.");
}
}
// 객체 생성
Person person = new Person("홍길동", 30);
person.introduce();
Java
에서는 public
, private
와 같은 범위를 설정해주고 new
키워드를 통해 객체를 생성하고 있어요.
Python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"안녕하세요, 저는 {self.name}이고 {self.age}살입니다.")
# 객체 생성
person = Person("홍길동", 30)
person.introduce()
Python
은 __init__
을 통해 생성자가 존재하고 self
인자가 존재하지만, 실제 선언 시에는 사용하지 않는 것을 확인할 수 있어요.
또한 Method
에서 print
문 앞에 f
를 추가하여, print
문 내에서 명시적으로 변수를 지정해주고 있는 것을 확인할 수 있어요.
위 예시 언어들의 상속 방법도 알아볼게요.
Swift
Person
클래스로부터 상속받아 Student
클래스를 정의하는 내용이에요.
init()
함수에서 school
을 설정하고 super
키워드를 통해 부모 클래스의 내용을 정의해요.
// 클래스는 위 내용 참고
class Student: Person {
var school: String
init(name: String, age: Int, school: String) {
self.school = school
super.init(name: name, age: age)
}
override func introduce() {
super.introduce()
print("저는 \(school)에 다니고 있습니다.")
}
}
let student = Student(name: "김컴소", age: 20, school: "대학교")
student.introduce()
Java
Java
에서는 생성자의 경우 @override
를 사용하지 않고 메서드에서만 @override
를 사용하고 있어요.
이외 내용은 Swift
와 비슷한 형태를 나타내요.
// 클래스는 위 내용 참고
public class Student extends Person {
private String school;
public Student(String name, int age, String school) {
super(name, age);
this.school = school;
}
@Override
public void introduce() {
super.introduce();
System.out.println("저는 " + school + "에 다니고 있습니다.");
}
}
Student student = new Student("김컴소", 20, "대학교");
student.introduce();
Python
Python
은 조금 더 직관적이고, 부수적인 내용이 적게 들어가 있어요.
# 클래스는 위 내용 참고
class Student(Person):
def __init__(self, name, age, school):
super().__init__(name, age)
self.school = school
def introduce(self):
super().introduce()
print(f"저는 {self.school}에 다니고 있습니다.")
student = Student("김컴소", 20, "대학교")
student.introduce()
이러한 각 언어의 클래스 선언 방법과 상속 형태에 대해 간단히 알아보았어요.
이제 자세한 내용을 확인해볼게요.
Swift
의 클래스 선언과 메서드스위프트에서 부모 클래스를 상속받는 자식 클래스를 정의하는 간단한 형식이에요.
class 새로운 클래스 이름 : 부모 클래스 {
// 프로퍼티
// 인스턴스 메서드
// 타입(type) 메서드(클래스 메서드)
}
형식이 복잡하지 않고 직관적으로 내용을 알 수 있어요.
스위프트의 property
는 저장 프로퍼티(stored property
)과 계산 프로퍼티(computed property
)가 존재해요.
이 중 stored property
는 반드시 초기값이 존재해야 해요.
초기값이 없는 경우 오류가 발생하며 실행할 수 없어요.
class Man {
var age : Int
var weight : Double
}
초기값을 지정하는 방법이 있어요.
초기값을 0으로 지정해주면서 값을 초기화 시키는 방법이에요.
보통 0으로 초기화하는 경우는 값이 항상 필요하고 0이 유효한 초기값으로 사용되는 경우에 많이 사용해요.
class Man {
var age : Int = 0
var weight : Double = 0
}
옵셔널로 초기화하는 경우 내부에서 자동으로 nil
값을 채워넣어요.
옵셔널로 초기화하는 경우는 값이 없을 수 있거나 초기에 알 수 없는 상태를 유지해야 하는 경우 옵셔널로 초기화해요.
또한 옵셔널은 값의 존재 여부를 명시적으로 확인하는 것을 강제하기 때문에 더 안전한 코드를 작성할 수 있어요.
class Man {
var age : Int?
var weight : Double?
}
타입 메서드는 클래스가 가지는 메서드로 클래스 자체에 관련된 기능을 수행할 수 있어요.
같은 클래스의 초기값을 변경하거나 print
구문을 출력하는 등의 기능을 수행할 수 있어요.
class Man {
var age : Int = 0
var weight : Double = 0.0
func display() {
print("나이=\(age), 몸무게=\(weight)")
}
class func cM() {
print("cM은 클래스 메서드입니다.")
}
static func scM() {
print("scM은 클래스 메서드(static)")
}
}
var goo : Man = Man()
print(goo.age)
goo.age = 10
goo.weight = 20.5
print(goo.age, goo.weight)
goo.display()
Man.cM()
Man.scM()
static
과 class
로 선언한 클래스 메서드는 차이점이 존재해요.
static
으로 선언 한 클래스 메서드의 경우에는 값을 수정할 수 없어요.
그러나 class
로 선언한 클래스 메서드는 상속받거나 오버라이드를 할 수 있어요.
init()
위에서부터 확인했던 init()
함수는 클래스가 생성되었을 때, 해당 객체의 인스턴스를 초기화하는 함수에요.
이 함수를 정의한 경우 인스턴스가 만들어지면 자동으로 호출이 돼요.
또한 init()
함수는 기본값이 없는 인스턴스만 초기화시킬 수도 있어요.
designated initializer
위에서 프로퍼티를 초기화할 때, 모든 저장 프로퍼티를 초기화하는 것을 designated initializer
라고 해요.
이렇게 모든 저장 프로퍼티를 초기화할 수 있는 경우에 서브 클래스에서 오버라이드할 수 있어요.
self
init()
과 같은 함수를 통해 프로퍼티를 초기화 하는 경우에 init()
함수 내에서 파라미터와 프로퍼티의 이름을 같게 하는 것이 직관적이에요.
간단하게 작성된 경우는 알아볼 수 있지만, 코드가 복잡해지면 의미를 알아차리기 힘들 수 있어요.
그런 경우를 대비에 Swift
에서는 self
를 통해 코드를 직관적으로 변경할 수 있어요.
저장 프로퍼티와 매개변수의 이름이 같은 경우, 매개변수는 지역변수로서 동작해요.
저장 프로퍼티는 self.<name>
을 통해 저장 프로퍼티를 가리킬 수 있어요.
상속은 기존에 존재하는 클래스의 내용을 이어받아 새로운 기능을 추가하거나 수정할 수 있는 기능이에요.
Swift
에서 상속은 super class
와 sub class
로 나눠서 설명할 수 있어요.
super class
super class
는 부모 클래스로 원본 내용의 클래스에요.
보통 재사용 가능한 기능들이 구현되어 있고, 범용적으로 사용할 수 있도록 구현하는 경우가 많아요.
sub class
sub class
는 super class
에게 상속받아서 생성된 클래스로 super class
의 내용을 복사해서 가지고 있어요.
sub class
에서 변경된 내용은 따로 설정하지 않는 이상 super class
에 영향을 주지 않아요.
부모 클래스는 하나만 가능해요. 상속의 방법은 간단해요.
class 자식 : 부모 {
// 부모 클래스는 하나만 가능해요.
// 콜론 다음이 여러 개이면 나머지는 프로토콜이라 해요.
}
또한 상속은 클래스만 가능하며, 다른 기능들을 상속받으려고 하면 상속이 아닌 프로토콜로 활용되요.
콜론 다음이 여러 개이면 나머지는 프로토콜이라고 해요.
프로토콜은 "이런 기능이 있어야 해"라고 정의하고, 이를 채택한 타입들은 "그 기능을 이렇게 구현할게"라고 응답하는 방식으로 동작해요.
다음은 상속받을 때, super
클래스와 프로토콜을 구분하는 형식의 예시에요.
class
클래스명 : 부모명, 프로토콜명{}class
클래스명 : 부모명, 프로토콜명1, 프로토콜명2{}class
클래스명 : 프로토콜명{}class
만 가능method overloading
메서드 오버로딩은 매개변수의 개수와 자료형이 다른 같은 이름의 함수를 여러 개 정의하는 것을 말해요.
매개변수 두 개가 다른 생성자의 경우 두 가지 방법으로 인스턴스를 만들 수 있어요.
코드를 통한 예시로 확인해볼게요.
class Man {
var age : Int = 1
var weight : Double = 3.5
func display() {
print("나이=\(age), 몸무게=\(weight)")
}
init(age: Int, weight : Double) {
self.age = age
self.weight = weight
}
init(age: Int) {
self.age = age
}
}
UIImage
UIImage
는 iOS
에서 이미지를 표현하는 객체로 주로 앱에서 이미지를 표시할 때 사용해요.
주로 UIImage(named: "<Image Name>")
형태로 생성하는데, 인자값에 따라 같은 이름의 함수도 다른 기능을 수행해요.
method overloading
의 예시로 확인해볼게요.
UIImageView
에 이미지 표시: UIImage
객체를 UIImageView
의 image
속성에 할당하여 화면에 이미지를 표시해요.UIImage(named:)
생성자를 사용하여 앱의 에셋 카탈로그에서 이미지를 로드해요.UIImage(contentsOfFile:)
생성자를 사용하여 파일 시스템에서 이미지를 로드해요.UIButton
등의 UI
요소에 이미지를 설정하는 데 사용돼요.pngData()
또는 jpegData(compressionQuality:)
메서드를 사용하여 이미지를 데이터로 변환해요.animatedImage(with:duration:)
메서드를 사용하여 여러 이미지로 구성된 애니메이션 이미지를 생성해요.failable initializer
failable initializer
는 실패가능한 생성자로 생성자의 넣는 값이 유효하지 않거나 없는 경우에 대처하기 위한 방법이에요.
내용을 이해하기 위해 코드로 확인해볼게요.
let myImage: UIImage = UIImage(named: "apple.png")!
//느낌표가 왜 있지?
apple.png
파일이 없으면 인스턴스를 만들 수 없고 nil
을 반환해요.
nil
값도 저장하려면 init
다음에 “?”를 붙이면 옵셔널 값을 리턴해요.
init?(named name: String) // failable initializers
init?
으로 만든 인스턴스는 옵셔널형으로 만들어져서, 사용하려면 옵셔널을 언래핑해야 해서 위의 예제에서 제일 마지막에 “!”가 있어요.
failable initializer
생성failable initializer
을 생성하는 방법을 알아볼게요.
예제를 통해 확인할게요.
class Man {
var age : Int = 1
var weight : Double = 3.5
init?(age: Int, weight: Double) {
if age <= 0 || weight <= 0 {
return nil
} else {
self.age = age
self.weight = weight
}
}
func display() {
print("나이=\(age), 몸무게=\(weight)")
}
}
var goo : Man? = Man.init(age: 5, weight: 10.3)
if let goo {
goo.dispay()
}
failable initializer
로 생성된 클래스의 인스턴스를 생성하는 방법은 4가지가 있어요.
다음 1, 2번 방법을 주로 사용해야 하고, 강제 언래핑은 crash
가 발생할 수 있기 때문에 사용의 주의해야 해요.
var kim : Man? = Man(age: 1, weight: 3.5)
// 1-1. 옵셔널 형으로 선언
if let kim1 = kim { // 1-2. 옵셔널 바인딩
kim1. display()
}
// 2. 인스턴스 생성과 동시에 옵셔널 바인딩
if let kim2 = Man(age: 2, weight:5.5) {
kim2.dispay()
}
// 3. 인스턴스 생성하면서 바로 강제 언래핑
var kim3 : Man = Man(age: 3, weight: 7.5)!
kim3.display()
// 4. 옵셔널 인스턴스를 사용시 강제 언래핑
var kim4 : Man? = Man(age: 4, weight: 10.5)
kim4!.display()
// 기본 Vehicle 클래스
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 기본 구현은 아무 소리도 내지 않음
}
}
// Car 클래스 - Vehicle을 상속
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
override func makeNoise() {
print("부릉부릉")
}
}
// Bicycle 클래스 - Vehicle을 상속하고 failable initializer 사용
class Bicycle: Vehicle {
var hasBasket = false
init?(hasBasket: Bool, initialSpeed: Double) {
self.hasBasket = hasBasket
super.init()
// 초기 속도가 0 미만이면 초기화 실패
if initialSpeed < 0 {
return nil
}
currentSpeed = initialSpeed
}
override func makeNoise() {
print("따르릉")
}
}
// ElectricBicycle 클래스 - Bicycle을 상속하고 failable initializer를 오버라이드
class ElectricBicycle: Bicycle {
var batteryLevel: Int
init?(hasBasket: Bool, initialSpeed: Double, batteryLevel: Int) {
self.batteryLevel = batteryLevel
// 배터리 레벨이 0-100 범위를 벗어나면 초기화 실패
if batteryLevel < 0 || batteryLevel > 100 {
return nil
}
super.init(hasBasket: hasBasket, initialSpeed: initialSpeed)
}
// non-failable initializer로 오버라이드
override init(hasBasket: Bool, initialSpeed: Double) {
self.batteryLevel = 100 // 기본 배터리 레벨을 100으로 설정
super.init(hasBasket: hasBasket, initialSpeed: max(0, initialSpeed))!
}
override func makeNoise() {
print("윙윙")
}
}
// 사용 예시
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print(car.description) // "traveling at 25.0 miles per hour in gear 3"
car.makeNoise() // "부릉부릉"
if let bicycle = Bicycle(hasBasket: true, initialSpeed: 15.0) {
print(bicycle.description) // "traveling at 15.0 miles per hour"
bicycle.makeNoise() // "따르릉"
} else {
print("자전거 초기화 실패")
}
if let electricBike = ElectricBicycle(hasBasket: false, initialSpeed: 20.0, batteryLevel: 80) {
print(electricBike.description) // "traveling at 20.0 miles per hour"
print("배터리 레벨: \(electricBike.batteryLevel)%")
electricBike.makeNoise() // "윙윙"
} else {
print("전기자전거 초기화 실패")
}
let anotherElectricBike = ElectricBicycle(hasBasket: true, initialSpeed: -5.0)
print(anotherElectricBike.description) // "traveling at 0.0 miles per hour"
print("배터리 레벨: \(anotherElectricBike.batteryLevel)%") // "배터리 레벨: 100%"