iOS 프로그래밍 실무 4주차

2025. 3. 26. 16:46📱 모바일 프로그래밍/iOS 프로그래밍 실무

viewController.swift

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

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }


}

 

SceneDelegate.swift

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

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}

 

AppDelegate.swift

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

import UIKit

@main
class 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

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // 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>) {
        // Called when the user discards a scene session.
        // 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.
    }


}

 

Q. 이 소스를 상세히 설명해줘

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

📌 application(_:configurationForConnecting:options:) 메서드란?

이 메서드는 새로운 Scene 세션이 생성될 때 호출됩니다.
즉, 사용자가 앱을 실행하거나 멀티태스킹에서 앱을 다시 열 때 Scene이 필요하면 호출됩니다.
iOS 13부터 Scene 기반 생명 주기가 도입되었기 때문에, SceneDelegate와 함께 동작하는 중요한 메서드입니다.

 

  • application: UIApplication
    • 현재 실행 중인 앱의 인스턴스입니다.
    • 앱의 생명 주기와 관련된 이벤트를 처리하는 역할을 합니다.
  • connectingSceneSession: UISceneSession
    • 새로운 Scene 세션 객체입니다.
    • 이 세션을 통해 앱의 UI와 관련된 정보를 관리할 수 있습니다.
    • iPadOS에서는 여러 개의 Scene을 동시에 실행할 수 있기 때문에 필요합니다.
  • options: UIScene.ConnectionOptions
    • Scene이 생성될 때 사용자가 어떤 방식으로 앱을 열었는지에 대한 정보를 포함합니다.
    • 예를 들어, 앱 아이콘을 눌러 실행했는지, URL Scheme을 통해 실행했는지 등의 정보를 확인할 수 있습니다.

📌 반환값: UISceneConfiguration

이 메서드는 UISceneConfiguration 객체를 반환해야 합니다.

UISceneConfiguration 이란?

  • UISceneConfiguration(name:role:)을 사용하여 Scene의 구성을 설정합니다.
  • name: Info.plist에서 정의한 Scene의 이름을 지정합니다.
  • role: Scene의 역할을 지정합니다. 일반적으로 .windowApplication을 사용합니다.

 

Swift에서 매개변수 앞에 _ (언더스코어)를 사용하는 이유는 외부 매개변수 이름을 생략하기 위해서입니다.

 

📌 configurationForConnecting connectingSceneSession: 두 개의 차이점

Swift에서 함수의 매개변수 선언 방식 때문에 이름이 두 개 사용됩니다.

configurationForConnecting과 connectingSceneSession은 각각 외부 매개변수 이름내부 매개변수 이름입니다.

✅ 차이점 정리

구분 설명
configurationForConnecting 외부 매개변수 이름 (External Parameter Name) → 함수 호출 시 사용됨
connectingSceneSession 내부 매개변수 이름 (Internal Parameter Name) → 함수 내부에서 사용됨

 

 

함수 호출 시 사용하는 인자 : argument

함수 내부에서 받아서 사용 : parameter

 

함수가 클래스 안으로 들어가면 : 메소드

 

함수

import UIKit

// 리턴형이 없는 경우에는 void 생략 가능
func sayHello() -> Void {
    print("Hello")
}

sayHello()

 

 

func add(x: Int, y: Int) -> Int {
    return x + y
}
print(add(x: 10, y: 20))
// 외부 매개변수명 생략하면 내부 매개변수명이 외부 매개변수명까지 겸함

// 변수 자료형
var x = 10
print(type(of: x))

// 함수 자료형
print(type(of: add))


30
Int
(Int, Int) -> Int

 

func add(xx x: Int, yy y: Int) -> Int {
    // 외부 내부: 자료형, 외부 내부: 자료형 -> 리턴형 
    return x + y   // 함수 정의 시는 내부 매개변수명(parameter name) 사용
}
print(add(xx: 10, yy: 20))   // 호출 시는 외부 매개변수명(argument label) 사용

 

// 가장 적게 쓰는
func add(_ x: Int,_ y: Int) -> Int {
    return x + y
}
print(add(1,2))

// 가장 많이 쓰는
func add(_ x: Int, with y: Int) -> Int {
    return x + y
}
print(add(1, with: 2))

 

func application(_ application: UIApplication, configurationForConnecting 
    connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) 
    -> UISceneConfiguration {
항목 설명
func 함수 선언 키워드
application 함수명
_ 외부 매개변수명 생략 (호출 시 application(UIApplication.shared, ...)처럼 사용 가능)
application 내부 매개변수명 (함수 내부에서 application으로 접근)
UIApplication application의 자료형 (앱의 실행과 관련된 객체)
configurationForConnecting 외부 매개변수명 (함수를 호출할 때 configurationForConnecting: ...으로 사용됨)
connectingSceneSession 내부 매개변수명 (함수 내부에서 사용)
UISceneSession connectingSceneSession의 자료형 (새로운 Scene의 세션 정보)
options 외부 매개변수명 & 내부 매개변수명 (둘 다 동일하게 사용)
UIScene.ConnectionOptions options의 자료형 (앱이 실행될 때 전달된 옵션 정보)
-> UISceneConfiguration 반환값 (Scene의 구성 정보를 담은 객체)
함수명 application(_:configurationForConnecting:options:)

 

** 중간고사 냄 **

함수 자료형은 같고, 함수명은 다 다르다(외부매개변수:외부매개변수:...), (콜론[:]의 갯수가 매개변수의 갯수)

더보기

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

    print(#function)

    return x + y

}

add(x:y:)

 

func add(first x: Int, second y: Int) -> Int {

    print(#function)

    return x + y

}

add(first:second:)

 

func add(_ x: Int, _ y: Int) -> Int {

    print(#function)

    return x + y

}

add(_:_:)

 

func add(_ x: Int, with y: Int) -> Int {

    print(#function)

    return x + y

}

add(_:with:)

 

함수 구글링( numberOfRowsInSection: 두번 째 외부 매개변수명으로 구글링 )

https://developer.apple.com/documentation/uikit/uitableviewdatasource/tableview(_:numberofrowsinsection:)

 

tableView(_:numberOfRowsInSection:) | Apple Developer Documentation

Tells the data source to return the number of rows in a given section of a table view.

developer.apple.com

 

UITableView의 메서드 중 하나로, 테이블 뷰에서 각 섹션에 표시할 행(row)의 개수를 반환하는 역할을 합니다.

📌 함수 설명

@MainActor
func tableView(
    _ tableView: UITableView,
    numberOfRowsInSection section: Int
) -> Int
  • @MainActor: 이 어트리뷰트는 해당 함수가 메인 스레드에서 실행되어야 함을 나타냅니다. UI 관련 작업은 항상 메인 스레드에서 수행해야 하므로, 이 함수가 UI 업데이트를 포함하는 경우 메인 스레드에서 실행되도록 보장합니다.
  • tableView: 이 메서드는 UITableViewDelegate 프로토콜에 속하는 메서드입니다. 따라서 UITableView 객체에서 호출됩니다.
  • numberOfRowsInSection: 이 메서드는 특정 섹션에 몇 개의 행(row)이 존재하는지를 반환하는 메서드입니다. 예를 들어, 테이블 뷰에 여러 섹션이 있을 경우, 각 섹션별로 몇 개의 행을 표시할지를 이 함수에서 반환합니다.
  • tableView: 첫 번째 매개변수로 UITableView 객체가 전달됩니다. 이 매개변수는 테이블 뷰를 참조합니다.
  • section: 두 번째 매개변수는 섹션 번호를 나타냅니다. 한 테이블 뷰 내에 여러 섹션이 있을 수 있으며, 각 섹션에 대해 몇 개의 행을 표시할지를 지정합니다.
  • -> Int: 이 함수는 Int 타입의 값을 반환합니다. 반환값은 해당 섹션에 표시할 행의 개수입니다.

 

클래스

class Man {
    var age : Int // 프로퍼티
}  // err, 초기값 없음
  • 클래스 안에 있는 것을 프로퍼티라고 하고, 이 프로퍼티는 초기값이 있거나 옵셔널 변수(상수)로 선언해야 한다.
class Man {
    var age : Int = 0
}

// 일반 변수 생성
var x : Int
x = 10

// 객체 생성
var han : Man = Man()
han.age = 10
print(han.age)

 

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

var han = Man()
han.display()

 

  • 인스턴스 초기화하기 : designated initializer
class Man {
    var age : Int = 1
    var weight : Double = 3.5
    // init() {}
    func display() {    // 인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
    init(yourAge: Int, YourWeight: Double) {
        age = yourAge
        weight = YourWeight
    }   // designated initializer
}

var han = Man(yourAge: 20, YourWeight: 35.5)
han.display()
// Man 클래스를 정의합니다. 이 클래스는 사람을 나타내며 나이와 몸무게를 속성으로 가집니다.
class Man {
    
    // 나이를 저장할 변수 선언
    var age: Int
    
    // 몸무게를 저장할 변수 선언
    var weight: Double
    
    // display() 메서드는 인스턴스 메서드로, 객체의 나이와 몸무게를 출력합니다.
    func display() {    
        // 인스턴스의 나이와 몸무게를 출력합니다.
        print("나이=\(age), 몸무게=\(weight)")
    }
    
    // 이 부분은 클래스의 designated initializer입니다. 객체를 초기화할 때 나이와 몸무게를 받아서 설정합니다.
    init(age: Int, weight: Double) {
        // 인스턴스의 'age' 속성을 전달된 age 값으로 초기화
        self.age = age
        
        // 인스턴스의 'weight' 속성을 전달된 weight 값으로 초기화
        self.weight = weight
    }
}

// Man 클래스의 객체를 생성합니다. age=20, weight=35.5를 초기값으로 사용합니다.
var han = Man(age: 20, weight: 35.5)

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

 

  • 상속
class Man {
    var age : Int
    var weight : Double
    // init() {}
    func display() {    // 인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
    init(age: Int, weight: Double) {
        self.age = age
        self.weight = weight
    }   // designated initializer
}

class Student : Man {
    
}

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

 

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

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(age: age, weight: weight)
    }
}
var lee : Student = Student(age: 20, weight: 55.5, name: "홍길동")
lee.displayS()
lee.display()

⚡ self.name = name과 super.init(age: age, weight: weight)의 순서가 중요한 이유

Swift에서 클래스 상속을 사용할 때 **이니셜라이저(initializer)**에서 super.init()과 self 프로퍼티의 초기화 순서는 엄격하게 규칙을 따릅니다.

📌 규칙:

  1. 자신의 저장 프로퍼티(self.property)를 먼저 초기화한 후에 super.init()을 호출해야 합니다.
  2. 부모 클래스의 이니셜라이저(super.init())가 먼저 오면, 서브클래스의 저장 프로퍼티를 초기화하기 전에 부모 클래스가 완전히 초기화되지 않은 상태가 됩니다.
    • Swift는 이를 **"안정성을 보장하지 않는 초기화"**로 간주하여 컴파일 오류를 발생시킵니다.

 

  • 부모와 똑같은 이름의 함수가 있을 때에는 내가 만든 함 수 앞에 override 키워드를 붙인다
// 부모 클래스 Man 정의
class Man {
    // 나이 속성 (정수형)
    var age: Int
    
    // 몸무게 속성 (실수형)
    var weight: Double
    
    // 인스턴스 메서드: 현재 객체의 나이와 몸무게를 출력하는 함수
    func display() {    
        print("나이=\(age), 몸무게=\(weight)")
    }
    
    // Designated Initializer (기본 생성자)
    init(age: Int, weight: Double) {
        self.age = age       // 전달된 age 값을 객체의 age 속성에 저장
        self.weight = weight // 전달된 weight 값을 객체의 weight 속성에 저장
    } 
}

// Man 클래스를 상속받은 자식 클래스 Student 정의
class Student: Man {
    // 새로운 속성: 이름 (문자열)
    var name: String
    
    // 부모 클래스의 display() 메서드를 오버라이딩 (재정의)
    override func display() {
        // 이름, 나이, 몸무게를 함께 출력
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)")
    }
    
    // Student 클래스의 생성자 (Initialzer)
    init(age: Int, weight: Double, name: String) {
        self.name = name  // ✅ 먼저 자신의 속성을 초기화해야 함
        super.init(age: age, weight: weight)  // ✅ 부모 클래스의 속성을 초기화
    }
}

// Student 타입의 인스턴스를 생성하여 변수 lee에 저장
var lee: Student = Student(age: 20, weight: 55.5, name: "홍길동")

// Student 클래스의 display() 메서드 호출 (오버라이딩된 메서드 실행됨)
lee.display()

 

프로토콜

📌 Delegation 패턴 간단 설명

Delegation(델리게이션) 패턴객체가 직접 작업을 수행하는 대신, 다른 객체(Delegate)에게 맡기는 디자인 패턴입니다.

✅ 개념 정리

  • Delegator (위임하는 객체) → 다른 객체에게 일을 시키는 역할
  • Delegate (위임받는 객체) → 대신 일을 처리하는 역할
  • Protocol (프로토콜) → 위임할 기능을 정의하는 약속

✅ 예제: iOS의 UITableView

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    let tableView = UITableView()
    let items = ["Apple", "Banana", "Cherry"]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self  // ✅ 이벤트 처리 위임
        tableView.dataSource = self // ✅ 데이터 관리 위임
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count // ✅ 데이터 제공
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("\(items[indexPath.row]) 선택됨!") // ✅ 이벤트 처리
    }
}

 

 

protocol Runnable { // 대리하고 싶은 함수 목록 작성
    var x : Int { get set } // 읽기와 쓰기 가능 프로퍼티, {get}은 읽기 전용
    // property in protocol must have explicit { get } or { get set } specifier
    func run()  // 메서드는 선언만 있음
}

class Man : Runnable {      // 채택, adopt
    var x : Int = 1          // 준수, conform
    
    func run() {
        print("Runingman")   // 준수, conform
    }
}
var kim : Man = Man()
kim.run()

 

 

🍽 음식점 예시로 이해하는 Swift의 상속과 프로토콜


✅ 1. 상속 (Inheritance) 예제

"사장님은 기본적인 직원의 역할을 가지고 있고, 아르바이트생은 사장님의 역할을 일부 상속받는다."

// 부모 클래스: 직원(Employee)
class Employee {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func work() {
        print("\(name)이(가) 일하고 있습니다.")
    }
}

// 자식 클래스: 아르바이트생(PartTimeWorker) - Employee 상속
class PartTimeWorker: Employee {
    func cleanTable() {
        print("\(name)이(가) 테이블을 닦고 있습니다.")
    }
}

// 객체 생성
let boss = Employee(name: "사장님")
boss.work()  // 사장님이(가) 일하고 있습니다.

let worker = PartTimeWorker(name: "알바생")
worker.work()  // 알바생이(가) 일하고 있습니다. (부모의 메서드 사용)
worker.cleanTable()  // 알바생이(가) 테이블을 닦고 있습니다.

결과:

  • PartTimeWorker(아르바이트생)는 Employee(직원)를 상속받아 기본적인 work() 기능을 사용
  • 추가로 cleanTable() 같은 고유 기능을 가질 수 있음

✅ 2. 프로토콜 (Protocol) 예제

"직원마다 역할이 다르니, 특정 기능(예: 요리, 서빙)을 맡길 수 있도록 한다."

// 프로토콜 정의
protocol Cooking {
    func cook()
}

protocol Serving {
    func serve()
}

// 사장님 클래스
class Boss {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

// 요리사 클래스 - Cooking 프로토콜 채택
class Chef: Boss, Cooking {
    func cook() {
        print("\(name)이(가) 요리를 합니다.")
    }
}

// 서버 클래스 - Serving 프로토콜 채택
class Waiter: Boss, Serving {
    func serve() {
        print("\(name)이(가) 손님에게 서빙합니다.")
    }
}

// 객체 생성
let chef = Chef(name: "주방장")
chef.cook()  // 주방장이(가) 요리를 합니다.

let waiter = Waiter(name: "홀 직원")
waiter.serve()  // 홀 직원이(가) 손님에게 서빙합니다.

결과:

  • Chef(요리사)는 Cooking 프로토콜을 채택하여 요리 기능을 가짐
  • Waiter(서빙 직원)는 Serving 프로토콜을 채택하여 서빙 기능을 가짐
  • 각각 필요한 기능만 추가하면서, 불필요한 속성은 상속받지 않음

📌 정리!

개념 설명 음식점 예시
상속 (Inheritance) 부모의 기능을 물려받아 확장 Employee(직원) → PartTimeWorker(알바생)
프로토콜 (Protocol) 특정 기능을 정의하고 필요한 곳에서 구현 Cooking(요리 가능), Serving(서빙 가능)

🔹 한 줄 요약

상속: "사장님이 하는 일을 알바생도 할 수 있지만, 추가로 다른 역할도 가질 수 있음."
프로토콜: "요리사와 서버는 역할이 다르므로 필요한 기능만 선택적으로 추가함."

 

🎯 예제: 동물(Animal) 클래스를 상속받고, 소리 내기 기능을 프로토콜로 구현

// 1️⃣ 소리 내는 기능을 정의하는 프로토콜
protocol SoundMaker {
    func makeSound()
}

// 2️⃣ 기본 동물 클래스 (부모 클래스)
class Animal {
    var name: String

    init(name: String) {
        self.name = name
    }

    func describe() {
        print("이 동물의 이름은 \(name)입니다.")
    }
}

// 3️⃣ 개(Dog) 클래스: Animal을 상속받고 SoundMaker 프로토콜을 채택
class Dog: Animal, SoundMaker {
    func makeSound() {
        print("\(name)가 멍멍 짖습니다! 🐶")
    }
}

// 4️⃣ 사용 예제
let myDog = Dog(name: "바둑이")
myDog.describe()   // "이 동물의 이름은 바둑이입니다."
myDog.makeSound()  // "바둑이가 멍멍 짖습니다! 🐶"

📝 요약

  • Animal 클래스는 기본적인 동물 정보를 저장
  • SoundMaker 프로토콜은 makeSound() 메서드를 강제
  • Dog 클래스는 Animal을 상속하면서, SoundMaker 프로토콜을 채택하여 makeSound()를 구현

이렇게 하면 Dog 클래스는 Animal의 속성을 물려받고, SoundMaker의 기능을 따로 추가할 수 있습니다.

 

 

출처: iOS 프로그래밍 실무 강의 자료