1-1. 프로퍼티 옵저버
값이 변경될 때 특정 동작을 수행함
프로퍼티 옵저버를 활용하면 프로퍼티의 값이 변경되기 전 특정 코드를 실행(willSet)하거나 값이 변경된 후(didSet)에 다른 코드를 실행할 수 있습니다! 이를 활용해서 속성의 변경 사항을 감지하고 추가 동작을 수행할 수 있어요.
class Order {
var quantity: Int = 0 {
didSet {
updateStatistics()
}
}
func updateStatistics() {
// 주문 수량에 따른 통계 업데이트 로직
print("주문 수량이 변경되었습니다. 통계를 업데이트합니다.")
}
}
let spongebob = Order()
spongebob.quantity = 10 // 주문 수량이 변경됨
1-2. 타입캐스팅
인스턴스의 실제 타입을 확인하거나 해당 타입으로 변환해주는 것
타입 캐스팅은 런타임에 동적으로 타입을 확인하고 조건에 따라 다른 동작을 수행할 수 있게 해줍니다. 다양한 타입을 다룰 때 유용하며, 안전한 형 변환을 지원합니다.
// 집게사장이 스펀지밥과 뚱이 중 어떤 캐릭터가 주문을 처리하는지 확인
class Employee {}
class Spongebob: Employee {
func takeOrder() {
print("스펀지밥이 주문을 처리합니다.")
}
}
class Patrick: Employee {
func takeOrder() {
print("뚱이가 주문을 처리합니다.")
}
}
let employee: Employee = Spongebob()
if let spongebob = employee as? Spongebob {
spongebob.takeOrder() // 스펀지밥이 주문을 처리
} else if let patrick = employee as? Patrick {
patrick.takeOrder()
}
- 다운 캐스팅 왜 / 어떨 때 써야됨?
1-3. 접근제한자
클래스, 구조체, 열거형, 프로퍼티, 메서드 등의 접근 범위를 제한해줌
접근 제한자를 사용하여 내부 구현을 숨기고 필요한 인터페이스만 노출시키는 등의 캡슐화(도어락같은 느낌)를 구현할 수 있습니다. 이를 통해 코드의 안정성과 유지 보수성을 높일 수 있습니다.
친구 집 비밀번호
class Cashier {
private var orderCount: Int = 0
func takeOrder() {
orderCount += 1
print("주문을 받았습니다.")
}
}
class Spongebob {
private let cashier = Cashier()
func placeOrder() {
cashier.takeOrder() // 다른 클래스에서는 직접 호출 불가능
}
}
let spongebob = Spongebob()
spongebob.placeOrder() // 주문을 받았습니다.
1-4. 클로저
일급 객체로서, 코드 블록을 변수나 상수에 저장하고 전달할 수 있는 기능
주로 비동기 작업, 콜백 함수, 정렬 등에 사용됩니다. 클로저를 사용하면 코드의 재사용성과 가독성을 높일 수 있으며, 간결한 문법으로 작성할 수 있습니다.
- 여기서 잠시, 일급객체란?
일급객체는 프로그래밍 언어에서 변수나 데이터 구조에 할당이 가능하고, 함수의 매개변수로 전달되고, 반환값으로 사용할 수 있는 객체를 말해요.
보다 직관적으로 이해하기 위해 외교관이라고 생각해 보시면 될 것 같습니다. 외교관은 어느 나라든 자유롭게 다닐 수 있고, 중앙선 한가운데 차를 세워놔도 함부러 견인을 할 수 없는 것 처럼 말이에요.만약 일급객체가 아니라면, 변수에 할당하거나 함수의 매개변수로 전달하는 등의 행위가 제한됩니다. 이는 코드를 유연하게 작성하는 데 어려움을 줄 수 있습니다. 일급객체를 사용하면 코드의 유지보수가 쉬워지며, 함수형 프로그래밍의 장점을 살릴 수 있습니다.
스펀지밥을 다시 불러 볼게요!
- 스펀지밥이 일급시민이라면, 그는 어디서든 일할 수 있고 집게사장(함수)에게 전달되어 어떤 업무를 할지 결정할 수 있습니다.이런식으로, 함수를 변수에 할당하고 다른 함수에 전달하여 사용함으로써 코드의 유연성과 가독성을 높일 수 있어요.
// 스펀지밥을 일급객체로 활용
func doWork(worker: () -> Void) {
print("일 시작 전에 노래 한 곡!")
worker() // 전달된 일급시민(함수)이 업무를 수행
print("일 끝나고 퇴근!")
}
// 스펀지밥의 업무 함수
func makeBurger() {
print("스펀지밥: 햄버거 만들기 시작!")
}
// 일급시민(함수)를 전달하여 업무 수행
doWork(worker: makeBurger)
// 클로저를 사용하여 주문이 완료되면 스펀지밥이 고객에게 알림
class Spongebob {
var notifyCustomer: (() -> Void)?
func placeOrder() {
// 주문 처리 로직...
notifyCustomer?()
}
}
let spongebob = Spongebob()
spongebob.notifyCustomer = {
print("주문이 완료되었습니다. 고객에게 알림을 보냅니다.")
}
spongebob.placeOrder() // 주문완료 알림 전송
사용법 좀 자세히
1-5. 고차함수
다른 함수를 매개변수로 받거나 함수를 반환하는 함수
고차함수를 사용하면 반복문을 직접 작성하지 않고도 간결하게 데이터를 처리할 수 있습니다. 주요 고차함수로는 map, filter, reduce 등이 있으며, 함수형 프로그래밍의 특징을 지원합니다. 알고리즘 풀이 보다보면 자주 보실거에요!
struct Order {
let name: String
let quantity: Int
}
let orders = [
Order(name: "햄버거", quantity: 2),
Order(name: "피자", quantity: 1),
Order(name: "스테이크", quantity: 3),
Order(name: "샐러드", quantity: 0)
]
var filteredOrders: [Order] = []
for order in orders {
if order.quantity > 0 {
filteredOrders.append(order)
}
}
for order in filteredOrders {
print(order.name) // 햄버거, 피자, 스테이크
}
struct Order {
let name: String
let quantity: Int
}
let orders = [
Order(name: "햄버거", quantity: 2),
Order(name: "피자", quantity: 1),
Order(name: "스테이크", quantity: 3),
Order(name: "샐러드", quantity: 0)
]
let filteredOrders = orders.filter { order in // for문 사용하지 않고 간단히!!
order.quantity > 0
}
for order in filteredOrders {
print(order.name) // 햄버거, 피자, 스테이크
}
1-6. 예외처리
프로그램 실행 중 발생할 수 있는 예외 상황에 대한 처리 방법
예외를 미리 예측하고 처리함으로써 프로그램의 안정성을 높일 수 있습니다. 예외를 처리하는 방법으로는 do-catch문이 주로 사용되며, 오류 정보를 전달하고 복구할 수 있습니다.
enum OrderError: Error {
case outOfStock // 품절 ㅜㅠ
case invalidQuantity // 게살버거 10000개요...?
}
func placeOrder(quantity: Int) throws {
guard quantity > 30 else {
throw OrderError.invalidQuantity
}
// 재고 확인 로직...
if isOutOfStock {
throw OrderError.outOfStock
}
// 주문 처리 로직...
}
do {
try placeOrder(quantity: 3)
print("주문이 완료되었습니다.")
} catch OrderError.outOfStock {
print("주문할 상품이 품절되었습니다.")
} catch OrderError.invalidQuantity {
print("유효하지 않은 주문 수량입니다.")
} catch {
print("알 수 없는 오류가 발생했습니다.")
}
1-7. ARC
ARC(Automatic Reference Counting)는 메모리 관리 기법
객체가 더 이상 사용되지 않을 때 자동으로 메모리에서 해제됩니다. 개발자가 명시적으로 메모리 관리를 신경쓰지 않아도 되므로 메모리 관리 부담을 줄여줍니다.
// 스펀지밥이 집게리아에서 주문을 처리하는 동안 메모리에서 해제되지 않도록 함
class Order {
let customer: String
init(customer: String) {
self.customer = customer
print("\\(customer)님의 주문이 생성되었습니다.")
}
deinit { // 이런 식으로 해제 시켜 줄 수 있음
print("\\(customer)님의 주문이 해제되었습니다.")
}
}
func placeOrder() {
let order = Order(customer: "스폰지밥")
// 주문 처리 로직...
}
placeOrder() // 스폰지밥님의 주문이 생성되었습니다. 스폰지밥님의 주문이 해제되었습니다.
1-8. 프로토콜
인터페이스(계약)를 정의하기 위한 일종의 명세서
프로토콜을 채택하는 타입은 프로토콜에서 요구한 속성과 메서드를 구현해야 합니다. 다중 상속과 비슷한 효과를 낼 수 있으며, 코드의 재사용성과 확장성을 높일 수 있습니다.
protocol OrderHandler {
func takeOrder()
func prepareOrder()
func serveOrder()
}
class Spongebob: OrderHandler {
func takeOrder() {
print("스펀지밥이 주문을 받습니다.")
}
func prepareOrder() {
print("스펀지밥이 주문을 준비합니다.")
}
func serveOrder() {
print("스펀지밥이 주문을 서빙합니다.")
}
}
class Krabs: OrderHandler {
func takeOrder() {
print("집게사장이 주문을 받습니다.")
}
func prepareOrder() {
print("집게사장이 주문을 준비합니다.")
}
func serveOrder() {
print("집게사장이 주문을 서빙합니다.")
}
}
let spongebob = Spongebob()
spongebob.takeOrder() // 스펀지밥이 주문을 받습니다.
1-9. 확장
기존의 클래스, 구조체, 열거형 등에 새로운 기능을 추가하는 것
확장을 통해 기존 타입의 기능을 확장하거나 프로토콜을 채택한 타입에 기능을 추가할 수 있습니다. 이를 통해 기존 코드를 변경하지 않고도 타입을 확장할 수 있습니다.
class AViewConotroller: UIViewController() {
override func viewDidLoad() {
getDeviceWidth() // 함수 호출부
}
func getDeviceWidth() -> CGFloat {
return UIScreen.main.bounds.width
}
}
class BViewController: UIViewController() {
override func viewDidLoad() {
getDeviceWidth() // 함수 호출부
}
func getDeviceWidth() -> CGFloat {
return UIScreen.main.bounds.width
}
}
getDeviceWidth()라는 메서드가 중복으로 선언이 되어있죠? 대부분의 개발자는 이러한 중복코드를 줄이고 싶어하는데요, 이렇게 선언해두면 중복으로 메서드 구현을 할 필요 없이 함수만 호출해서 사용 할 수 있습니다 :)
extension UIViewController {
func getDeviceWidth() -> CGFloat {
return UIScreen.main.bounds.width
}
}
class AViewConotroller() {
override func viewDidLoad() {
getDeviceWidth() // 함수 호출부
}
}
class BViewConotroller() {
override func viewDidLoad() {
getDeviceWidth() // 함수 호출부
}
}
1-10. 제네릭
타입에 의존하지 않고 일반화된 코드를 작성하는 기능.
제네릭을 사용하면 다양한 타입에 대해 동일한 코드를 작성할 수 있으며, 타입 안정성을 보장합니다. 컬렉션, 함수, 구조체 등 다양한 곳에서 활용됩니다.
struct Order<T> {
let name: T
init(name: T) {
self.name = name
}
}
let burgerOrder = Order(name: "햄버거")
let pizzaOrderCount = Order(name: 12)
let steakOrderPrice = Order(name: 18.9)
print(burgerOrder.name) // "햄버거"
print(pizzaOrderCount.name) // 12
print(steakOrderPrice.name) // 3.14
Any랑 다른 점
1-11. 비동기와 네트워킹
작업을 순차적으로 실행하지 않고 병렬로 처리하는 기법
비동기 작업은 네트워킹과 밀접한 관련이 있으며, 네트워크 요청과 응답을 비동기적으로 처리할 수 있습니다. 비동기와 네트워킹을 통해 응답성과 성능을 향상시킬 수 있습니다.
func orderFoodSync() {
let pizza = orderPizza() // 여기서 주문하고 기다림
let chicken = orderChicken()
let tteokbokki = orderTteokbokki()
// 주문한 음식이 도착하면 처리
eat(pizza)
eat(chicken)
eat(tteokbokki)
}
위 예시처럼 배달의 민족에서 다양한 음식(피자, 치킨, 엽떡 등)을 시킬 때 음식이 배달 된 이후 음식을 시키는 것이 동기 방식이고
func orderFoodAsync() {
let dispatchGroup = DispatchGroup()
var pizza: Food?
var chicken: Food?
var tteokbokki: Food?
// 각 음식을 동시에 주문
DispatchQueue.global().async(group: dispatchGroup) {
pizza = orderPizza()
}
DispatchQueue.global().async(group: dispatchGroup) {
chicken = orderChicken()
}
DispatchQueue.global().async(group: dispatchGroup) {
tteokbokki = orderTteokbokki()
}
// 모든 주문이 완료될 때까지 기다리지 않음
dispatchGroup.notify(queue: .main) {
// 주문한 음식이 도착하면 처리
eat(pizza!)
eat(chicken!)
eat(tteokbokki!)
}
}
비동기 방식에서는 여러 음식을 동시에 주문하고, 각각이 도착하는대로 처리합니다. 이때 DispatchGroup을 사용하여 모든 주문이 완료될 때까지 기다리지 않고 계속 진행합니다.
1-12. Combine
Combine은 Swift에서 제공하는 함수형 반응형 프로그래밍 프레임워크
비동기 데이터 스트림을 처리하기 위한 연산자들을 제공하며, 데이터의 흐름을 선언적으로 조작할 수 있습니다. 특히 UI 업데이트, 네트워킹, 사용자 입력 등에서 유용하게 사용됩니다.
import Combine
class OrderManager {
var orderSubject = PassthroughSubject<String, Never>()
func placeOrder(order: String) {
// 주문 처리 로직...
// 스트림에 주문을 발행
orderSubject.send(order)
}
}
let orderManager = OrderManager()
// 주문 스트림을 구독하여 처리
let cancellable = orderManager.orderSubject
.sink { order in
print("주문이 들어왔습니다: \\(order)")
}
// 주문 발행
orderManager.placeOrder(order: "햄버거") // 주문이 들어왔습니다: 햄버거
orderManager.placeOrder(order: "피자") // 주문이 들어왔습니다: 피자
// 구독 취소
cancellable.cancel()
1-13. RXSwift
RXSwift는 ReactiveX 프레임워크의 Swift 버전으로, 비동기 및 이벤트 기반 프로그래밍을 위한 라이브러리
Observable 시퀀스를 사용하여 데이터 스트림을 처리하고, 연산자를 통해 데이터를 변환하고 결합할 수 있습니다. 함수형 및 반응형 프로그래밍 패러다임을 지원합니다.
import RxSwift
class OrderManager {
var orderSubject = PublishSubject<String>()
func placeOrder(order: String) {
// 주문 처리 로직...
// 스트림에 주문을 발행
orderSubject.onNext(order)
}
}
let orderManager = OrderManager()
// 주문 스트림을 구독하여 처리
let disposable = orderManager.orderSubject
.subscribe(onNext: { order in
print("주문이 들어왔습니다: \\(order)")
})
// 주문 발행
orderManager.placeOrder(order: "햄버거") // 주문이 들어왔습니다: 햄버거
orderManager.placeOrder(order: "피자") // 주문이 들어왔습니다: 피자
// 구독 해제
disposable.dispose()
'스폰지밥으로 공부하는 swift > swift 문법' 카테고리의 다른 글
클로저 왜 씀? (0) | 2024.01.29 |
---|---|
[값타입 vs 참조타입]을 '집게리아 레시피'로 이해 해보좌 (1) | 2024.01.28 |
final 왜 씀..? (0) | 2024.01.06 |
unowned vs weak 뭐가 다른데? (1) | 2024.01.04 |
참조, 강한참조 약한참조, 순환참조...? (0) | 2024.01.03 |