iOS

[iOS] 함수와 클래스 및 상속

챎 님 2025. 3. 26. 16:46
더보기

이 포스트는 한성현 교수님의 iOS 프로그래밍 실무 수업을 듣고 작성하였습니다.

오늘은 간단한 문법에 들어가기 전에,

Xcode 내에서 App 파일을 만들었을 때 기본적으로 구성되어있는 코드를 한 번 살펴보겠습니다.

저는 Perplexity 에게 질문하여, 모든 코드에 주석을 달아 보게 하였습니다.

세 가지 파일 중 먼저 AppDeleate 파일입니다.

import UIKit

// AppDelegate 클래스는 앱의 생명주기를 관리하는 역할을 합니다.
// @main 속성은 이 클래스가 앱 실행의 진입점(entry point)임을 나타냅니다.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // 이 메서드는 앱이 실행된 직후 호출됩니다.
    // launchOptions에는 앱이 실행된 이유와 관련된 정보가 포함될 수 있습니다.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 앱 실행 후 초기 설정을 할 수 있는 지점입니다.
        // 예를 들어, 초기화 작업이나 로그 설정 등을 할 수 있습니다.
        return true // true를 반환하면 앱이 정상적으로 실행됩니다.
    }

    // MARK: UISceneSession Lifecycle
    // MARK는 코드의 가독성을 높이기 위해 사용됩니다. Xcode에서 자동으로 섹션 구분을 제공합니다.

    // 새로운 Scene Session이 생성될 때 호출되는 메서드입니다.
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // UISceneConfiguration 객체를 반환하여 새 Scene을 설정합니다.
        // Scene은 iOS 13 이상에서 멀티 윈도우를 지원하기 위해 도입된 개념입니다.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
        // "Default Configuration"은 기본 Scene 설정 이름입니다.
        // sessionRole은 Scene의 역할(예: WindowScene)을 나타냅니다.
    }

    // 사용자가 특정 Scene Session을 삭제했을 때 호출되는 메서드입니다.
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // 사용자가 앱 실행 중 또는 백그라운드 상태에서 Scene을 제거했을 때 호출됩니다.
        // 여기서 제거된 Scene과 관련된 리소스를 해제하거나 정리할 수 있습니다.
        // 예를 들어, 메모리 관리 작업 등을 수행할 수 있습니다.
    }
}

 

이 파일 속에서 새 Scene 을 설정하는 함수 부분에 대해 자세히 알아 보겠습니다.

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

그 속에 _ 의 의미가 무엇인지 함께 물어 보았습니다.

언더 스코어는 사용 시에 외부 이름을 생략할 수 있으며, 외부 매개변수 이름을 생략하면 호출 시 간결하게 사용할 수 있기 때문이었습니다.

이는 코드 가독성을 높이는 데에 도움이 된다는 것이겠지요.

 

configurationForConnecting connectingSceneSession:

그리고 configuartionForConnecting 과 connectingSceneSession 을 두 가지 다 사용하는 이유는 무엇일까요?

그 이유는 Swift 의 함수 매개변수 선언 방식 때문입니다.

함수의 매개변수를 정의할 때 외부 이름과 내부 이름을 각각 지정할 수 있게 됩니다.

외부 이름은 전자(Parameter)에 있는 것으로 함수 호출 시 사용되며, 후자(Argument) 의 경우 함수 내부에서 사용되며 매개변수 참조 시에 사용됩니다.

이렇게 코드를 작성하게 된다면 가독성과 명확성이 제공됩니다.

 

간단하게 함수명을 알아 볼 수 있는 예제를 통해 함수명을 출력해 보겠습니다.

func add(first x: Int, second y: Int) -> Int {
    print(#function) //add(first:second:)
    return(x+y)
}
print(add(first:10, second:20))

이렇게 되면 함수명은 add(first: second:) 로 출력이 됩니다.

출력은 콤마 없이 출력되며, 있을 필요 없습니다.

즉, 함수의 개수는 콜론의 개수라고 생각하면 더 쉽게 이해가 가능합니다!

 

다시 원래 코드로 돌아가여,

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

위 코드의 함수명을 출력해 볼까요?

간단히 설명해 둔 것을 토대로 결과를 보면

application(_: configurationForConnecting: options:)

가 함수명이 됩니다!

 

두 번째는 SceneDelegate 입니다.

import UIKit

// SceneDelegate는 iOS 13 이상에서 도입된 멀티윈도우(Scene) 기능을 관리하는 클래스입니다.
// UIWindowSceneDelegate 프로토콜을 채택하여 Scene의 생명주기 이벤트를 처리합니다.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    // 앱의 UI를 표시하는 메인 윈도우를 정의합니다.
    var window: UIWindow?

    // MARK: - Scene Lifecycle Methods

    // Scene이 처음 연결될 때 호출되는 메서드입니다.
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // 이 메서드는 UIWindow를 UIWindowScene에 연결하거나 초기 설정을 수행하는 데 사용됩니다.
        // 스토리보드를 사용하는 경우, window 속성이 자동으로 초기화되고 Scene에 연결됩니다.
        guard let _ = (scene as? UIWindowScene) else { return }
        // 위 코드는 scene이 UIWindowScene 타입으로 캐스팅 가능한지 확인하며, 그렇지 않으면 메서드를 종료합니다.
    }

    // Scene이 시스템에 의해 해제될 때 호출되는 메서드입니다.
    func sceneDidDisconnect(_ scene: UIScene) {
        // 이 메서드는 Scene이 백그라운드로 전환되거나 세션이 삭제될 때 호출됩니다.
        // 여기서 해당 Scene과 관련된 리소스를 해제하거나 상태를 저장할 수 있습니다.
        // 단, 세션이 완전히 삭제되지 않을 수도 있으므로 복구 가능한 상태를 유지해야 합니다.
    }

    // Scene이 활성화되었을 때 호출되는 메서드입니다.
    func sceneDidBecomeActive(_ scene: UIScene) {
        // 이 메서드는 Scene이 비활성 상태에서 활성 상태로 전환될 때 호출됩니다.
        // 일시 중단된 작업을 재개하거나 타이머를 다시 시작해야 할 때 유용합니다.
    }

    // Scene이 비활성화되기 직전에 호출되는 메서드입니다.
    func sceneWillResignActive(_ scene: UIScene) {
        // 이 메서드는 Scene이 활성 상태에서 비활성 상태로 전환될 때 호출됩니다.
        // 예를 들어, 전화가 오거나 다른 알림으로 인해 앱이 일시 중단될 경우 이 메서드가 호출됩니다.
        // 작업을 일시 중단하거나 중요한 데이터를 저장할 수 있습니다.
    }

    // Scene이 백그라운드에서 포그라운드로 전환될 때 호출되는 메서드입니다.
    func sceneWillEnterForeground(_ scene: UIScene) {
        // 이 메서드는 앱이 백그라운드에서 다시 활성화되기 전에 호출됩니다.
        // 백그라운드로 전환 시 적용했던 변경 사항을 되돌리거나 UI를 업데이트할 수 있습니다.
    }

    // Scene이 포그라운드에서 백그라운드로 전환될 때 호출되는 메서드입니다.
    func sceneDidEnterBackground(_ scene: UIScene) {
        // 이 메서드는 앱이 백그라운드 상태로 전환될 때 호출됩니다.
        // 데이터를 저장하거나 공유 리소스를 해제하는 등 백그라운드 작업을 수행할 수 있습니다.
        // 예를 들어, Core Data의 변경 사항을 저장하거나 사용자 데이터를 안전하게 저장하는 데 사용됩니다.
    }
}

 

마지막으로 ViewController 입니다.

import UIKit

// ViewController 클래스는 화면(View) 하나를 관리하는 역할을 합니다.
// UIViewController를 상속받아 iOS 앱의 화면 생명주기를 관리합니다.
class ViewController: UIViewController {

    // MARK: - UIViewController Lifecycle Methods

    // viewDidLoad는 뷰 컨트롤러의 뷰가 메모리에 로드된 후 호출됩니다.
    // 이 메서드는 앱 실행 시 한 번만 호출되며, 초기 설정을 수행하는 데 적합합니다.
    override func viewDidLoad() {

 

 

위 코드를 간단하게 다루어 봤으니 Swift 문법에 대해서 알아 보겠습니다.

먼저, 클래스입니다.

사람이라는 클래스를 정의해 보며, 살펴 보겠습니다!

class Man {
    var age : Int = 0 // 초기 값을 주지 않으면 에러
}

클래스 안에서의 변수는 프로퍼티라고 부르는데, 이는 초기 값을 지정해 주어야 합니다.

간단하게 = 0 을 적어 주며, 초기 값을 지정해 줄 수 있겠죠?

그렇다면 직접 지정해 주지 않고 초기 값을 지정하려면 어떠한 방법이 있을까요?

class Man {
    var age : Int? // 초기 값이 nil
}

옵셔널 형태로 ? 를 통해 정의해 주면, 초기 값이 nil 로 초기 값이 있는 상태가 됩니다.

이를 stored property(저장 프로퍼티) 라고 부릅니다.

 

이제 윤이라는 사람을 생성해 준 뒤, 나이를 적어 보겠습니다.

class Man {
    var age : Int = 0 // 초기 값을 주지 않으면 에러
}
var x : Int
var yoon : Man
yoon.age = 22

윤의 나이를 22 로 지정해 주었는데 에러가 발생합니다.

그 이유는 무엇일까요?

객체 초기화가 되지 않았기 때문입니다.

이는 이렇게 해결이 가능합니다.

class Man {
    var age : Int = 0 // 초기 값을 주지 않으면 에러
}
var x : Int
var yoon : Man = Man()
yoon.age = 22

초기화를 시켜 주는 initalizer 에 대해 더욱 자세하게 알아 보겠습니다.

designated initializer 를 간단히 살펴 보겠습니다.

이는 Swift에서 클래스, 구조체, 또는 열거형의 인스턴스를 생성할 때 사용하는 초기화 메서드입니다. 

 

제가 작성한 Man 클래스에 주석을 달아 설명해 보겠습니다.

// Man 클래스 정의
class Man {
    // 저장 속성 정의: 나이 (정수형)
    var age: Int
    // 저장 속성 정의: 몸무게 (실수형)
    var weight: Double
    
    // display() 메서드 정의: 나이와 몸무게를 출력하는 기능을 제공
    func display() {
        // 나이와 몸무게를 출력
        print("나이=\(age), 몸무게=\(weight)")
    }
    
    // Designated Initializer 정의
    // 매개변수를 통해 age와 weight 값을 받아 저장 속성을 초기화
    init(age: Int, weight: Double) {
        self.age = age       // 전달받은 age 값을 저장 속성 age에 할당
        self.weight = weight // 전달받은 weight 값을 저장 속성 weight에 할당
    }
}

// Man 클래스의 객체 생성 및 초기화
// 매개변수로 나이 22와 몸무게 30.5를 전달하여 초기화
var yoon = Man(age: 22, weight: 30.5)

// 생성된 객체의 display() 메서드를 호출하여 나이와 몸무게를 출력
yoon.display()

 

다음으로는 Delegation 입니다.

쉽게 이야기해서 도움을 받아서 처리를 해 주는 디자인 패턴입니다.

하나의 객체가 모든 일을 처리하는 것이 아니라 처리해야 할 일 중 일부를 다른 객체에 넘기는 것입니다.

위임된 기능은 프로토콜에서 정의하며, delegate가 위임된 기능을 제공합니다.

 

그렇다면 프로토콜에 대해 간단히 알아 보겠습니다.

프로토콜은 특정 클래스와 관련없는 함수(메서드)들의 선언 집합입니다.

protocol 프로토콜명{
    프로퍼티명
    메서드 선언 //메서드는 선언만 있음
}
protocol 프로토콜명 : 부모1프로토콜, 부모2프로토콜{
	// 프로토콜은 다중 상속도 가능
}

이렇게 정의가 가능합니다.

 

쉬운 이해를 위해서, perplexity 에게 "swift 에서 상속과 프로토콜을 동시에 사용하는 아주 쉬운 예제를 만들고 설명해 줘" 라고 질문해 보았습니다.

// 기본 직원 클래스
class 직원 {
    var 이름: String
    
    init(이름: String) {
        self.이름 = 이름
    }
    
    func 출근하기() {
        print("\(이름)이(가) 출근했습니다.")
    }
}

// 주문 처리 프로토콜
protocol 주문처리 {
    func 주문받기()
    func 음식준비하기()
}

// 서빙 프로토콜
protocol 서빙 {
    func 음식서빙하기()
}

// 웨이터 클래스: 직원을 상속받고, 주문처리와 서빙 프로토콜을 채택
class 웨이터: 직원, 주문처리, 서빙 {
    func 주문받기() {
        print("\(이름) 웨이터가 주문을 받습니다.")
    }
    
    func 음식준비하기() {
        print("\(이름) 웨이터가 주방에 주문을 전달합니다.")
    }
    
    func 음식서빙하기() {
        print("\(이름) 웨이터가 음식을 서빙합니다.")
    }
}

// 요리사 클래스: 직원을 상속받고, 주문처리 프로토콜을 채택
class 요리사: 직원, 주문처리 {
    func 주문받기() {
        print("\(이름) 요리사가 주문을 확인합니다.")
    }
    
    func 음식준비하기() {
        print("\(이름) 요리사가 음식을 조리합니다.")
    }
}

// 사용 예시
let 김웨이터 = 웨이터(이름: "김철수")
let 이요리사 = 요리사(이름: "이영희")

김웨이터.출근하기()
김웨이터.주문받기()
김웨이터.음식준비하기()
이요리사.음식준비하기()
김웨이터.음식서빙하기()
  1. 상속 사용:
    • `직원` 클래스는 기본 클래스로, 모든 직원의 공통 속성과 메서드를 정의합니다.
    • `웨이터`와 `요리사` 클래스는 `직원` 클래스를 상속받아 `이름` 속성과 `출근하기()` 메서드를 물려받습니다.
  2. 프로토콜 사용:
    • `주문처리` 프로토콜은 주문과 관련된 메서드를 정의합니다.
    • `서빙` 프로토콜은 서빙과 관련된 메서드를 정의합니다.
    • `웨이터` 클래스는 `주문처리`와 `서빙` 프로토콜을 모두 채택합니다.
    • `요리사` 클래스는 `주문처리` 프로토콜만 채택합니다.
  3. 상속과 프로토콜의 조합:
    • `웨이터` 클래스는 `직원`을 상속받으면서 동시에 `주문처리`와 `서빙` 프로토콜을 채택합니다.
    • `요리사` 클래스는 `직원`을 상속받으면서 `주문처리` 프로토콜을 채택합니다.