Swift

    [Swift] 셀 재사용에 따른 중복 binding 이슈(feat.disposeBag)

    [Swift] 셀 재사용에 따른 중복 binding 이슈(feat.disposeBag)

    최근 RxSwift를 활용한 프로젝트 속 diffable datasource를 통해서 collection view를 구현하던 중, collection view의 아이템이 많아지니 collection view에 binding된 여러 요소들에게서 예상치 못한 에러가 발생했다. 먼저 에러가 생기는 기존의 코드를 확인해보자. self.datasource = UICollectionViewDiffableDataSource( collectionView: self.collectionView, cellProvider: { [weak self ] collectionView, indexPath, item in guard let cell = collectionView.dequeueReusableCell( withReuseId..

    [Swift] Async, Await (2) [feat. WWDC]

    [Swift] Async, Await (2) [feat. WWDC]

    해당 글에서는 Swift Concurrency의 성능에대해서 알아봅니다. (WWDC21 Swift Concurrency Behind the Scenes 21분까지의 내용을 정리했습니다.) 두 가지를 중점적으로 볼 것입니다. - Threading Model (GCD와의 비교) - Actor를 활용하여 Synchronization을 하는 방법 Threading Model 아래와 같은 앱이 있다고 생각해봅시다. - Main Thread에서는 유저의 event gesture를 처리 - Main Thread는 비동기 Serial Queue에 loadNewsFeeds()를 호출한다. Serial Queue에서 Work를 비동기적으로 던지는 이유? -> 왜냐하면 main thread는 유저의 input을 받을 준비를..

    [Swift] Async, Await(1) (feat. WWDC)

    [Swift] Async, Await(1) (feat. WWDC)

    아래는 WWDC21 Meet async/await in Swift 및 Swift concurrency: Behind the scenes를 보고 정리한 내용입니다! 만약 네트워크 통신을 통해서 이미지를 받아오고, 썸네일을 만들어본다고 가정해봅시다. 해당 과정은 아래의 도식화된 그림과 같은 과정을 따르게 됩니다. 근데, dataTask(with:completion:) 메소드와 prepareThumbnail(of:completionHandler:)의 경우에는 시간이 오래걸리기에 비동기 코드를 활용해야됩니다. 그러면 아래와 같이 completion handler를 활용한 코드로 구현할 수 있습니다. 위 코드에는 보자마자 알 수 있드시, 생길 수 있는 문제점들이 많습니다. 먼저 completionHandler를 ..

    [Swift] WWDC21 - ARC in Swift: Basics and Beyond

    [Swift] WWDC21 - ARC in Swift: Basics and Beyond

    해당 글은 WWDC21 - ARC in Swift: Basics and Beyond를 공부하고 난 후, 정리한 글입니다! 먼저 오브젝트의 라이프 타임과 Swift의 ARC에 대해서 복습해보겠습니다. 오브젝트의 라이프타임은 초기화와 같이 시작이되고, 마지막 사용 이후에 라이프 타임은 종료됩니다. 라이프 타임 이후에는 ARC가 자동으로 메모리 할당 해제하게 됩니다. 그리고 Swift Compiler는 retain/release 작업의 삽입을컴파일 타임에 진행합니다. 여기서 말하는 retain 작업은 런타임에 reference count를 증가시키고, release 작업은 reference count를 감소시킵니다. 마지막으로 reference count가 0이되면, 오브젝트는 할당해제가 됩니다. 예를 통해서..

    [Swift] [weak self]는 언제 사용할까?

    [Swift] [weak self]는 언제 사용할까?

    우리는 무의식적으로 [weak self]를 활용할 때가 매우 많습니다. 흔히 [weak self]를 활용하는 이유를 메모리 릭이라고 합니다. 그렇다면, 우리는 항상 [weak self]를 활용하면 될까요? [weak self]를 언제 사용하고, 무엇인 지 공부해 보겠습니다.(weak를 남발하는 것의 side effect는 다른 글에서 공부해보겠습니다!) 먼저 클로져의 캡쳐 현상에 대해서 간단하게 알아보겠습니다. 클로져의 캡쳐 클로저는 내부에서 외부 변수를 사용할 때, 해당 변수를 클로져 내부적으로 저장합니다. 근데, 해당 변수가 값 타입이든 참조 타입이든지 간에 무조건 memory capture를 합니다. 즉, 클로져 안에서 값 타입인 외부 변수를 수정하면 참조 타입과 같이 변경이 되는 것이죠. 쉽게 말..

    [Swift] WWDC16 Understanding Swift Performance(3)

    [Swift] WWDC16 Understanding Swift Performance(3)

    저번 글 포스트에 이어서 계속해서 WWDC16 Understanding Swift Performance를 정리해 보겠습니다. 이번에는 Generic 타입 변수가 어떻게 저장되고 복사되는 지를 이야기해보겠습니다. 또한, method dispatch 또한 어떻게 진행되는 지 알아보겠습니다. 아래와 같은 코드가 존재한다고 해봅시다. 위 코드는 제네릭을 활용하여 제네릭 타입을 Drawable 프로토콜로 제약을 주었습니다. 그렇다면 위 코드와 그냥 파라미터 타입을 Drawable로 설정한 코드는 어떠한 차이가 있을까요? Generic 타입은 Static Polymorphism을 지원합니다. 또 다른 말로는 Parametric Polymorphism이라고도 합니다. 위와 같은 코드에서 foo 함수가 실행되면, Sw..

    [Swift] WWDC16 Understanding Swift Performance(2)

    [Swift] WWDC16 Understanding Swift Performance(2)

    저번 글 포스트에 이어서 계속해서 WWDC16 Understanding Swift Performance를 정리해 보겠습니다. 프로토콜 타입의 변수들이 어떻게 저장되며 복사되고, 프로토콜의 method dispatch는 어떻게 작동하는지 알아봅시다. Protocol Types 아래와 같은 코드를 작성했다고 생각해 봅시다. 위 코드는 Drawable 프로토콜이 정의되어 있고, Point와 Line은 각각 Drawable 프로토콜을 채택하고 있습니다. 그리고 Drawable 타입을 담고 있는 배열도 정의되어 있습니다. 해당 코드는 다형성을 제공합니다.(Polymorphism) 그러나 V-table dispatch를 하는 공통된 상속 관계를 가지고 있지 않습니다.(Point와 Line은 구조체입니다!) 그러면 ..

    [Swift] WWDC16 Understanding Swift Performance(1)

    [Swift] WWDC16 Understanding Swift Performance(1)

    WWDC16 Understanding Swift Performance를 들으면 정리한 내용입니다. 스위프트의 퍼포먼스를 고려하며 코드를 짜기위해서 고민해야되는 부분은 크게 세 가지입니다. 차근차근 세 가지가 무엇인 지 알아보겠습니다. Allocation Stack의 경우, LIFO(Last In First Out)구조로써, stack 끝에 포인터가 위치합니다. 우리는 Stack pointer가 가르키는 곳을 줄임으로써 필요한 메모리를 할당하고, 포인터를 증가시킴으로써 메모리를 할당 해제합니다. (위에서 아래로!) 메모리 할당하는 과정에서 Stack pointer는 원래 있던 곳으로 다시 위치하게 됩니다. Heap의 경우, 훨씬 더 다이나믹하지만 stack에 비해서는 덜 효율적입니다. Heap에 메모리를 ..

    [Swift] 다형성을 활용하여 Enum 대체하기

    [Swift] 다형성을 활용하여 Enum 대체하기

    OCP 법칙 확장에는 열려있고, 변경에는 닫혀있어야 한다. '확장에는 열려있고’라는 말은 손 쉽게 기능 추가를 할 수 있고(기존의 코드 변경 없이), ‘변경에는 닫혀있다’라는 말은 기능 추가 및 수정을 할 때, 여러 코드들이 한꺼번에 같이 수정된다는 말입니다. 그렇다면, 이러한 OCP 법칙을 만족하지 못하는 상황이 뭐가 있을까요? 일단 무분별하게 사용하고 있는 enum을 생각해볼 수 있습니다. Enum에 case를 하나 추가하는 순간, 해당 enum을 switch문으로 분기처리하고 있는 곳에서 새로운 코드를 추가해주어야합니다. 그렇다면, 이러한 문제점을 해결할 수 있는 방법이 무엇이 있을까요? 평소에 저희는 OCP법칙을 만족시키기 위하여 프로토콜을 적극적으로 활용했습니다. 이러한 문제점 또한 enum을 ..

    [Swift] 다형성과 추상화

    [Swift] 다형성과 추상화

    다형성(Polymorphism) OOP의 4가지 특성(상속, 추상화, 캡슐화, 다형성) 중 하나인 다형성은 무엇을 뜻하는 것일까요? 다형성의 관용적인 개념은 같은 모양의 코드가 다른 행위를 하는 것을 뜻합니다. 즉, 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질을 뜻하는 것입니다. 마치 핸드폰 속 키보드를 통하여 다이얼을 누르기도 하고, 문자를 하기도 하며, 게임도 하는 것과 같이 모양은 같지만 서로 다른 기능을 하고있는 것을 디바이스 기반의 다형성이라고 할 수 있습니다. 이것은 프로그래밍 언어의 각 요소들(상수, 변수, 객체, 메소드 등)이 다양한 자료형에 속하는 것을 허가하는 성질이라도 할 수 있습니다. 위의 키보드 예시에서는 각각의 역할이 있지만, 결국은 다 키보드라..

    [Swift] IUO(옵셔널 암시적 추출)

    [Swift] IUO(옵셔널 암시적 추출)

    IUO(Implicitly Unwrapped Optional이란?) 옵셔널 묵시적(암시적) 추출 강제 추출(!)이나, 옵셔널 바인딩을 활용하는 것과 같이 별도의 추출 과정이 없이도 자동으로 옵셔널이 해제되는 것을 뜻한다. 🌟 IUO도 Optional type을 선언하는 방법 중 하나이다! Optional type을 non-optional type에 대입할 때 따로 추출하는 과정이 없이 바로 할당이 가능하다! 즉, When we define an Implicitly unwrapped optional, we define a container that will automatically perform a force unwrap each time we read it. IUO를 정의하는 순간, 우리는 해당 값을 읽..

    [Swift] self는 언제 쓸까?

    [Swift] self는 언제 쓸까?

    HTML 삽입 미리보기할 수 없는 소스 self를 언제 쓸까? self ⇒ 클래스나 구조체의 인스턴스 자기 자신을 의미한다. 인스턴스 변수인지 지역변수인지를 명확하기 위해서 self를 활용한다. Objective-c 개발자들은 무의식적으로 self를 쓰는 경향이 있다! Compiler가 self를 강제할 경우 Initializer에서 모호함을 피하기 위하여 @escaping closure에서 무의식적으로 생기는 강한 참조 문제를 피하기 위하여 강제하는 경우 (1) - Initializer에서의 모호함 피하기 아래와 같이 파라미터로 받는 변수와 인스턴스 변수는 naming이 동일하다. 이럴 경우에 둘 간의 모호함을 피하기위하여 self 를 붙히게된다. struct People { let name: Stri..

    [Swift] initializers(생성자)

    [Swift] initializers(생성자)

    HTML 삽입 미리보기할 수 없는 소스 Initializers(초기화) 소들님의 블로그를 참고하여 작성한 글입니다. https://babbab2.tistory.com/167 ‼️제일 중요‼️ 생성자 메서드가 종료되기 전까지, 생성자 안에 모든 프로퍼티는 초기값을 지니고 있어야 한다 구조체의 초기화 1. 프로퍼티에 기본 값 넣어주기 선언과 동시에 기본 값을 넣어주어 초기화하기 struct Human { let name: String = "Miro" let age: Int = 28 } 2. 프로퍼티 타입을 Optional로 하기 초기화를 할 때 Optional 타입의 프로퍼티는 nil로 초기화가 된다. struct Human { let name: String? let age: Int? } 3. init 함수..

    [Swift] Closure

    [Swift] Closure

    소들이님 클로져 글 + 개인적으로 공부한 내용 정리한 내용입니다 :) 참고 - https://babbab2.tistory.com/81 클로져(Closure) Named Closure ⇒ 우리가 알고있는 함수 그 자체 Unnamed Closure ⇒ 우리가 알고있는 클로져! 클로져 표현식 → Parameter Name도 존재하고, Argument label은 존재하지 않는다. {(파라미터) -> 리턴타입 in 코드 블럭 } // 실제 예 let 클로져 = { (age: Int) -> String in return "내 나이, \(age)" } 클로져는 1급 객체이다 변수나 상수에 대입이 가능하다. 함수의 파라미터 값으로 클로져를 전달할 수 있다. func closureExample(completionHan..

    [Swift] Enumeration(열거형)

    [Swift] Enumeration(열거형)

    Enumeration(열거형) 원시 값이 있는 열거형 아래의 경우는 원시 값을 지정해주지 않고, 그냥 Int만 채택해주어도 알아서 원시값이 설정된다. enum SoccerPlayer: Int { case attacker = 0 case defenser = 1 case midfielder = 2 } 연관 값이 있는 열거형 아래와 같이 default로 존재하는 연관 값을 가진 case, default가 없는 연관 값 case도 존재한다. enum TypeName { // default 값을 넣을 수 있다. case caseName(type: String = "NameString") case caseNameTwo(typeNumber: Int) } 아래와 같이 switch문으로 연관 값을 처리할 수 있다. le..

    [Swift] mutating

    [Swift] mutating

    mutating 키워드 struct안에 있는 프로퍼티 값을 변경시킨다고 무조건 mutating 붙히는 게 아니다! 아래와 같은 경우에만 mutating 키워드를 붙힌다. 변경되는 값이 값 타입일 경우에만 mutating을 붙힌다. 만약 변경되는 값이 참조타입이지만 주소값이 변경이된다면 mutating 을 붙힌다.(아래의 예들을 통해서 이해를 해보자) 아래 예의 경우, 변경되는 값은 참조타입인 Array 속 프로퍼티가 변하는 것이기에 mutating을 붙히지 않아도된다.(elements가 변해도 array 프로퍼티가 참조하고 있는 Array() 객체의 주소 값이 변하는 것은 아니기에!) // 가능하다. class Array { var elements = [1,2,3,4,5] } struct TestStru..

    [Swift] 값 타입, 참조 타입, let, var

    [Swift] 값 타입, 참조 타입, let, var

    let VS var struct 구조체의 경우, 그 안의 프로퍼티 값을 변경하려면 무조건 var로 인스턴스를 생성해야된다. 단, 프로퍼티는 Struct 안에서 var로 선언되어있어야된다. struct Struct { var string = "happy" } let structInstance = Struct() structInstance.string = "sad" // error class 클래스의 경우, 그 안의 프로퍼티 값을 변경할 때, var나 let이나 상관없이 인스턴스를 생성해주면된다. 단, 프로퍼티는 클래스 안에서 var로 선언되어있어야된다. class Class { var string = "happy" } let classInstance = Class() classInstance.string ..

    [Swift] 함수 return에대한 고민

    [Swift] 함수 return에대한 고민

    함수의 return에 대한 고민 함수의 바디에서 return을 만나면 그 이후는 진행하지도 않고 끝난다! // Code after 'return' will never be executed! // 항상 true를 return한다! func returnBool(x: Int) -> Bool { return true if x > 5 { return true } return false } 아래와 같은 예에서도 만약 x가 5보다 클 경우, if문을 만족하므로 return true를 하고 함수를 끝낸다! func returnBool(x: Int) -> Bool { if x > 5 { return true } return false } print(returnBool(x: 6)) // true for 문 속 guard문..

    [Swift] Error Handling

    [Swift] Error Handling

    Error Handling 오류 정의 정의/ throw / try do - catch까지 사용한 예‼️⁉️ throws를 하는 함수를 사용하려면 무조건 앞에 try를 붙혀주어야된다. // enum형으로 error타입명 정의 enum DataError : Error { // Error 프로토콜을 구현한 것은 오류 타입으로 사용하라는 일종의 가독성 표시 case overSizeYear case incorrectData(part: Int) } // 오류가 나는 조건을 throws와 함께 배치 -> throw 문 추가 func getNextYearAndThrows(paramYear: Int) throws -> Int { guard paramYear = 0 else { throw DataError.incorrec..

    [Swift] 프로퍼티 옵저버

    [Swift] 프로퍼티 옵저버

    프로퍼티 옵저버(Property Observer) 프로퍼티 값의 변화를 관찰하는 것! 누군가 프로퍼티에 값을 설정할 때 작동한다. 저장 프로퍼티에 추가할 수 있다. 두 가지의 옵션이 존재한다. willSet → 값이 저장되기 전에 호출이된다. didSet → 새 값이 저장된 직후에 호출이된다. willSet 값이 지정되기 직전에 새로 저장될 값이 파라미터로 전달이된다. 아래와 같이 구현이 가능하다. var name: String = "Unknown" { willSet(newName) { print("현재 이름 = \(name), 바뀔 이름 = \(newName)") } } 또한, 파라미터 이름을 생략하여 아래와 같이도 설정할 수 있다. var name: String = "Unknown" { willSet..

    [Swift] 상속(Inheritance)

    [Swift] 상속(Inheritance)

    상속(Inheriatance) → 클래스만 상속이 가능하며 단일 상속만 가능하다! 서브 클래싱(subclassing) → 기본 클래스를 기반으로 새로운 클래스를 만드는 작업이다. 상속 받는 클래스 → 서브 클래스 상속 해주는 클래스 → 슈퍼 클래스 예를 통하여 이해해보자. 아래와 같이 Human 클래스가 존재한다. class Human: Hashable { var name: String? var age: Int? } 그리고, Human 클래스를 상속 받는 Teacher 클래스가 존재한다. Teacher 클래스는 Human 클래스의 멤버들을 모두 가지면서, 추가적으로 더 필요한 멤버들을 가진 클래스이다. class Teacher: **Human** { var subject: String? } “Teache..

    [Swift] Lazy (지연 저장 프로퍼티)

    [Swift] Lazy (지연 저장 프로퍼티)

    Lazy (지연 저장 프로퍼티) 처음 사용되기 전까지 초기값이 계산되지 않는 프로퍼티 항상 변수로 선언되어야한다. 처음에는 값이 없다가 초기화가 되면 값이 생기게 되기에 struct와 class에서만 사용이 가능합니다. computed property와는 사용을 할 수가 없다. lazy는 호출된 처음에 메모리에 값을 올리고 계속해서 그 값을 쓴다. 반면 computed property는 호출될 때마다 지속적으로 연산을 진행해야되므로. closure내에서의 self를 통해서 접근이 가능하다. closure내에서의 self를 통해서 접근이 가능하다. 아래와 같은 코드를 통하여 위의 말을 이해해보자.(연산 프로퍼티 아니다!) class Person { var name:String lazy var greetin..

    [Swift] Optional Binding(nil-coalescing)

    [Swift] Optional Binding(nil-coalescing)

    Nil-Coalescing Operator → Optional Type 표현식에 값이 저장되어 있는 지 확인하고 꺼낼 필요가 없어진다. a ?? b 일 때, a는 옵셔널 값이고, 옵셔널에서 값을 추출하여 값이 있으면 a 옵셔널의 값을 반환하고, 값이 없으면 b 값을 반환한다. 예를 통해서 알아보자. 아래와 같이 옵셔널 스트링을 선언해보자. let name: String? = "Miro" 옵셔널 바인딩을 통해서 print를 해보자. if let name = name { print("Hello, \(name)") } else { print("hello, what's your name") } // Hello, Miro 만약, nil-coalescing을 사용한다면? 아래와 같이 사용이 가능하다. print("..

    [Swift] 고차함수(Map,Filter,Reduce), allSatisfy, forEach,enumerated()

    [Swift] 고차함수(Map,Filter,Reduce), allSatisfy, forEach,enumerated()

    Map 함수 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너를 생성한다. 예를 통해서 map 함수를 익혀보자. 아래와 같이 numbers array와 빈 doubledNumbers array를 만들어보자 let numbers: [Int] = [0, 1, 2, 3, 4] doubledNumbers = [Int]() 그리고, map함수를 통하여 numbers의 각 요소를 2배하여 새로운 배열을 반환해보자. doubledNumbers = numbers.map({ (num: Int) -> Int in return num * 2 } 위의 코드는 생략을 통하여 아래와 같이 표현할 수 있다. doubledNumbers = numbers.map { $0 * 2 } Filter 함수 filter 함수는 컨테이너 ..

    [Swift] guard VS if

    [Swift] guard VS if

    guard guard문 속 else문에서는 return(or break, continue, throw)으로 즉시 종료시킨다. gurad 조건 else{ //조건이 false면 실행된다. return } guard문을 사용하는 이유? → 가독성이 훨씬 좋아진다. if문일 경우 func solution() { if condition1 { if condition2 { if condition 3 { print("come in") } else { print("bye") } } else { print("bye") } } else { print("bye") } } // 세 조건이 모두 참이면 come in, 하나라도 거짓이면 bye를 출력하는 함수이다. guard문일 경우 func solution() { guard ..

    [Swift] , 와 &&의 차이점

    [Swift] , 와 &&의 차이점

    ',' VS '&&' 먼저 논리 연산자에대해서 간단하게 복습을 하고 가보자! Operator 정리 쉽게 말하자면 && => 둘 다 참일 때만 참을 던진다 || => 둘 중 하나만 참이여도 참을 던진다. 그러면 본격적으로 '컴마'와 '&&'의 차이점에 대해서 알아보자 둘이 비슷하게 사용이 가능하지만, 옵셔널 바인딩일 때에 둘의 사용을 조심해야된다.(기본적으로 둘 다 조건이 다 참일 때 참을 던진다.) 옵셔널 바인딩 + 추가적인 condition을 동시에 쓸 경우 → 무조건 comma로 이어줘야된다. Boolean expression 두개를 연달아 이어줄 경우 → Comma와 && 둘 다 사용해도 된다. &&의 경우 두개의 boolean expression을 하나의 boolean expression으로 연산..

    [Swift] - Access Control

    [Swift] - Access Control

    Access Control(접근제어)는 다른 소스 파일 및 모듈의 코드에서, 코드의 일부에 대한 접근을 제한합니다. 모듈 → 시스템(혹은 프로그램)이나 제품 등에서 개별적인 기능이나 역할을 가진 부품, 요소/ import 시킬 수 있는 요소(Framework라고도 할 수 있다 ex. UIkit) 소스 파일 → 모듈 내의 단일 swift 소스 파일(ex. viewcontroller.swift) Access levels 아래로 갈수록 접근하기가 어려워진다! open & public internal file-private private Open VS Public 예를 통해서 한번 Open 과 Public의 차이점을 알아보자. 서브 클래싱 관점. 먼저 내가 외부의 framework MiroFramework를 만..

    [Swift] 연산 프로퍼티

    [Swift] 연산 프로퍼티

    연산 프로퍼티(Computed Property) → 값을 ‘저장’한다기보다는 특정한 연산을 통해서 값을 리턴해준다! How? getter setter 예) getter와 setter를 사용하기위해서는 저장소(tempX)가 필요하다! class Point { var tempX : Int = 1 var x: Int { get { return tempX } set(newValue) { tempX = newValue * 2 } } } var p: Point = Point() p.x = 12 위 코드에서의 setter에서 newValue는 set(newValue) { tempX = newValue * 2 } 1) 아래와 같이 써도 되고 set(miro) { tempX = miro * 2 } 2) 아예 없애도 된다..

    [Swift] 타입 프로퍼티, 타입 메소드

    [Swift] 타입 프로퍼티, 타입 메소드

    타입 프로퍼티(Type Property) - 타입 자체에 연결할 수 있으며, 처음 엑세스 할때는 게으르게 초기화(lazy initialized)가 됩니다. - 항상 초기화가 되어있어야한다. - 언제 한번 누군가 한번 불러서 메모리에 올라가면(그전까지는 올라가지 않는다= lazy), 그 뒤로는 생성되지 않으며 언제 어디서든 이 타입 - 프로퍼티에 접근 할 수 있다. ⇒ 전역변수 - static, class이라는 키워드를 통하여 구현을 한다. - 두 가지의 타입 프로퍼티가 존재합니다. 저장 타입 프로퍼티 → var , let으로 선언 가능, 기본값을 주어야된다. 연산 타입 프로퍼티 → var로만 선언이 가능 static 키워드 예를 통해서 알아보자. 아래와 같이 static을 붙혀, 저장 타입 프러퍼티와 연..

    [Swift] first 문

    [Swift] first 문

    💥 배열 속 주어진 조건을 만족하는 요소 찾기 first문 배열 속 하나하나를 클로져 안에 parameter로 넣어주어 클로져 안에 있는 body에서 첫번째로 true를 던질 때 그 요소를 가져온다. return을 생략하는 방법 return을 생략하지 않는 방법 즉, closure 안에서 return true가 나올 때까지 계속 반복하고, 안나오면 nil을 내뱉는다. closure 안에서 false가 나오면 거기서 멈추고 다음 요소로 넘어간다! 대체로 아래와 같이 return을 생략하는 방식으로 많이 사용한다. let numbers = [3, 7, 4, -2, 9, -6, 10, 1] let 넘버 = numbers.first { num in num < 0 } print(넘버) // Optional(-2)..