[Swift] 함수의 상속과 오버로딩, failable initializer

승민·2024년 10월 16일
0

iOS

목록 보기
6/12
post-thumbnail

알아야 할 점

  • 본 문서는 iOS 프로그래밍 강의 내용을 중심으로 작성되었어요.
  • 이전 내용을 모르면 이해가 어려울 수 있어요.
  • 앞으로도 계속 내용을 추가할 예정이에요.

목적

  • 클래스, 객체, 인스턴스의 구분을 다시 알아볼거에요.
  • Swift의 클래스 선언과 메서드 사용법을 학습할 예정이에요.
  • 그 이외에도 init, method overloading, failable initializer 등의 내용도 다룰 예정이에요.

학습 순서

클래스상속method overloadingfailable initializer

1. 클래스

클래스는 코드의 중복을 없애고 다양한 기능을 정의하고 사용할 수 있게 하는 기능이에요.
클래스를 이해하기 위해 클래스와 관련해서 헷갈릴 수 있는 부분들을 다시 한 번 정리하고 넘어갈게요.

1 - 1. 클래스, 객체, 인스턴스의 구분

클래스와 객체, 인스턴스의 경우는 언어에 상관없이 일관된 개념이기 때문에 이해하고 있으면 좋아요.

설계도인 클래스로부터 만들어진 실체를 객체라 하고 실제로 사용 중인 객체를 인스턴스라고 해요.

1 - 2. 객체지향 용어 비교

위 개념은 다른 객체지향 언어에서도 공통되는 개념이지만, 각 언어별로 지칭하는 용어는 다를 수 있어요.
어떻게 나눠지는지 확인해볼게요.

이렇게 각 언어별로 불리는 이름이 달라요.
Swift에서는 Data프로퍼티(property)라고 지칭하고 있어요.
자동차를 클래스에 비교해서 쉽게 설명하면 다음과 같이 나타낼 수 있어요.

자동차라는 클래스에서 가지는 요소인 '문', '핸들', '바퀴' 등을 Swift에서는 Property라고 하며, 자동차가 가지는 행동 또는 조작 가능한 부분인 '움직인다', '정차한다', '감속한다' 등을 Swift에서는 Method라고 해요.

1 - 3. 언어별 클래스와 객체 생성

이제 위 내용을 실제 코드에서 어떻게 구현할 수 있는지 예제를 살펴보고 다른 언어비교하여 어떤 차이점이 있는 확인해볼게요.

  • 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 클래스를 생성하고 Propertynameage을 가지고 있어요.
그리고 Methodinit()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문 내에서 명시적으로 변수를 지정해주고 있는 것을 확인할 수 있어요.


1 - 4. 언어별 상속 방법

위 예시 언어들의 상속 방법도 알아볼게요.

  • 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()

이러한 각 언어의 클래스 선언 방법과 상속 형태에 대해 간단히 알아보았어요.

이제 자세한 내용을 확인해볼게요.


2. 클래스 메서드

2 - 1. Swift의 클래스 선언과 메서드

스위프트에서 부모 클래스를 상속받는 자식 클래스를 정의하는 간단한 형식이에요.

class 새로운 클래스 이름 : 부모 클래스 {
	// 프로퍼티
    // 인스턴스 메서드
    // 타입(type) 메서드(클래스 메서드)
}

형식이 복잡하지 않고 직관적으로 내용을 알 수 있어요.

2 - 2. 클래스의 프로퍼티

스위프트의 property는 저장 프로퍼티(stored property)과 계산 프로퍼티(computed property)가 존재해요.

이 중 stored property는 반드시 초기값이 존재해야 해요.
초기값이 없는 경우 오류가 발생하며 실행할 수 없어요.

class Man {
  var age : Int
  var weight : Double
}


초기값을 지정하는 방법이 있어요.

  • 0으로 초기화

초기값을 0으로 지정해주면서 값을 초기화 시키는 방법이에요.

보통 0으로 초기화하는 경우는 값이 항상 필요하고 0이 유효한 초기값으로 사용되는 경우에 많이 사용해요.

class Man {
	var age : Int = 0
    var weight : Double = 0
}


  • 옵셔널로 초기화

옵셔널로 초기화하는 경우 내부에서 자동으로 nil 값을 채워넣어요.

옵셔널로 초기화하는 경우는 값이 없을 수 있거나 초기에 알 수 없는 상태를 유지해야 하는 경우 옵셔널로 초기화해요.
또한 옵셔널은 값의 존재 여부를 명시적으로 확인하는 것을 강제하기 때문에 더 안전한 코드를 작성할 수 있어요.

class Man {
	var age : Int?
    var weight : Double?
}


2 - 5. 타입 메서드

타입 메서드는 클래스가 가지는 메서드로 클래스 자체에 관련된 기능을 수행할 수 있어요.
같은 클래스의 초기값을 변경하거나 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()


staticclass로 선언한 클래스 메서드는 차이점이 존재해요.
static으로 선언 한 클래스 메서드의 경우에는 값을 수정할 수 없어요.
그러나 class로 선언한 클래스 메서드는 상속받거나 오버라이드를 할 수 있어요.

2 - 6. 인스턴스 초기화 init()

위에서부터 확인했던 init() 함수는 클래스가 생성되었을 때, 해당 객체의 인스턴스를 초기화하는 함수에요.
이 함수를 정의한 경우 인스턴스가 만들어지면 자동으로 호출이 돼요.
또한 init() 함수는 기본값이 없는 인스턴스만 초기화시킬 수도 있어요.

2 - 7. designated initializer

위에서 프로퍼티를 초기화할 때, 모든 저장 프로퍼티를 초기화하는 것을 designated initializer라고 해요.
이렇게 모든 저장 프로퍼티를 초기화할 수 있는 경우에 서브 클래스에서 오버라이드할 수 있어요.

2 - 8. self

init()과 같은 함수를 통해 프로퍼티를 초기화 하는 경우에 init() 함수 내에서 파라미터와 프로퍼티의 이름을 같게 하는 것이 직관적이에요.
간단하게 작성된 경우는 알아볼 수 있지만, 코드가 복잡해지면 의미를 알아차리기 힘들 수 있어요.
그런 경우를 대비에 Swift에서는 self를 통해 코드를 직관적으로 변경할 수 있어요.

저장 프로퍼티와 매개변수의 이름이 같은 경우, 매개변수는 지역변수로서 동작해요.
저장 프로퍼티는 self.<name>을 통해 저장 프로퍼티를 가리킬 수 있어요.

3. 클래스 상속

상속은 기존에 존재하는 클래스의 내용을 이어받아 새로운 기능을 추가하거나 수정할 수 있는 기능이에요.
Swift에서 상속은 super classsub class로 나눠서 설명할 수 있어요.

  • super class

super class는 부모 클래스로 원본 내용의 클래스에요.
보통 재사용 가능한 기능들이 구현되어 있고, 범용적으로 사용할 수 있도록 구현하는 경우가 많아요.

  • sub class

sub classsuper class에게 상속받아서 생성된 클래스로 super class의 내용을 복사해서 가지고 있어요.
sub class에서 변경된 내용은 따로 설정하지 않는 이상 super class에 영향을 주지 않아요.

3 - 1. 상속 방법

부모 클래스는 하나만 가능해요. 상속의 방법은 간단해요.

class 자식 : 부모 {
	// 부모 클래스는 하나만 가능해요.
    // 콜론 다음이 여러 개이면 나머지는 프로토콜이라 해요.
}

또한 상속은 클래스만 가능하며, 다른 기능들을 상속받으려고 하면 상속이 아닌 프로토콜로 활용되요.

3 - 2. 프로토콜

콜론 다음이 여러 개이면 나머지는 프로토콜이라고 해요.
프로토콜은 "이런 기능이 있어야 해"라고 정의하고, 이를 채택한 타입들은 "그 기능을 이렇게 구현할게"라고 응답하는 방식으로 동작해요.

다음은 상속받을 때, super 클래스와 프로토콜을 구분하는 형식의 예시에요.

  • class 클래스명 : 부모명, 프로토콜명{}
    • 부모가 있으면 부모 다음에 표기
  • class 클래스명 : 부모명, 프로토콜명1, 프로토콜명2{}
  • class 클래스명 : 프로토콜명{}
    • 부모가 없으면 바로 표기 가능
  • 상속은 class 만 가능

4. 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
    }
 }


4 - 1. UIImage

UIImageiOS에서 이미지를 표현하는 객체로 주로 앱에서 이미지를 표시할 때 사용해요.
주로 UIImage(named: "<Image Name>") 형태로 생성하는데, 인자값에 따라 같은 이름의 함수도 다른 기능을 수행해요.
method overloading의 예시로 확인해볼게요.

  1. UIImageView에 이미지 표시: UIImage 객체를 UIImageViewimage 속성에 할당하여 화면에 이미지를 표시해요.
  2. 에셋 카탈로그에서 이미지 로드: UIImage(named:) 생성자를 사용하여 앱의 에셋 카탈로그에서 이미지를 로드해요.
  3. 파일 시스템에서 이미지 로드: UIImage(contentsOfFile:) 생성자를 사용하여 파일 시스템에서 이미지를 로드해요.
  4. 버튼 및 기타 UI 요소의 이미지 설정: UIButton 등의 UI 요소에 이미지를 설정하는 데 사용돼요.
  5. 이미지 데이터 변환: pngData() 또는 jpegData(compressionQuality:) 메서드를 사용하여 이미지를 데이터로 변환해요.
  6. 애니메이션 이미지 생성: animatedImage(with:duration:) 메서드를 사용하여 여러 이미지로 구성된 애니메이션 이미지를 생성해요.


5. failable initializer

failable initializer는 실패가능한 생성자로 생성자의 넣는 값이 유효하지 않거나 없는 경우에 대처하기 위한 방법이에요.
내용을 이해하기 위해 코드로 확인해볼게요.

let myImage: UIImage = UIImage(named: "apple.png")! 
//느낌표가 왜 있지?

apple.png 파일이 없으면 인스턴스를 만들 수 없고 nil을 반환해요.
nil값도 저장하려면 init다음에 “?”를 붙이면 옵셔널 값을 리턴해요.

init?(named name: String) // failable initializers

init?으로 만든 인스턴스는 옵셔널형으로 만들어져서, 사용하려면 옵셔널을 언래핑해야 해서 위의 예제에서 제일 마지막에 “!”가 있어요.

5 - 1. 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()
}

5 - 2. 인스턴스 생성 방법

failable initializer로 생성된 클래스의 인스턴스를 생성하는 방법은 4가지가 있어요.

다음 1, 2번 방법을 주로 사용해야 하고, 강제 언래핑은 crash가 발생할 수 있기 때문에 사용의 주의해야 해요.

  1. 옵셔널 형으로 선언
  2. 인스턴스 생성과 동시에 옵셔널 바인딩
  3. 인스턴스 생성하면서 바로 강제 언래핑
  4. 옵셔널 인스턴스를 사용시 강제 언래핑
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()


6. 정리

// 기본 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%"

출처 : Smile Han - iOS 프로그래밍 기초

0개의 댓글