[iOS] 프로그래밍 실무 복습 #4 - 함수와 메서드(method), 클래스(class), 상속(Inheritance), 프로토콜(protocol)과 Delegate

2025. 3. 26. 13:05iOS프로그래밍실무

더보기

이 글은 한성현 교수님의 ‘iOS 프로그램 실무’ 강의를 수강한 후, 복습을 위해 작성된 글입니다.

주로 실습 위주의 내용을 다루고 있습니다.

 

🔷 함수

 

- ViewController.swift

//
//  ViewController.swift
//  ddd
//
//  Created by choseongeun on 3/26/25.
//

import UIKit // UIKit 프레임워크를 가져옵니다. UI 요소를 만들고 관리하는 데 필요합니다.

class ViewController: UIViewController { // ViewController 클래스는 UIViewController를 상속받습니다.

    override func viewDidLoad() { // 뷰가 메모리에 로드된 후 호출되는 메서드입니다.
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 메서드를 호출하여 기본 동작을 수행합니다.
        // Do any additional setup after loading the view. // 뷰가 로드된 후 추가 설정을 할 수 있는 곳입니다.
    }
}

 

- ScenceDelegate.swift

//
//  SceneDelegate.swift
//  ddd
//
//  Created by choseongeun on 3/26/25.
//

import UIKit // UIKit 프레임워크를 가져옵니다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate { // SceneDelegate 클래스는 UIResponder와 UIWindowSceneDelegate를 상속받습니다.

    var window: UIWindow? // UIWindow 객체를 저장할 변수입니다.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 
        // 새로운 씬이 연결될 때 호출되는 메서드입니다.
        guard let _ = (scene as? UIWindowScene) else { return } // 씬이 UIWindowScene인지 확인합니다. 아니면 메서드를 종료합니다.
    }

    func sceneDidDisconnect(_ scene: UIScene) { 
        // 씬이 시스템에 의해 해제될 때 호출되는 메서드입니다.
        // 이 메서드에서 리소스를 해제할 수 있습니다.
    }

    func sceneDidBecomeActive(_ scene: UIScene) { 
        // 씬이 비활성 상태에서 활성 상태로 전환될 때 호출되는 메서드입니다.
        // 이곳에서 일시 중지된 작업을 재시작할 수 있습니다.
    }

    func sceneWillResignActive(_ scene: UIScene) { 
        // 씬이 활성 상태에서 비활성 상태로 전환될 때 호출되는 메서드입니다.
        // 이곳에서 일시 중지할 작업을 처리할 수 있습니다.
    }

    func sceneWillEnterForeground(_ scene: UIScene) { 
        // 씬이 백그라운드에서 포그라운드로 전환될 때 호출되는 메서드입니다.
        // 이곳에서 백그라운드에서 변경된 내용을 되돌릴 수 있습니다.
    }

    func sceneDidEnterBackground(_ scene: UIScene) { 
        // 씬이 포그라운드에서 백그라운드로 전환될 때 호출되는 메서드입니다.
        // 이곳에서 데이터를 저장하고 공유 리소스를 해제할 수 있습니다.
    }
}

 

- AppDelegate.swift

//
//  AppDelegate.swift
//  ddd
//
//  Created by choseongeun on 3/26/25.
//

import UIKit // UIKit 프레임워크를 가져옵니다. UI 요소를 만들고 관리하는 데 필요합니다.

@main // 이 클래스가 애플리케이션의 진입점임을 나타냅니다.
class AppDelegate: UIResponder, UIApplicationDelegate { // AppDelegate 클래스는 UIResponder와 UIApplicationDelegate를 상속받습니다.

    // 애플리케이션이 시작될 때 호출되는 메서드입니다.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch. // 애플리케이션이 시작된 후 사용자 정의를 위한 지점입니다.
        return true // 애플리케이션이 성공적으로 시작되었음을 나타냅니다.
    }

    // MARK: UISceneSession Lifecycle // UISceneSession 생명주기 관련 메서드 섹션입니다.

    // 새로운 씬 세션이 생성될 때 호출되는 메서드입니다.
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Use this method to select a configuration to create the new scene with. // 새로운 씬을 생성하기 위한 구성을 선택하는 데 사용됩니다.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) // 기본 구성으로 새로운 씬을 생성합니다.
    }

    // 사용자가 씬 세션을 버릴 때 호출되는 메서드입니다.
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // 애플리케이션이 실행되지 않을 때 세션이 버려지면 이 메서드가 호출됩니다.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return. // 버려진 씬에 특정한 리소스를 해제하는 데 사용됩니다.
    }
}

 

Swift 함수에서 매개변수 레이블과 이름의 차이

 

Swift에서는 매개변수 레이블(Argument Label)매개변수 이름(Parameter Name) 을 구분하여 사용합니다. 이를 통해 코드 가독성을 높이고 함수 내부에서 변수를 명확하게 다룰 수 있습니다.

 

✅ configurationForConnecting connectingSceneSession 의 구조

func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
) -> UISceneConfiguration

 

1. 매개변수 레이블 (configurationForConnecting)

함수 호출 시 사용됩니다.

appDelegate.application(myApp, configurationForConnecting: session, options: options) 처럼 사용됩니다.

역할을 명확히 하여 가독성을 높여줍니다.

2. 매개변수 이름 (connectingSceneSession)

함수 내부에서 변수처럼 사용됩니다.

connectingSceneSession.role처럼 값을 참조할 때 사용됩니다.

 

✅ _ (언더바)의 역할

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration

 

_ 를 사용하면 첫 번째 매개변수의 레이블을 생략할 수 있습니다.

application:을 명시할 필요 없이 appDelegate.application(myApp, configurationForConnecting: session, options: options) 처럼 간결하게 호출할 수 있습니다.

iOS 시스템에서 자동 호출되는 함수이므로 첫 번째 매개변수의 레이블이 불필요하기 때문에 생략하는 것이 일반적입니다.

 

✅ 결론

매개변수 레이블함수 호출 시 사용되어 가독성을 높입니다.

매개변수 이름함수 내부에서 값에 접근할 때 사용됩니다.

_를 사용하면 첫 번째 매개변수 레이블을 생략하여 코드가 간결해집니다.

 

이러한 Swift의 설계 방식은 가독성과 직관성을 높이기 위한 원칙입니다. 😊


🔶 Argument vs. Parameter

구분 역할 사용 위치 예시
Argument (인자)  함수 호출 시 전달하는 값 함수 외부  greet(to: "성은")
Parameter (매개변수)  함수 내부에서 값을 받는 변수  함수 내부  func greet(to name: String)

 

📌 Argument는 함수 호출 시 사용하고, Parameter는 함수 내부에서 값을 다룰 때 사용합니다.


🔶 Void의 의미와 생략 가능성

import UIKit

func sayHello() -> Void {
    // return값이 없는 경우 '-> Void' 생략 가능
    print("Hello")
}

sayHello()

 

Void는 반환값이 없음을 나타내지만, Swift에서는 이를 생략해도 무방합니다. 🚀


🔶 Swift에서 함수와 타입 확인

func add(x: Int, y: Int) -> Int {
    return x + y
}

print(add(x: 10, y: 20)) // 출력: 30

var x = 10
print(type(of: x))       // 출력: Int
print(type(of: add))     // 출력: (Int, Int) -> Int

 

📌 오류 발생 주의

print(add(10,20)) // ❌ 오류 발생!

 

오류 원인:

Swift에서는 함수 호출 시 매개변수 레이블을 반드시 명시해야 합니다.

👉 add(10, 20)가 아니라 add(x: 10, y: 20)처럼 레이블을 포함해야 합니다.

 

올바른 코드:

print(add(x: 10, y: 20)) // ✅ 정상 작동

 

📌 정리

1. Swift에서는 함수 호출 시 매개변수 레이블을 명시해야 함

add(10, 20) → ❌ 오류 발생

add(x: 10, y: 20) → ✅ 정상 작동

2. 함수 타입 확인 가능

print(type(of: add)) → 출력: (Int, Int) -> Int

 

📌 결론

Swift에서는 가독성을 위해 함수 호출 시 매개변수 레이블을 명시해야 한다는 점을 기억하세요! 🚀

 

🔶 매개변수 레이블과 타입 확인

// 첫 번째 함수: 매개변수 레이블 수정
func add(xx x: Int, yy y: Int) -> Int {
    return x + y
}

print(add(x: 10, y: 20)) // ❌ 오류 발생

// 매개변수 레이블에 맞춰 호출
print(add(xx: 10, yy: 20)) // ✅ 정상 작동
print(type(of: add)) // 출력: (Int, Int) -> Int

 

✅ 오류 발생 원인

func add(xx x: Int, yy y: Int) -> Int {
    return xx + yy
}

print(add(xx: 10, yy: 20)) // ✅ 정상 작동
print(type(of: add)) // 출력: (Int, Int) -> Int

 

📌 오류 발생 주의

func add(xx x: Int, yy y: Int)에서 xxyy매개변수 레이블입니다.

레이블은 함수 호출 시 사용되며, 내부에서 사용되는 변수 이름과는 구분됩니다.

 

주요 사항:

1. xxyy는 함수 내부에서 사용하는 변수 이름입니다.

2. xy매개변수 레이블이며, 함수 호출 시 필수로 사용해야 합니다.

 

📌 정리:

1. 매개변수 레이블과 변수 이름은 다르다

함수 내부에서 xxyy는 내부 변수명으로 사용되지만, 함수 호출 시에는 레이블 xxyy를 사용해야 합니다.

2. 오류 발생 이유:

print(add(x: 10, y: 20)) // ❌ 오류 발생

 

위 코드에서는 매개변수 레이블을 잘못 사용한 것입니다. xxyy가 매개변수 레이블이므로 호출할 때도 그에 맞는 레이블을 사용해야 합니다.

 

✅ 결론:

매개변수 레이블을 정확하게 사용하지 않으면 오류가 발생합니다.

함수 내부에서 사용하는 변수 이름(xx, yy)과 호출 시 사용하는 매개변수 레이블(x, y)은 다르게 처리되기 때문에 주의해야 합니다. 🚀


🔶 외부 매개변수명과 내부 매개변수명

// 함수 정의
func add(x: Int, y: Int) -> Int {
    return x + y
}

// 함수 호출
add(x: 10, y: 20) // ✅ 정상 작동

 

 

외부 매개변수명은 함수 호출 시 사용되는 레이블입니다.

내부 매개변수명은 함수 내부에서 사용되는 변수명입니다.

 

기본적으로, Swift에서 매개변수의 외부 레이블은 매개변수 이름과 동일하게 사용됩니다. 위 코드에서 xy는 외부 매개변수명과 내부 매개변수명이 동일합니다.

 

✅ 외부 매개변수명 생략하기

 

외부 매개변수명을 생략하면, 내부 매개변수명이 외부 매개변수명까지 겸하게 됩니다. 예를 들어:

// 매개변수 레이블 생략
func add(_ x: Int, _ y: Int) -> Int {
    return x + y
}

// 함수 호출
add(10, 20) // ✅ 정상 작동

 

이 경우, add(_ x: Int, _ y: Int)처럼 매개변수 레이블 앞에 **언더바(_)**를 사용하여 외부 매개변수명을 생략할 수 있습니다.

함수 호출 시 add(10, 20)처럼 레벨을 생략한 채 직접 전달할 수 있습니다.

 

✅ 정리

1. 외부 매개변수명과 내부 매개변수명:

기본적으로 외부 매개변수명은 내부 매개변수명과 동일합니다.

함수 호출 시 외부 매개변수명(x, y)을 사용해야 합니다.

2. 외부 매개변수명 생략:

매개변수 앞에 **언더바(_)**를 사용하여 외부 매개변수명을 생략하면, 함수 호출 시 외부 매개변수명 없이 직접 값만 전달할 수 있습니다.

 

✅ 예시:

1. 외부 매개변수명과 내부 매개변수명이 동일:

func add(x: Int, y: Int) -> Int {
    return x + y
}
add(x: 10, y: 20)

 

2. 외부 매개변수명 생략:

func add(_ x: Int, _ y: Int) -> Int {
    return x + y
}
add(10, 20)

 

이렇게 외부 매개변수명을 생략하면 코드가 더 간결해지고, 함수 호출 시 불필요한 레이블을 제거할 수 있습니다. 😊


✅ 매개변수 레이블 (Argument Label) 활용 예시

// 1. 매개변수 레이블을 명시적으로 지정
func add(first x: Int, second y: Int) -> Int {
    return x + y
}
print(add(first: 5, second: 10))  // 5 + 10 = 15

// 2. 기본 매개변수 레이블을 사용하는 경우
func add(x: Int, y: Int) -> Int {
    return x + y
}
print(add(x: 10, y: 20))  // 10 + 20 = 30

// 3. 외부 매개변수명 생략 (언더바(_) 사용)
func add(_ x: Int, _ y: Int) -> Int {
    return x + y
}
print(add(1, 2))  // 1 + 2 = 3

// 4. 특정 매개변수에만 다른 레이블 지정
func add(_ x: Int, with y: Int) -> Int {
    return x + y
}
print(add(5, with: 3))  // 5 + 3 = 8

 

✅ 매개변수 레이블 정리

1. 매개변수 레이블을 명시적으로 지정:

함수 선언에서 firstsecond와 같은 레이블을 사용하여 함수 호출 시 각 매개변수의 의미를 명확히 전달할 수 있습니다.

func add(first x: Int, second y: Int) -> Int { ... }
print(add(first: 5, second: 10))

 

 

2. 기본 매개변수 레이블 사용:

매개변수 레이블을 별도로 지정하지 않으면, 매개변수 이름을 그대로 레이블로 사용합니다.

func add(x: Int, y: Int) -> Int { ... }
print(add(x: 10, y: 20))

 

 

3. 외부 매개변수명 생략:

_를 사용하여 외부 매개변수명을 생략할 수 있습니다. 이 경우 함수 호출 시 매개변수명 없이 값만 전달합니다.

func add(_ x: Int, _ y: Int) -> Int { ... }
print(add(1, 2))

 

 

4. 특정 매개변수에 다른 레이블 사용:

외부 매개변수명에 특별한 이름을 부여할 수도 있습니다. 예를 들어 with 같은 단어를 사용하여 두 번째 매개변수를 구별할 수 있습니다.

func add(_ x: Int, with y: Int) -> Int { ... }
print(add(5, with: 3))

 

✅ 결론

매개변수 레이블은 함수 호출 시 인자의 의미를 명확하게 전달하는 데 도움을 줍니다.

외부 매개변수명 생략은 코드가 더 간결해지지만, 가독성에 영향을 줄 수 있으므로 상황에 맞게 사용하는 것이 중요합니다.


🔶 #function을 사용하여 함수명 출력하기

 

Swift에서는 외부 매개변수명을 다르게 지정하여 이름이 동일한 함수 여러 개를 정의할 수 있습니다. 이를 통해 매개변수 레이블에 따라 함수의 기능을 구분할 수 있으며, 함수 오버로딩을 지원합니다.

// 1. 매개변수 레이블을 명시적으로 지정한 함수
func add(first x: Int, second y: Int) -> Int {
    print(#function)  // 함수명이 출력됨
    return x + y
}
print(add(first: 5, second: 10))  // 함수 호출
// 출력: add(first:second:)


// 2. 기본 매개변수 레이블 사용한 함수
func add(x: Int, y: Int) -> Int {
    print(#function)  // 함수명이 출력됨
    return x + y
}
print(add(x: 10, y: 20))  // 함수 호출
// 출력: add(x:y:)


// 3. 외부 매개변수명 생략한 함수
func add(_ x: Int, _ y: Int) -> Int {
    print(#function)  // 함수명이 출력됨
    return x + y
}
print(add(1, 2))  // 함수 호출
// 출력: add(_:_:)


// 4. 특정 매개변수에 다른 레이블 지정한 함수
func add(_ x: Int, with y: Int) -> Int {
    print(#function)  // 함수명이 출력됨
    return x + y
}
print(add(5, with: 3))  // 함수 호출
// 출력: add(_:with:)

 

✅ 외부 매개변수명으로 함수 오버로딩 가능

외부 매개변수명이 다르면 같은 함수명이라도 다른 함수로 간주됩니다.

Swift에서는 매개변수 레이블이 다르면 동일한 함수 이름을 여러 번 정의할 수 있기 때문에, 매개변수의 의미나 용도에 따라 함수 이름을 쉽게 구분할 수 있습니다.

 

✅ 출력 결과 예시

1. add(first: second:) - 첫 번째 함수가 호출됨.

2. add(x: y:) - 두 번째 함수가 호출됨.

3. add(_: _:) - 외부 매개변수명을 생략한 함수가 호출됨.

4. add(_: with:) - with 레이블이 추가된 함수가 호출됨.


✅ numberOfRowsInSection 함수

@MainActor: 이 속성은 메서드가 UI 작업을 다룬다는 것을 나타냅니다. 즉, 이 메서드는 메인 스레드에서 실행되어야 하며 UI 업데이트와 관련된 작업이므로, UI 관련 코드에서 안전하게 호출될 수 있도록 보장합니다.

tableView: UITableView: UITableView 객체 자체를 전달받습니다. 이 객체는 어떤 테이블 뷰에서 호출되었는지 알려줍니다.

numberOfRowsInSection section: Int: 이 매개변수는 테이블 뷰의 섹션 인덱스를 나타냅니다. 이 값을 통해 특정 섹션에 몇 개의 행이 표시될지 알 수 있습니다.

-> Int: 이 함수는 Int 타입을 반환합니다. 반환값은 주어진 섹션에 표시할 행의 개수입니다.


🔷 클래스(class)

 

 

✅ 클래스에서 프로퍼티 초기화 방법

 

클래스의 프로퍼티는 반드시 초기화를 해줘야 사용이 가능합니다. 만약 초기화되지 않은 프로퍼티를 사용하려 하면 컴파일 에러가 발생합니다. 프로퍼티 초기화 방법은 크게 3가지로 나눌 수 있습니다.

 

1. 초기값을 직접 지정하기

 

가장 간단한 방법은 프로퍼티 선언 시 초기값을 설정하는 것입니다.

class Man {
    var age: Int = 0
}

 

위 코드에서는 age라는 프로퍼티가 **0**으로 초기화됩니다. 이렇게 초기값을 직접 설정하면 별도로 init 메서드를 정의하지 않아도 됩니다.

 

2. 옵셔널 변수로 선언하기

 

초기값을 지정할 필요가 없다면, 옵셔널 변수로 선언할 수 있습니다. 옵셔널 변수는 기본적으로 **nil**로 초기화됩니다.

class Man {
    var age: Int?
}

 

옵셔널 변수는 값이 없을 때 **nil**을 기본값으로 가지므로, 꼭 초기값을 지정하지 않아도 됩니다. 이 방법은 값이 유동적인 경우에 유용합니다.

 

3. init을 이용해 초기화하기

 

클래스 내에서 init 메서드를 이용하여 프로퍼티를 초기화할 수도 있습니다. 이 방법은 클래스 인스턴스를 만들 때 값을 설정할 수 있는 방법입니다.

class Man {
    var age: Int
    
    init(age: Int) {
        self.age = age
    }
}

 

이 예제에서는 init(age:) 초기화 메서드를 통해 Man 객체를 생성할 때 나이를 설정할 수 있습니다.

 

에러 발생 예시

 

초기값이 없는 프로퍼티를 그냥 선언만 하고 사용하려 하면 컴파일 에러가 발생합니다. 예를 들어, 아래와 같이 age 프로퍼티에 초기값을 설정하지 않으면 에러가 발생합니다.

class Man {
    var age: Int
}

let man = Man()  // 에러 발생: 'age' 프로퍼티는 초기화되지 않았습니다.

 

이렇게 초기값을 주지 않으면 반드시 init 메서드로 초기화하거나 옵셔널로 선언해야 합니다.

 

✅ 정리

 

클래스의 프로퍼티는 초기화되지 않으면 사용할 수 없다는 점을 기억하고, 위에서 소개한 3가지 방법으로 초기화해주면 됩니다! 😊

초기값 지정: 프로퍼티 선언 시 기본값 설정.

옵셔널 변수 사용: 기본값 없이 nil로 초기화.

init 메서드 사용: 객체 생성 시 값을 설정.

 

이 방법들을 활용하면 에러 없이 클래스 프로퍼티를 잘 초기화할 수 있습니다!


🔶 클래스와 초기화 메서드 (Initializer)

 

클래스를 정의할 때, 인스턴스를 생성할 수 있는 초기화 메서드(init)를 제공할 수 있습니다. 이 초기화 메서드는 인스턴스를 생성하면서 클래스의 프로퍼티에 값을 설정해줍니다. init 메서드는 클래스의 프로퍼티를 초기화하는 중요한 역할을 합니다.

 

1. 기본 프로퍼티 초기화

class Man {
    var age: Int = 1
    var weight: Double = 3.5
    
    func display() {  // 인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
}

var cho = Man()  // 초기화된 값으로 인스턴스 생성
cho.display()  // 나이=1, 몸무게=3.5

 

위 코드에서는 클래스 Man이 선언되고, ageweight 프로퍼티에 초기값이 설정되어 있습니다. 이때, init 메서드를 따로 정의하지 않더라도 자동으로 기본 초기값으로 초기화됩니다.

 

2. 커스텀 초기화 메서드 사용

 

init 메서드를 사용하여 커스텀 초기화를 할 수 있습니다. init 메서드를 통해 클래스의 프로퍼티를 인스턴스를 생성하면서 설정할 수 있습니다.

class Man {
    var age: Int = 1
    var weight: Double = 3.5
    
    func display() {
        print("나이=\(age), 몸무게=\(weight)")
    }
    
    init(yourAge: Int, yourWeight: Double) {
        age = yourAge
        weight = yourWeight
    }
}

var cho = Man(yourAge: 20, yourWeight: 50.5)  // 초기값 전달
cho.display()  // 나이=20, 몸무게=50.5

 

위 예제에서는 init(yourAge:yourWeight:) 초기화 메서드를 정의하여, 인스턴스를 생성할 때 나이와 몸무게를 매개변수로 전달받아 설정할 수 있습니다. 이를 통해 초기값을 자유롭게 지정할 수 있습니다.

 

3. self 키워드로 초기화

 

init 메서드에서 매개변수 이름과 프로퍼티 이름이 같을 때, self 키워드를 사용하여 매개변수프로퍼티를 명확하게 구분할 수 있습니다. 이를 통해 혼동을 피하고, 클래스의 프로퍼티에 값을 할당할 수 있습니다.

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'로 구분
        self.weight = weight
    } //designated initializer
}

var cho = Man(age: 20, weight: 50.5)  // 초기값 전달
cho.display()  // 나이=20, 몸무게=50.5

 

self클래스의 프로퍼티를 나타내며, 매개변수프로퍼티 이름이 같을 때 이를 구분하는 데 사용됩니다.

Designated Initializer는 클래스의 주요 초기화 메서드로, 클래스의 모든 프로퍼티를 초기화하는 역할을 합니다. self를 사용해 모든 프로퍼티를 설정할 수 있습니다.

 

✅ 정리

클래스의 프로퍼티는 초기화되지 않으면 사용할 수 없습니다.

초기화 메서드(init)를 사용하여 인스턴스를 생성할 때 프로퍼티에 값을 설정할 수 있습니다.

**self**를 사용하면, 매개변수와 클래스 프로퍼티의 이름이 동일할 때 구분할 수 있습니다.

기본적으로 제공되는 초기화 외에도 커스텀 초기화를 통해 클래스를 더 유연하게 활용할 수 있습니다.

 

이렇게 클래스에서 초기화 메서드를 잘 활용하면, 객체를 보다 세밀하게 관리하고 다양한 형태의 인스턴스를 만들 수 있습니다!


🔶 상속 (Inheritance)

 

상속은 자식 클래스가 부모 클래스의 속성과 메서드를 상속받아 재사용하거나 확장하는 개념입니다. Swift에서는 class를 사용하여 상속을 구현할 수 있습니다.

 

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

class Student: Man {
    // Student 클래스는 Man 클래스를 상속받습니다.
}

var kim: Student = Student(age: 25, weight: 55.5)
kim.display()  // 나이=25, 몸무게=55.5

var cho = Man(age: 20, weight: 50.5)
cho.display()  // 나이=20, 몸무게=50.5

 

 

Student 클래스는 Man 클래스를 상속받았습니다. 이로 인해 StudentMan 클래스의 age, weight, display() 메서드를 그대로 사용할 수 있습니다.

상속된 클래스는 부모 클래스의 초기화 메서드도 사용할 수 있습니다. 위 코드에서 StudentMan 클래스의 초기화 메서드를 호출하여 객체를 생성하고 있습니다.


🔶 super 키워드와 초기화

 

super는 자식 클래스에서 부모 클래스의 메서드나 초기화 메서드를 호출할 때 사용하는 키워드입니다. 부모 클래스의 초기화 메서드를 명시적으로 호출해야만 자식 클래스의 객체가 제대로 초기화됩니다.

 

1. super를 사용하여 부모 클래스 초기화 호출

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

class Student: Man {
    var name: String
    
    func displayS() {
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)")
    }
    
    init(age: Int, weight: Double, name: String) {
        self.name = name
        super.init(age: age, weight: weight)  // 부모 클래스의 초기화 메서드 호출
    }
}

var lee: Student = Student(age: 20, weight: 65.2, name: "홍길동")
lee.displayS()  // 이름=홍길동, 나이=20, 몸무게=65.2
lee.display()   // 나이=20, 몸무게=65.2

 

 

super.init(age: weight:): 자식 클래스인 Student에서 부모 클래스인 Man의 초기화 메서드를 super.init을 사용하여 호출하고 있습니다.

자식 클래스에서 지정된 초기화 메서드를 구현할 때는 반드시 부모 클래스의 초기화 메서드를 호출해야 합니다. 그렇지 않으면 컴파일 에러가 발생합니다.

 

2. super.init 호출 누락으로 인한 에러 발생

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

class Student: Man {
    var name: String
    
    func displayS() {
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)")
    }
    
    init(age: Int, weight: Double, name: String) {
        self.name = name
        // super.init 호출 누락
    }  // 에러 발생: 'super.init' isn't called on all paths before returning from initializer
}

var lee: Student = Student(age: 20, weight: 65.2, name: "홍길동")
lee.displayS()  // 에러 발생
lee.display()

 

“super.init isn’t called on all paths before returning from initializer”

이 에러는 자식 클래스에서 부모 클래스의 초기화 메서드를 호출하지 않아서 발생합니다. 자식 클래스의 초기화 메서드 내에서 반드시 부모 클래스의 초기화 메서드를 호출해야 합니다.


🔶 override 에러 발생

 

override는 자식 클래스에서 부모 클래스의 메서드를 재정의할 때 사용하는 키워드입니다. 만약 부모 클래스에서 정의된 메서드를 자식 클래스에서 **override**하지 않고 이름만 같은 메서드를 만들면 에러가 발생할 수 있습니다.

 

super.init 호출 누락으로 인한 오류

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

class Student: Man {
    var name: String
    
    // 부모 클래스의 display() 메서드를 재정의하려고 할 때 에러 발생
    func display() {
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)")
    }
    
    init(age: Int, weight: Double, name: String) {
        self.name = name
        // 부모 클래스의 init 호출을 하지 않으면 에러 발생
        super.init(age: age, weight: weight)  // 반드시 super.init을 호출해야 합니다
    }
}

var lee: Student = Student(age: 20, weight: 65.2, name: "홍길동")
lee.display() // 이름=홍길동, 나이=20, 몸무게=65.2

 

 

자식 클래스에서 부모 클래스의 초기화 메서드를 호출하지 않으면 컴파일 오류가 발생합니다. 부모 클래스의 초기화 메서드를 반드시 호출해야 객체가 제대로 초기화됩니다.

 

✅ 핵심 정리:

상속을 통해 자식 클래스는 부모 클래스의 속성과 메서드를 물려받아 재사용할 수 있습니다.

자식 클래스에서 super.init을 호출하여 부모 클래스의 초기화 메서드를 명시적으로 호출해야만 객체가 제대로 초기화됩니다.

자식 클래스에서 부모 클래스의 **메서드를 override**하여 재정의할 때는 반드시 override 키워드를 사용해야 하며, super를 사용해 부모 클래스 메서드를 호출할 수 있습니다.


🔷 프로토콜(Protocol)

 

프로토콜 정의하기

protocol Runnable {
    var x: Int { get set } // 속성 요구사항
    func run() // 메서드 요구사항
}

 

프로토콜은 메서드와 속성의 요구사항을 정의합니다. 이 프로토콜을 채택한 타입은 반드시 요구된 속성과 메서드를 구현해야 합니다.

 

프로토콜 채택 및 구현

class Man: Runnable {
    var x: Int = 1 // 프로토콜에서 요구한 속성 구현
    
    func run() {
        print("Runnnn~~~") // 프로토콜에서 요구한 메서드 구현
    }
}

var kim = Man()
kim.run() // "Runnnn~~~" 출력

 

위 코드에서 Man 클래스는 Runnable 프로토콜을 채택합니다. 이 클래스는 x라는 속성과 run() 메서드를 구현해야 하며, 이를 통해 Runnable 프로토콜을 준수합니다.

 

에러 발생 예시

protocol Runnable {
    var x: Int { get set }
    func run()
}

class Man: Runnable {
    // 에러 발생: "Type 'Man' does not conform to protocol 'Runnable'"
    // 'x' 속성 및 'run()' 메서드를 구현해야 합니다.
}

 

위와 같이 Man 클래스에서 x 속성과 run() 메서드를 구현하지 않으면, 프로토콜을 준수하지 않아 컴파일 에러가 발생합니다.

 

프로토콜을 준수한 클래스

protocol Runnable {
    var x: Int { get set }
    func run()
}

class Man: Runnable {
    var x: Int = 1
    
    func run() {
        print("Runnnn~~~")
    }
}

var kim = Man()
kim.run() // "Runnnn~~~" 출력

 

이제 Man 클래스는 Runnable 프로토콜을 준수하므로 정상적으로 동작합니다.

 

📌 정리

프로토콜은 메서드와 속성 요구사항을 정의합니다.

프로토콜을 채택한 클래스는 반드시 해당 요구사항을 구현해야 하며, 그렇지 않으면 컴파일 에러가 발생합니다.

에러 해결을 위해서는 요구된 속성과 메서드를 반드시 구현해야 합니다.

 

프로토콜을 사용하면 코드의 일관성과 재사용성을 높일 수 있습니다. 🎉


🔶 상속과 프로토콜을 동시에 사용하는 예제

 

우리는 동물이라는 기본 클래스와 직원이라는 프로토콜을 정의하고, 그 후 강아지고양이 클래스가 상속을 받고, 강아지고양이가 모두 직원 프로토콜을 채택하여 각자의 역할을 다하는 예제를 만들겠습니다.

 

✅ 프로토콜 정의

 

먼저, 직원이라는 프로토콜을 정의해보겠습니다. 이 프로토콜은 이름직책이라는 속성을 요구하고, 일하다()라는 메서드를 요구합니다.

protocol Employee {
    var name: String { get set }
    var position: String { get set }
    
    func work()
}

 

name: 직원의 이름

position: 직원의 직책

work(): 직원이 하는 일에 대한 메서드

 

 

✅ 상속을 위한 동물 클래스 정의

 

그다음, Animal이라는 기본 클래스를 만들겠습니다. Animal 클래스는 namesound라는 속성, 그리고 makeSound()라는 메서드를 가집니다.

class Animal {
    var name: String
    var sound: String
    
    init(name: String, sound: String) {
        self.name = name
        self.sound = sound
    }
    
    func makeSound() {
        print("\(name) makes \(sound) sound.")
    }
}

 

name: 동물의 이름

sound: 동물이 내는 소리

makeSound(): 동물이 소리를 내는 메서드

 

 

✅ 강아지와 고양이 클래스 정의 (상속과 프로토콜 채택)

 

이제 Animal 클래스를 상속받고, Employee 프로토콜을 채택한 DogCat 클래스를 정의하겠습니다.

class Dog: Animal, Employee {
    var position: String
    
    init(name: String, sound: String, position: String) {
        self.position = position
        super.init(name: name, sound: sound)
    }
    
    func work() {
        print("\(name) is guarding the house.")
    }
}

class Cat: Animal, Employee {
    var position: String
    
    init(name: String, sound: String, position: String) {
        self.position = position
        super.init(name: name, sound: sound)
    }
    
    func work() {
        print("\(name) is catching mice.")
    }
}

 

Dog 클래스와 Cat 클래스는 모두 Animal 클래스를 상속받고, Employee 프로토콜을 채택하여 work() 메서드를 구현합니다.

Dog는 집을 지키는 역할을 하고, Cat은 쥐를 잡는 역할을 합니다.

 

 

✅ 사용 예시

 

이제 이 클래스를 사용하여 각 동물들의 동작을 출력해보겠습니다.

let dog = Dog(name: "Buddy", sound: "bark", position: "Guard Dog")
dog.makeSound()  // "Buddy makes bark sound."
dog.work()       // "Buddy is guarding the house."

let cat = Cat(name: "Whiskers", sound: "meow", position: "Mouse Catcher")
cat.makeSound()  // "Whiskers makes meow sound."
cat.work()       // "Whiskers is catching mice."

 

📌 설명

1. 상속 (Inheritance):

DogCat 클래스는 Animal 클래스를 상속받아 name, sound 등의 속성 및 makeSound() 메서드를 물려받습니다. 이로 인해 DogCatAnimal의 기능을 재사용하면서, 각각 고유한 특성을 추가할 수 있습니다.

2. 프로토콜 (Protocol):

DogCat 클래스는 Employee 프로토콜을 채택하여 positionwork() 메서드를 구현해야 합니다. 이를 통해 DogCat은 각각 직원으로서 역할을 다하고, work() 메서드를 통해 각자의 일을 수행합니다.

 

📌 결과

DogCat은 공통된 Animal 클래스에서 상속받은 기능을 사용하면서, Employee 프로토콜을 통해 자신의 역할(일)을 정의하게 됩니다.

이 방식으로 상속과 프로토콜을 동시에 사용하여 코드의 재사용성과 확장성을 높일 수 있습니다.

 

이 예제처럼, 상속은 기본적인 속성 및 기능을 물려받고, 프로토콜은 클래스가 반드시 구현해야 할 요구사항을 정의하는 데 유용합니다. 이 두 가지를 결합하여, 더욱 유연하고 구조적인 코드를 만들 수 있습니다!