{ (parameter) -> return type in // 구현 코드 } // 함수와 클로저 비교 func pay(user: String, amount: Int) { // code } let payment = { (user: String, amount: Int) in // code }
func func1(_ param: String) -> String {
return param + "!"
}
func func2(name: String) -> String {
return name + "***"
}
var a: (String) -> String = func1
// 함수를 변수에 할당가능(변수가 함수를 가르키게 됨)
var a: (String) -> String = func1 // func1의 타입을 가지는 변수를 선언함으로써 변수 a에 함수 func1을 할당한 것
a("안녕")
a = func2
a("hello")
// 함수(클로저)를 변수에 할당해서
let closure1 = { (param: String) -> String in // 클로저 리터럴
return param + "!"
}
// 사용(실행)
closure1("스티브")
closure(param: { (str: String) in return str.count } )
// 1. 타입 추론
closure(param: { str in return str.count } )
// 2. closure가 한 줄일 경우, return을 생략할 수 있음
closure(param: { str in str.count } )
// 3. Argument 이름을 축약할 수 있음(첫 번째 Argument = $0, 두 번째 Argument = $1 ...)
closure(param { $0.count } )
// 4. Trailing Closure 후행 클로저(매개변수 생략 가능)
closure() { $0.count }
// 5. 함수 생성자 생략
closure { $0.count }
예시) 함수의 입력타입과 반환타입이 (Int, Int) -> Int
일 때
let closure: (Int, Int) -> Int = { (a, b) in return a * b }
// Argument 이름 축약까지 진행하면
let closure: (Int, Int) -> Int = { $0 * $1 }
// (a, b) in return a * b 부분이 $0 * $1 구문으로 축약되었다고 보면 된다.
클로저가 메소드의 인자로 전달되었을 때, 메소드의 실행이 종료된 후 실행되는 클로저(비동기)
파라미터 타입 앞에 무조건@escaping
키워드를 붙여주어야 함
또한, @escaping
키워드가 붙은 클로저가 self
의 요소를 사용할 때 명시적으로 self.x
등과 같이 언급해주어야 함
DispatchQueue.global().async {
let result = "finished"
print(result)
}
/*
위의 함수는 어떤 함수들의 앞에 있어도 순차적으로 실행되는 것이 아니라,
비동기적으로, 다른 스레드에서 실행되어지게 됨.
따라서 함수의 실행은 스레드에서 작업이 끝나는 순간 진행되어짐.
즉, 같은 파일에 있는 함수들과 병행으로 실행된다는 의미
*/
map
함수 : 컬렉션 내부의 기존 데이터를 변형하여 새로운 컬렉션을 생성예시 )
let numbers = ["1", "2", "3", "4", "5"]
var numArray: [Int] = number.map{ Int($0) }
print(numArray) // [1, 2, 3, 4 ,5]
filter
함수 : 기존 컨테이너의 요소 중 조건에 만족하는 값으로 이루어진 새로운 컨테이너를 만들어서 반환예시)
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var oddNumArray: [int] = numbers.filter{ $0 % 2 == 0 }
print(oddNumArray) // [2, 4, 6, 8, 10]
reduce
함수 : 기존 컨테이너 요소에 대해 정의한 클로저로 매핑한 결과를 새로운 컨테이너로 반환예시)
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var sum: Int = numbers.reduce(0, +) // 초기값 0, 모든 요소를 더한 값을 반환
var sum2 : Int = numbers.reduce(0){ $0 + $1 } // 위의 코드와 같은 역할을 함
Error
: 던져질 수 있는 오류 값을 나타내는 유형, Error
프로토콜을 채택하여 사용자 정의 에러를 정의할 수 있음throws
와 do-catch
문
throws
: 리턴값 반환 이전에 오류 발생 시에 에러 객체를 반환한다는 의미. 오류가 발생할 가능성이 있는 메소드 제목 옆에 사용
throw
: 오류가 발생할 구간에서 사용
do-catch
:throw
로 던진 에러를do-catch
문에서 처리한다.
예시)
enum CustomError: Error {
case outOfBounds
case invalidInput(String)
}
func processValue(_ value: Int) throws -> Int {
if value < 0 {
throw CustomError.invalidInput("Value cannot be negative")
} else if value > 100 {
throw CustomError.outOfBounds
}
return value * 2
}
// do-catch 블록을 이용하여 throwing 함수 호출 및 에러 처리하기
do {
let result = try processValue(50)
print("Result is \(result)")
} catch CustomError.outOfBounds {
print("Value is out of bounds!")
} catch CustomError.invalidInput(let errorMessage) {
print("Invalid Input: \(errorMessage)")
} catch {
print("An error occurred: \(error)")
}
// 출력 : Result is 100
do {
let result = try processValue(-10)
print("Result is \(result)")
} catch CustomError.outOfBounds {
print("Value is out of bounds!")
} catch CustomError.invalidInput(let errorMessage) {
print("Invalid Input: \(errorMessage)")
} catch {
print("An error occurred: \(error)")
}
// 출력 : Invalid Input: Value cannot be negative
try, try?, try!
try
: 에러가 발생할 수 있는 코드 블록 표시, 에러를 던질 수 있는 함수나 메소드 호출 시 사용됨. do-catch
문을 사용해서 에러를 처리할 수 있음.try?
: do-catch
문 없이 사용가능, 에러 발생 시에는 nil
값을 반환, 에러가 없다면 옵셔널 값을 반환함.try!
: 에러 발생 시 프로그램 강제 종료, 반환 타입은 언제나 옵셔널이 해제된 값, 오류가 없다는 보장이 있을 때 사용하는 것을 권장.enum MyError: Error {
case invalidInput
}
func someThrowingFunction(value: Int) throws -> String {
guard value >= 0 else {
throw MyError.invalidInput // value가 음수인 경우 에러를 던짐
}
return "The value is \(value)"
}
// throwing 함수 호출과 에러 처리하기
do {
let result = try someThrowingFunction(value: 5)
print(result)
} catch {
print("Error occurred: \(error)")
}
do {
let result = try someThrowingFunction(value: -2) // 에러 발생
print(result)
} catch {
print("Error occurred: \(error)") // 음수 값을 처리하는 에러
}
// try?를 사용하여 에러 처리하기
let result1 = try? someThrowingFunction(value: 5) // 유효한 값 호출
print(result1) // Optional("The value is 5")
let result2 = try? someThrowingFunction(value: -2) // 에러 발생
print(result2) // nil
// try!를 사용하여 에러 처리하기
let result3 = try! someThrowingFunction(value: 5) // 유효한 값 호출
print(result3) // The value is 5
let result4 = try! someThrowingFunction(value: -2) // 에러 발생
print(result4)
객체가 생성될 때마다 참조 횟수가 1 증가, 해당 객체를 참조하는 다른 객체나 변수가 없어지거나 더 이상 사용되지 않을 때 참조 횟수가 1 감소한다. 참조 횟수가 0이 되면 해당 객체는 메모리에서 해제.
nil
이 할당되는 행위) 참조 횟수 1 감소nil
로 설정하여 메모리 crush를 예방하는 기능.강한 참조 순환은 메모리 누수 문제를 일으키는 문제
참조는 기본적으로 (default 값으로) 강한 참조를 사용함. 이 때, 참조를 잘못 사용하면 메모리 누수 문제가 발생할 가능성이 있음.
대표적인 메모리 누수가 일어나는 경우는 두 개 이상의 인스턴스가 서로를 강한 참조할 때 발생됨.
문제 해결 방법
weak
키워드를 사용하는 참조 사용, 옵셔널로 선언되는 참조, 참조 대상이 메모리에서 해제되면 자동으로 nil
로 설정됨. weak
키워드가 붙으면 다른 객체에서 해당 부분을 참조해도 참조횟수를 올리지 않음.unowned
참조는 참조 대상이 해제될 수 있는 경우에만 사용. 그리고 그 객체가 메모리에서 해제되지 않은 상태에서만 해당 unowned
참조를 사용해야 함gettable, settable
을 명시함.gettable
: 추 후에 값을 할당할 수 있는지settable
: 미리 할당된 값을 읽기만 할 수 있게됨.var
변수로 선언해야 함.// 예시
protocol Student {
var studentId: Int { get set }
var name: String { get }
func printInfo() -> String
}
struct UnderGraduateStudent: Student {
var studentId: Int
var name: String
var major: String
func printInfo() -> String {
return "\(name), whose student id is \(studentId), is major in \(major)"
}
}
struct GraduateStudent: Student {
var studentId: Int
var name: String
var degree: String
var labNumber: Int
func printInfo() -> String {
return "\(name), member of lab no.\(labNumber), has a \(degree) degree"
}
}
// 프로토콜은 타입으로서도 사용가능
let underGraduate: Student = UnderGraduateStudent(studentId: 1, name: "홍길동", major: "computer")
let graduate: Student = GraduateStudent(studentId: 2, name: "김철수", degree: "master", labNumber: 104)
let studentArray: [Student] = [underGraduate, graduate]
프로토콜은 타입으로써 사용할 수도 있다는 점을 명심하자!
associatedtype
: 프로토콜 내에서 실제 타입을 명시하지 않고, 해당 프로토콜을 채택하는 타입에서 실제 타입을 결정하도록 하는데 사용associatedtype
으로 정의된 타입은, 프로토콜을 채택한 클래스나 구초제, 열거형 내에서 그 타입을 정의할 수 있음을 의미함typealias
: 기존 타입에 대해 새로운 이름을 지정하거나, 복잡한 타입에 대한 간결한 별칭을 생성할 때 사용Extension
extension <TypeName> {
code blocks // 추가하고 싶은 속성, 인스턴스, 초기화 구문, 프로토콜, 서브스크립트, 중첩타입 추가
}
Generic
placeholder ex) T, U, V etc.
사용(타입의 종류를 알려주지 않지만, 어떤 타입이라는 것을 의미)placeholder
<T, U>와 같이 여러개 구현 가능 inout
키워드 : 매개변수에 붙여, 함수 외부에서도 호출이 가능하게끔 해주는 키워드. 호출 시 값 앞에 '&' 문자를 붙여 해당 값을 참조로 호출 가능
// 함수 정의
func increment(_ value: inout Int) {
value += 1
}
var number = 5
print("Before increment: \(number)") // 출력: Before increment: 5
// 함수 호출 시 매개변수에 &를 사용하여 변수의 참조를 전달
increment(&number)
print("After increment: \(number)") // 출력: After increment: 6
// 제네릭을 사용하면 타입에 상관없이 사용가능, a, b는 T로 표현된 똑같은 타입을 가져야 함
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
where
키워드 : 제네릭의 제약조건(Constraints) 중 하나. 해당 제약을 충족하는 타입만을 사용하도록 강제함. 특정 프로토콜 채택, 특정 타입과의 상속 관계 등을 제한 가능.
// 프로토콜 채택 제약 예시
func process<T>(value: T) where T: Numeric {
// Numeric 프로토콜을 채택하는 타입만을 제네릭 타입 T로 받음
print("Value is a numeric type.")
}
process(value: 5) // 출력: Value is a numeric type.
process(value: 3.14) // 출력: Value is a numeric type.
// process(value: "Hello") // 컴파일 에러 - 문자열은 Numeric 프로토콜을 채택하지 않음
// 클래스의 상속 관계 제약 예시
class MyClass {}
class MySubclass: MyClass {}
func process<T>(value: T) where T: MyClass {
print("Value is an instance of MyClass or its subclasses.")
}
let obj = MySubclass()
process(value: obj) // 출력: Value is an instance of MyClass or its subclasses.
// process(value: "Hello") // 컴파일 에러 - 문자열은 MyClass 또는 그 하위 클래스가 아님
DispatchQueue.main
을 통해 실행DispatchQueue.global()
은 전역 스레드에서 작업을 처리하는데 사용DispatchQueue
에서 async, sync
를 사용하여 작업을 추가할 시, 백그라운드에서 실행됨// 동기적으로 실행되는 작업
DispatchQueue.global().sync {
print("Synchronous Task")
}
// 비동기적으로 실행되는 작업
DispatchQueue.global().async {
print("Asynchronous Task")
}
// 백그라운드에서 비동기 작업 실행
DispatchQueue.global().async {
// 여기서 백그라운드에서 실행될 작업을 수행합니다.
for i in 1...5 {
print("Background Task \(i)")
}
// 작업이 완료되었음을 메인 스레드로 알립니다.
DispatchQueue.main.async {
print("Background Task Completed, Updating UI")
// UI 업데이트 등을 수행할 수 있습니다.
}
}
// URLSession의 dataTask 함수 정의
func dataTask(
with url: URL,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask // 비동기 함수이기 때문에 @escaping 키워드 작성
// 네트워크 작업을 처리할 함수
func fetchData() {
if let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1") {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// 네트워크 작업 완료 후 실행될 코드 (비동기적으로 실행)
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
if let httpResponse = response as? HTTPURLResponse {
print("Status code: \(httpResponse.statusCode)")
}
if let data = data {
do {
// JSON 데이터 파싱
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let jsonDict = json as? [String: Any] {
// 파싱된 데이터 활용
print("Received JSON data: \(jsonDict)")
}
} catch {
print("JSON parsing error: \(error.localizedDescription)")
}
}
}
task.resume() // 네트워크 작업 시작 (비동기적으로 실행됨)
}
}
// fetchData 함수 호출
fetchData()
Combine
함수형 반응형 프로그래밍을 위한 프레임워크, 비동기적인 데이터 스트림을 다루고 조작하는데 사용
Publisher
: 데이터 스트림 생성, 이벤트를 방출하는 타입. 값 방출이 가능(오류 방출 혹은 작업 완료를 알릴 수 있음)Subscriber
: 발행자에서 방출되는 이벤트를 받아 처리하는 타입, 값을 받아서 처리, 오류나 작업 완료 이벤트를 처리함Operator
: 데이터 스트림을 조작하고 변환하기 위한 다양한 연산자(map, filter, flatMap, etc.
)가 제공됨.Cancellable
: 구독을 취소할 수 있는 타입, 더 이상 이벤트를 받지 않도록 설정할 수 있음// combine 예시
import Foundation
import Combine
class DataModel {
@Published var textValue: String = ""
} // @publsher 프로퍼티 래퍼는 값의 변경이 있을 때마다 해당 값의 변경 사항을 게시함
// 값이 변경된 것을 newValue 라는 이름의 변수로 게시함
// 변경되기 전의 값을 oldValue 라는 이름의 변수로 게시
let dataModel = DataModel()
let cancellable = dataModel.$textValue.sink { newValue in
print("Value changed to: \(newValue)")
} // $textValue는 textValue의 publisher를 의미
// sink 연산자는 해당 publisher를 구독하고, 값이 변경될 때마다 클로저 내의 코드를 실행하는 연산자
dataModel.textValue = "Hello, Combine!"
dataModel.textValue = "Another value"
/*
출력
Value changed to:
Value changed to: Hello, Combine!
Value changed to: Another value
*/
Swift 언어로 작성된 함수형 반응형 프로그래밍을 위한 "라이브러리", 옵저버블 시퀀스와 이벤트 기반 프로그래밍을 간편하게 처리할 수 있도록 지원
일반적으로 특정 기능을 수행하는, 프로그램에서 재사용 가능한 코드의 모음
개발시간 단축, 효율적인 개발에 도움이 됨
다양한 형태와 목적으로 제공됨
보통 함수, 클래스, 모듈, 프레임워크 형태로 제공됨.
프로젝트에서 사용하는 다양한 라이브러리 및 외부 의존성들을 관리하고, 프로젝트에 효과적으로 통합하기 위한 도구
대표적 Dependency Manager
Observable
은 next, error, completed
등의 이벤트를 발생시키는데에 사용됨Observable
을 변형하거나 조작하는 함수, 데이터 스트림 조작에 사용 // RxSwift의 예시
import Foundation
import RxSwift
import RxCocoa
class DataModel {
let textValueSubject = BehaviorSubject<String>(value: "")
// BehaviorSubject를 사용해 textValueSubject를 초기화
var textValue: Observable<String> {
return textValueSubject.asObservable()
} // textValueSubject를 textValue의 Observable로 반환
}
let dataModel = DataModel()
let disposable = dataModel.textValue.subscribe(onNext: { newValue in
print("Value changed to: \(newValue)")
}) // Subscribe 메소드가 textValue를 구독하고, 값의 변경이 있을 때마다 onNext 클로저 내부 함수를 실행
dataModel.textValueSubject.onNext("Hello, RxSwift!")
dataModel.textValueSubject.onNext("Another value")
/*
출력
Value changed to:
Value changed to: Hello, RxSwift!
Value changed to: Another value
*/