CollectionView는 TableView와 다르게 layout을 설정해주지 않으면 생성시 오류가 발생합니다.

CollectionView 생성자에서 인자로 받는 UICollectionViewLayout이 있습니다.

왠만한 레이아웃은 UICollectionViewLayout으로 할 수 있지 않을까? 싶었는데 CollectionView 생성 인자로 모두 들어갈 수 있다는 점에서 많이 헷갈렸습니다.

이번 기회에 정리해보고자 합니다.

 

 

UICollectionViewLayout

https://developer.apple.com/documentation/uikit/uicollectionviewlayout

 

UICollectionViewLayout | Apple Developer Documentation

An abstract base class for generating layout information for a collection view.

developer.apple.com

 

- 컬렉션 뷰의 레이아웃을 설정하기 위한 클래스 입니다.

- 셀의 위치, 추가적인 뷰와 컬렉션 뷰 안의 데코레이션 뷰를 결정하고 컬렉션뷰에 전달합니다.

- UICollectionViewLayout을 사용하기위해 서브클래싱을 진행해야합니다.

 

 

Subclassing Notes

레이아웃 객체는 컬렉션뷰 아이템의 위치, 크기, 시각적 상태등을 디자인에 맞춰서 정의합니다.

레이아웃의 뷰는 컬렉션 뷰의 datasource에 의해 생성됩니다.

 

- Cell 은 레이아웃에 의해 배치되는 주요 요소입니다. 각 셀은 컬렉션 안의 단일 데이터 아이템으로 나타냅니다. 사용자가 각 항목을 선택, 끌기 재정렬 할 수 있도록 interactive한 셀을 만들 수 있습니다. 컬렉션 뷰는 단일 셀 그룹을 가질 수 있고 해당 셀을 여러 섹션으로 나눌 수 있습니다. 레이아웃 객체는 컬렉션뷰의 컨텐츠영역의 셀을 정렬할 수 있습니다.

- Supplementary view 데이터를 표시하지만 사용자가 선택할 수 없습니다. Header View와 Footer View를 구성하기 위해 사용됩니다. Supplementary view는 선택적으로 사용할 수 있고, 레이아웃 객체에서 정의합니다.

- Decoration view 선택할 수 없고, 컬렉션 뷰와 데이터로 연동되지 않은 뱃지와 같은 장식품입니다. Decoration viewSupplementary view 와는 다른 유형입니다. Supplementary view 와 마찬가지로 선택적으로 사용할 수 있고, 사용과 배치는 레이아웃 객체에서 정의합니다.

 

Collection View 는 위와 같은 레이아웃 정보를 제공하는 레이아웃 객체를 호출합니다. 스크린에 표시되는 모든 셀과 뷰는 레이아웃 객체를 사용하여 배치됩니다. 마찬가지로 항목이 추가되거나 삭제될 때 해당 항목에 대한 추가적인 레이아웃 단계가 발생합니다. 그러나 컬렉션 뷰는 레이아웃을 항상 스크린에 표시되는 객체로 제한합니다.

 

Methods

 

'iOS' 카테고리의 다른 글

UIBeizerPath  (0) 2025.02.26
NotificationCenter  (0) 2025.02.20
iOS Combine  (0) 2025.02.19
URLSession 번역  (0) 2024.12.13
Fetching website data into memory  (0) 2024.12.10

UIBeizerPath

A path that consists of straight and curved line segments that you can render in your custom views.
공식문서에 따르면, 사용자의 커스텀 뷰에서 렌더링할 수 있는 직선 곡선 세그먼트로 구성된 경로라고 합니다.

해당 객체는 UIKit의 Drawing에 속해있는 클래스입니다.

한마디로 사용자가 원하는 모양을 그려낼 수 있도록 도와주는 객체라고 생각하시면 됩니다.

 

init

UIBeizerPath를 생성하는데 기본으로 주어지는 모양들이 존재합니다.

// 기본 생성
public init()
// 직사각형 모양 CGRect를 인자로 받는다. CGRect(x: ,y: , width: , height: ,) x y 는 그리기의 시작지점, width height는 크기
public convenience init(rect: CGRect)

// 타원형 모양 CGRect를 인자로 받는다. CGRect(x: ,y: , width: , height: ,) x y 는 그리기의 시작지점, width height는 크기
public convenience init(ovalIn rect: CGRect)

// 둥근 사각형 모양 만든다. CGRect(x: ,y: , width: , height: ,) x y 는 그리기의 시작지점, width height는 크기 , cornerRadius 는 꼭지점의 둥근정도를 CGFloat 값으로 전달
public convenience init(roundedRect rect: CGRect, cornerRadius: CGFloat)

// 부분적으로 둥근 사각형 모양을 만든다. CGRect(x: ,y: , width: , height: ,) x y 는 그리기의 시작지점, width height는 크기
// UIRectCorner은 어느 꼭지점을 둥글게 만들지 결정
// cornerRadii 는 둥근 정도를 CGSize 값으로 받는다.
public convenience init(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize)

// 원 모양으로 그린다.
// center: CGPoint(x:, y:) 원의 중간위치
// radius: 반지름
// startAngle: 그리기 시작 각도
// endAngle: 그리기 종료 각도
// clockwise: 시계방향으로 그릴지 Bool값으로 전달
public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)

// Core Graphics를 인자로 받아 경로 생성한다.
public convenience init(cgPath CGPath: CGPath)

 

이렇게 원, 직사각형, 타원형, 둥근 사각형 기타 등등 많은 표준된 모양을 지원해주지만, 사용자가 직접 그릴 수도 있습니다.

 

 

직접 그리기 

위와 같은 모양을 그리기 위해서 또는 선을 긋기 위해선 시작지점이 필요합니다.

// 생성
let path = UIBezierPath()

// 시작지점 지정
// CGPoint(x: ,y: )
path.move(to: CGPoint)

// 직선 생성
// CGPint(x: ,y: ) 시작지점에서 x,y 지점과 잇는 선 생성
path.addLine(to: CGPoint)

// 3차원 곡선 생성
path.addCurve(to: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint)

// 가장 최근에 그어진 선의 끝 지점과 시작지점과 잇기
path.close()

// 현재까지 추가된 선을 모두 그리기
path.stroke()

// 현재까지 그려진 선이 둘러싼 영역을 채우기
path.fill()

 

'iOS' 카테고리의 다른 글

UICollectionViewLayout과 UICollectionViewFlowLayout  (0) 2025.03.03
NotificationCenter  (0) 2025.02.20
iOS Combine  (0) 2025.02.19
URLSession 번역  (0) 2024.12.13
Fetching website data into memory  (0) 2024.12.10

NotificationCenter

직역하면 알림센터로, Observer에게 Notification 객체를 전달하는 싱글톤 객체입니다.

델리게이트 패턴은 일대일 송수신에 장점이 있지만, 이 객체는 일대다, 다대다 통신의 장점을 갖고 있습니다.

 

Notification

    /// A tag identifying the notification.
    public var name: Notification.Name

    /// An object that the poster wishes to send to observers.
    ///
    /// Typically this is the object that posted the notification.
    public var object: Any?

    /// Storage for values or objects related to this notification.
    public var userInfo: [AnyHashable : Any]?

Notification에는 name, object, userInfo가 담겨져 있습니다.

name: 전달하고자 하는 Notification 식별 이름

object: 전달하는 데이터(객체)

userInfo: Notification과 관련된 데이터

 

Post(발송하기)

NotificationCenter.default.post(name: NSNotification.Name, object: Any?, userInfo: [AnyHashable : Any]?)

이벤트 발생 지점에서 NotificationCenter의 post를 통해 Notification을 전송합니다.

전송할때 식별되는 이름은 Notification.Name 입니다.

 

 

전달받기(Observer 등록)

NotificationCenter.default.addObserver(forName: NSNotification.Name?, object: Any?, queue: OperationQueue?, using: (Notification) -> Void)

알림을 받을 지점에서 Observer를 등록합니다.

알림받고 using: 클로저를 통해 바로 실행시키거나 알림을 받으면 메서드가 실행되도록 설정할 수 있습니다.

NotificationCenter.default.addObserver(Any, selector: Selector, name: NSNotification.Name, object: Any?)

 

'iOS' 카테고리의 다른 글

UICollectionViewLayout과 UICollectionViewFlowLayout  (0) 2025.03.03
UIBeizerPath  (0) 2025.02.26
iOS Combine  (0) 2025.02.19
URLSession 번역  (0) 2024.12.13
Fetching website data into memory  (0) 2024.12.10

Combine

Combine은 비동기 및 이벤트 처리 코드를 작성하는데 사용되는 프레임워크 입니다.

이전에 반응형 프로그래밍으로 RxSwift를 사용해 본 적이 있는데, 애플에서 개발한 Combine도 있다고 하여 개념을 정리해보고자 합니다

 

 

Publisher: 값을 생성하고 전달하는 객체입니다.

public protocol Publisher {
    associatedtype Output
    associatedtype Failure: Error

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

 

Subscriber: Publisher로부터 데이터를 수신하고 처리하는 객체입니다.

public protocol Subscriber: CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure: Error

    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

Operators: Publisher로부터 전달받은 값을 변환하거나 조작하는 객체입니다.

 

Combine의 흐름은 Publisher -> Operator -> Subscriber 순으로 진행됩니다.

 

 

'iOS' 카테고리의 다른 글

UIBeizerPath  (0) 2025.02.26
NotificationCenter  (0) 2025.02.20
URLSession 번역  (0) 2024.12.13
Fetching website data into memory  (0) 2024.12.10
URL Loading System 번역  (2) 2024.12.10

비동기 프로그래밍은 개발에 있어서 중요한 부분입니다.

하나의 앱은 한개의 프로세스가 순차적으로 이루어지는 것이 아닌 다양한 작업이 동시다발적으로 이루어지기 때문에 이러한 작업을 효율적으로 나누고 한번에 처리하도록 설계해야합니다.

설계하는데 있어서 중요한 비동기 프로그래밍에 대해서 적어보고자 합니다.

 

동기 / 비동기

동기(Synchronous): 작업이 완료될 때까지 대기한 후, 작업이 완료되면 다음 작업을 실행

비동기(Asynchronous): 작업 완료를 기다리지 않고 바로 다음 작업을 실행

 

 

 

직렬(serial) / 병렬(concurrent)

직렬(Serial): 순차적으로 진행

병렬(Concurrent): 동시에 진행

 

이름에서 의미를 찾아 볼 수 있지만, 직렬 병렬을 나누는 이유는 작업이 들어가는 큐에는 직렬 수행, 병렬 수행을 지정할 수 있기 떄문입니다.

 

Main Queue(Serial): Main thread로 serial queue를 사용합니다.

Global Queue(Concurrent): 그 외 thread로 concurrent queue를 사용합니다.

Custom Queue(Serial & Concurrent): 직렬, 병렬 수행을 직접 지정한 queue를 사용합니다.

 

 

DispatchQueue

iOS에서 비동기 프로그래밍을 설계할 수 있도록 지원하는 API입니다.

별도로 설치없이 사용할 수 있고 구문은 다음과 같습니다.

 

// Main Thread에서 비동기적 실행
DispatchQueue.main.async {
    
}
// Sub Thread에서 동기적 실행
DispatchQueue.global().sync {
    
}
// Sub Thread에서 비동기적 실행
DispatchQueue.global().async {
    
}

'Swift' 카테고리의 다른 글

순환 참조  (0) 2025.02.17
Swift 자료구조  (0) 2025.02.13

class 와 같은 인스턴스는 참조 형식 인스턴스 입니다.

두 개의 참조 형식 인스턴스가 서로 강한 참조(Strong reference)를 한다면 순환 참조가 발생합니다.

이는 두 인스턴스가 메모리에서 해제되어도 서로에 대한 참조가 해제되지 않기 때문에 메모리에 남게되고 심해지면 메모리 릭 현상이 발생합니다.

이러한 순환참조를 해결하기 위해 참조할 때 weak, unowned 키워드를 사용합니다.

 

먼저 순환참조가 발생하는 예시입니다.

// Person Class
class Person {
    var name: String
    var pet: Pet?
    
    init(name: String) {
        self.name = name
        print("Person init")
    }
    deinit {
        print("Person deinit")
    }
}
// Pet Class
class Pet {
    var owner: Person?
    
    init() {
        print("Pet init")
    }
    
    deinit {
        print("Pet deinit")
    }
}

// 두 개의 인스턴스 생성
var person: Person?
var pet: Pet?
person = Person(name: "cheolsu")
pet = Pet()

// 각 인스턴스의 속성을 다른 인스턴스가 강한 참조하도록 설정
person?.pet = pet
pet?.owner = person

// 메모리에서 해제한다.
person = nil
pet = nil

// 출력 결과
Person init
Pet init
-> Person 클래스와 Pet 클래스의 메모리 해제가 이루어지지 않음

person, pet 인스턴스를 모두 메모리에서 해제하여도 deinit 이 되지 않았습니다.

 

결국 서로의 참조 상태는 메모리에 남아있습니다.

 

약한 참조 weak

선언하는 프로퍼티 앞에 weak 키워드를 붙여 약한 참조하도록 명시적으로 설정하는 방법입니다.

class Pet {
    weak var owner: Person?
    
    init() {
        print("Pet init")
    }
    
    deinit {
        print("Pet deinit")
    }
}

weak 키워드가 붙여진 프로퍼티는 다음과 같은 특징을 갖고 있습니다.

- 참조하는 인스턴스의 RC(Reference Count)를 증가시키지 않습니다.

- 참조하는 인스턴스가 메모리에서 해제되면 nil이 할당되어 메모리에서 해제시킵니다.

- 런타임중 nil이 할당되는 경우가 존재할 수 있으므로 옵셔널 형식입니다.

 

 

unowned

두번째로는 unowned 키워드를 선언하는 프로퍼티 앞에 붙이는 방법이 있습니다.

class Pet {
    unowned var owner: Person
    
    init() {
        print("Pet init")
    }
    
    deinit {
        print("Pet deinit")
    }
}

unowned 키워드가 붙여진 프로퍼티는 다음과 같은 특징을 갖고 있습니다.

- 참조하는 인스턴스의 RC(Reference Count)를 증가시키지 않습니다.

- 참조하는 인스턴스가 메모리에서 해제되어도 여전히 Heap영역의 인스턴스를 가리키는 포인터가 됩니다.

따라서 unowned은 옵셔널 형식이 아니게 됩니다.

 

 

weak와 unowned의 차이점은 참조하는 인스턴스가 메모리에서 해제될 때 나타납니다.

weak은 nil이 되지만, unowned은 여전히 포인터로 남습니다.

따라서 참조되는 인스턴스가 메모리에 할당되는 기간이 참조하는 인스턴스보다 짧다면 weak을 사용하는 편이 좋고,

참조되는 인스턴스가 메모리에 할당되는 기간이 참조하는 인스턴스보다 같거나 길다면, 또는 필연적인 관계라면 unowned를 사용하는게 좋습니다.

 

 

'Swift' 카테고리의 다른 글

비동기 프로그래밍 DispatchQueue  (0) 2025.02.18
Swift 자료구조  (0) 2025.02.13

배열

- 데이터를 순차적으로 저장하는 구조

- 인덱스를 사용해 특정위치의 요소에 접근할 수 있음

- 특정 인덱스를 통해 접근하는 것은 빠르지만(O(1)), 삽입/삭제 과정은 오래걸린다(O(n))

 

예시

 

 

선입선출의 형식인 데이터구조 (FIFO)

- Enqueue: 큐 맨 뒤에 요소 추가

- Dequeue: 큐 맨 앞의 데이터를 반환

- Peek: Front에 위치한 데이터 값 반환

- Front: 큐의 앞부분 (가장 먼저 들어온 데이터가 위치함)

- Rear: 큐의 뒷부분 ( 가장 최근에 들어온 데이터가 위치함)

 

dequeue하는 과정에서 removeFirst() 메소드를 이용한다면 시간복잡도가 O(n)이 걸리게된다.

이를 해결하기위해 index를 이용해 front부분을 확인하여 데이터를 반환하는 방법도 있지만,

reversed()도 O(1)이 걸리므로 이 방법을 사용했다.

import Foundation

struct Queue<T> {
    var elements: [T] = []
    
    // 큐 갯수
    var count: Int {
        return elements.count
    }
    // 큐의 가장 먼저 나갈 데이터
    var peek: T? {
        if elements.isEmpty {
            return nil
        } else {
            return elements[0]
        }
    }
    // 큐의 앞부분
    var front: T? {
        if elements.isEmpty {
            return nil
        } else {
            return elements[0]
        }
    }
    // 큐의 뒷부분
    var rear: T? {
        if elements.isEmpty {
            return nil
        } else {
            return elements.last!
        }
    }
    // 큐 비어있는지
    var isEmpty: Bool {
        return elements.isEmpty
    }
    // 큐 데이터 삽입
    mutating func enqueue(element: T) {
        elements.append(element)
    }
    // 큐 데이터 삭제(반환)
    mutating func dequeue() -> T? {
        if elements.isEmpty {
            return nil
        } else {
            var arr = Array(elements.reversed())
            let returnValue = arr.popLast()
            elements = Array(arr.reversed())
            return returnValue
        }
    }
}

'Swift' 카테고리의 다른 글

비동기 프로그래밍 DispatchQueue  (0) 2025.02.18
순환 참조  (0) 2025.02.17

구조체와 클래스는 비슷하면서 서로 다른 성질을 갖고 있습니다.

먼저 공통점으로는 다음과 같습니다.

- 변수나 상수를 사용하여 값을 저장하는 프로퍼티로 정의할 수 있음

- 함수를 사용하여 기능을 제공하는 메서드로 정의할 수 있음

- 속성값에 접근할 수 있는 방법을 제공하는 서브스크립트를 정의할 수 있음

- 객체를 원하는 초기 상태로 설정해주는 초기화 블록(init)을 정의할 수 있음

- 객체에 함수적 기능을 추가하는 확장 구문을 사용할 수 있음

- 특정 형식의 함수적 표준을 제공하기 위한 프로토콜을 구현할 수 있음

 

이와 같은 공통점이 있지만, 구조체에서는 안되지만 클래스에서는 할 수 있는 기능이 다음과 같이 있습니다.

- 클래스의 특성을 다른 클래스에게 물려줄 수 없음

- 실행 시 컴파일러가 클래스 인스턴스의 타입을 미리 파악하고 검사할 수 있음

- 인스턴스가 소멸되기 직전에 처리해야 할 구문을 미리 등록해 놓을 수 있음 (deinit)

- 클래스 인스턴스가 전달될 때에는 참조 형식으로 제공되며, 참조가 가능한 개수는 제약이 없음

 

 또한 클래스는 참조형식, 구조체는 값형식 프로퍼티로 클래스는 값이 변경됨에 따라 참조된 클래스의 값에 영향을 주고 구조체는 영향을 주지 않는 성질을 갖고 있습니다.

// 구조체
struct User {
    var name: String
    var age: Int
}

var userStruct = User(name: "", age: 0)
var cheolsu = userStruct
var yuri = userStruct

print("cheolsu: \(cheolsu)")
print("yuri: \(yuri)")

yuri.name = "yuri"
yuri.age = 5

print("cheolsu: \(cheolsu)")
print("yuri: \(yuri)")

// 출력 결과
cheolsu: User(name: "", age: 0)
yuri: User(name: "", age: 0)
cheolsu: User(name: "", age: 0)
yuri: User(name: "yuri", age: 5)
// yuri의 값을 변경했지만 cheolsu 객체에 영향을 주지 않는다.

// 클래스
class User {
    var name: String = ""
    var age: Int = 0
}

var userClass = User()
var cheolsuClass = userClass
var yuriClass = userClass

yuriClass.name = "yuri"
yuriClass.age = 5

print(yuriClass.age)
print(yuriClass.name)
print(cheolsuClass.age)
print(cheolsuClass.name)

// 출력 결과
5
yuri
5
yuri
// yuri의 값을 변경했지만, cheolsu객체의 특성도 변경되었다.

 

 

 

'Swift > 문법' 카테고리의 다른 글

Swift Concurrency 정리  (7) 2024.11.10
Swift 프로토콜 번역(추가 번역중) (Swift.org)  (1) 2024.02.11

+ Recent posts