iOS 프로그래밍 실무 4주차
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: 두번 째 외부 매개변수명으로 구글링 )
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 프로퍼티의 초기화 순서는 엄격하게 규칙을 따릅니다.
📌 규칙:
- 자신의 저장 프로퍼티(self.property)를 먼저 초기화한 후에 super.init()을 호출해야 합니다.
- 부모 클래스의 이니셜라이저(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 프로그래밍 실무 강의 자료