layoutSubviews

뷰의 값을 호출한 즉시 변경시켜주는 메소드.
호출되면 해당 뷰의 모든 하위 뷰들의 layoutSubviews 또한 연달아 호출됨.
이러한 이유로 비용이 많이 드는 메소드이며, 직접 호출하는 것이 지양됨.

자동으로 Update Cycle에 layoutSubviews를 호출하도록 예약하는 이벤트

  • 뷰의 크기 조절
  • 하위뷰 추가
  • 스크롤뷰를 스크롤
  • 디바이스 회전
  • 뷰의 오토레이아웃 제약조건 변경

layoutSubview를 호출하도록 예약하는 메소드 두 가지

  • setNeedsLayout : 비동기적으로 동작하여 바로 반환됨. Update Cycle에 layoutSubviews 호출.
  • layoutIfNeeded : 동기적으로 동작. Update Cycle을 기다리지않고 바로 layoutSubviews 호출.

만일 메인 런 루프에서 하나의 뷰가 (1) setNeedsLayout을 호출한 후 (2) layoutIfNeeded를 호출한다면?
(2) 메소드는 그 즉시 실행되어 뷰의 값이 재계산되고 화면에 바로 반영되기 때문에,
(1)이 예약한 layoutSubviews 메소드는 Update Cycle에 반영해야할 변경된 값이 존재하지 않게됨 = 즉 호출되지 않음

이러한 이유로 layoutIfNeeded는 그 즉시 값이 변경되어야하는 애니메이션에 많이 사용됨.
만일 setNeedsLayout을 사용한다면 애니메이션 블록에서 그 즉시 뷰의 값이 변경되는 것이 아니라 추후 Update Cycle에 값이 반영되므로 값의 변경은 이루어지지만 애니메이션 효과는 볼 수 없다.

setNeedsLayout vs layoutIfNeeded | 이동건의 이유있는 코드
View Programming Guide for iOS | Apple Developer

 

ClosedRange 구조체

@frozen struct ClosedRange<Bound> where Bound : Comparable

ClosedRange 구조체의 타입 매개변수 Bound는 Comparable 프로토콜을 준수하는 타입이어야 한다는 타입 제약이 명시되어 있다.
(+ Comparable 프로토콜을 준수하면 <, <=, >=, > 연산자로 비교가 가능함)

static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self>
let throughFive = 0...5 // [0, 5]

ClosedRange 인스턴스는 closed range operator(닫힌 범위 연산자) ... 로 만들 수 있다.
전제조건 minimum <= maximum 를 만족해야 한다.

throughFive.contains(0) // true
throughFive.contains(3) // true
throughFive.contains(5) // true
throughFive.contains(10) // false

upper bound(상한)과 lower bound(하한)을 모두 포함한다.

let zeroInclusive = 0...0
zeroInclusive.contains(0) // true
zeroInclusive.isEmpty // false

상한을 포함하므로, 하한과 상한이 동일한 ClosedRange 인스턴스는 해당 값을 포함하게 된다.
따라서 Closed Range 인스턴스는 빈 범위를 나타낼 수 없다.

Range 구조체

@frozen struct Range<Bound> where Bound : Comparable
static func ..< (minimum: Self, maximum: Self) -> Range<Self>
let underFive = 0.0..<5.0 // [0.0, 5.0)

Range 인스턴스는 half-open range operator(반-개방 범위 연산자) ..< 로 만들 수 있다.

underFive.contains(3.14) // true
underFive.contains(6.28) // false
underFive.contains(0.0) // true
underFive.contains(5.0) // false

위와 같이 어떠한 값이 해당 범위에 포함하는지 확인할 수 있다.

// Range
let empty = 0.0..<0.0
empty.contains(0.0) // false
empty.isEmpty // true
// ClosedRange
let zeroInclusive = 0...0
zeroInclusive.contains(0) // true
zeroInclusive.isEmpty // false

또한 Closed Range와 다르게, 빈 범위를 나타낼 수 있다.

참조

xcrun: error: unable to find utility "xcodebuild", not a developer tool or in PATH

[Xcode] - [Preferences] - [Locations] - [Command Line Tools] 설정해주면 해결 😃

제네릭(Generic)을 이용해 코드를 구현하면 어떤 타입에도 유연하게 대응할 수 있습니다.
제네릭으로 구현한 기능과 타입은 재사용하기도 쉽고, 코드의 중복을 줄일 수 있기에 깔끔하고 추상적인 표현이 가능합니다.

// 제네릭을 사용하지 않은 예
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA: Int = a
a = b
b = temporaryA
}
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA: String = a
a = b
b = temporaryA
}
var numberOne: Int = 5
var numberTwo: Int = 10
swapTwoInts(&numberOne, &numberTwo)
var stringOne: String = "A"
var stringTwo: String = "B"
swapTwoStrings(&stringOne, &stringTwo)

Double 타입의 값 교환을 원한다면 또 다른 함수를 구현해야 합니다. 그리고 타입마다 다른 함수를 써줘야 하는 불편함도 있습니다.

22.1 제네릭 함수

제네릭 함수를 사용하면, 같은 타입인 두 변수의 값을 교환한다는 목적을 타입에 상관없이 할 수 있도록 단 하나의 함수로 구현할 수 있습니다.

// 제네릭을 사용한 예
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA: T = a
a = b
b = temporaryA
}
swapTwoValues(&numberOne, &numberTwo)
swapTwoValues(&stringOne, &stringTwo)
swapTwoValues(&numberOne, &stringOne) // ERROR! 같은 타입끼리만 교환 가능

제네릭 함수는 실제 타입 이름을 써주는 대신에 플레이스홀더(Placeholder, 위 예제 함수에서는 T)를 사용합니다.
플레이스홀더 타입 T의 실제 타입은 함수가 호출되는 그 순간 결정됩니다.

또한 플레이스홀더 타입 T는 함수의 매개변수의 타입으로 사용할 수 있으며,
함수의 반환 타입으로 사용할 수도 있으며,
함수 내부 변수의 타입 지정을 위해 사용할 수도 있습니다.

타입 매개변수의 이름은 타입 이름이기도 하므로 대문자 카멜케이스를 사용하여 표현합니다.
ex) T, U, V, Key, Value, Element

22.2 제네릭 타입

제네릭 함수에 이어 제네릭 타입을 구현할 수도 있습니다. 제네릭 타입을 구현하면 사용자 정의 타입인 구조체, 클래스, 열거형 등이 어떤 타입과도 연관되어 동작할 수 있습니다.

모든 타입을 대상으로 동작할 수 있는 스택 기능을 구현해보겠습니다.

// 제네릭을 사용한 Stack 구조체 타입
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
// Element의 타입으로 Double을 사용
var doubleStack: Stack<Double> = Stack<Double>()
doubleStack.push(1.0)
print(doubleStack.items) // [1.0]
doubleStack.push(2.0)
print(doubleStack.items) // [1.0, 2.0]
doubleStack.pop()
print(doubleStack.items) // [1.0]
// Element의 타입으로 String을 사용
var stringStack: Stack<String> = Stack<String>()
stringStack.push("1")
print(stringStack.items) // ["1"]
stringStack.push("2")
print(stringStack.items) // ["1", "2"]
stringStack.pop()
print(stringStack.items) // ["1"]

만약 요소로 모든 타입을 수용할 수 있도록 구현하려고 한다면

  1. Stackitem 배열을 Any 타입으로 정의하는 방법
  2. 제네릭을 사용하여 Element의 타입으로 Any를 사용하는 방법
// 1. Stack의 item 배열을 Any 타입으로 정의하는 방법
struct AnyStack {
var items = [Any]()
mutating func push(_ item: Any) {
items.append(item)
}
mutating func pop() -> Any {
return items.removeLast()
}
}
var anyStack: AnyStack = AnyStack()
anyStack.push(1.0)
print(anyStack.items) // [1.0]
anyStack.push("2")
print(anyStack.items) // [1.0, "2"]
anyStack.push(3)
print(anyStack.items) // [1.0, "2", 3]
anyStack.pop()
print(anyStack.items) // [1.0, "2"]
// 2. 제네릭을 사용하여 Element의 타입으로 Any를 사용하는 방법
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var anyStack: Stack<Any> = Stack<Any>()
anyStack.push(1.0)
print(anyStack.items) // [1.0]
anyStack.push("2")
print(anyStack.items) // [1.0, "2"]
anyStack.push(3)
print(anyStack.items) // [1.0, "2", 3]
anyStack.pop()
print(anyStack.items) // [1.0, "2"]

두 가지 방법 중, 제네릭을 사용했을 때 훨씬 유연하고 광범위하게 사용할 수 있으며, Element의 타입을 정해주면 그 타입에만 동작하도록 제한할 수 있어 더욱 안전하고 의도한 대로 기능을 사용하도록 유도할 수 있습니다.

22.3 제네릭 타입 확장

만약 익스텐션을 통해 제네릭을 사용하는 타입에 기능을 추가하고자 한다면, 익스텐션 정의 타입 매개변수를 명시하지 않아야 합니다.
대신 기존의 제네릭 정의에 명시한 타입 매개변수를 익스텐션에서 사용할 수 있습니다.

extension Stack<Element> { // error
extension Stack {
var topElement: Element? { // 기존의 제네릭 정의에 명시된 Element
return self.items.last
}
}

22.4 타입 제약

타입 제약(Type Constraints)은 타입 매개변수가 가져야 할 제약사항을 지정할 수 있는 방법입니다.
타입 매개변수 자리에 사용할 실제 타입이 특정 클래스를 상속받은 타입이어야 한다든지, 특정 프로토콜을 준수하는 타입이어야 한다는 등의 제약을 줄 수 있습니다.
타입 제약은 클래스 타입 또는 프로토콜로만 줄 수 있습니다. (열거형, 구조체 타입 X)

// T는 BinaryInteger 프로토콜을 준수하는 타입
func swapTwoValues<T: BinaryInteger>(_ a: inout T, _ b: inout T) {
// ...
}

여러 제약을 추가하고 싶다면 where 절을 활용합니다.

// T는 BinaryInteger 프로토콜과 FloatingPoint 프로토콜을 준수하는 타입
func swapTwoValues<T: BinaryInteger>(_ a: inout T, _ b: inout T) where T: FloatingPoint {
// ...
}

Tip 타입 제약에 자주 사용할 만한 프로토콜에는Hashable , Equatable, Comparable, Indexable, IteratorProtocol, Error, Collection, CustomStringConvertible 등이 있습니다.

22.5 프로토콜의 연관 타입

연관 타입(Associated Type)은 프로토콜에서 사용할 수 있는 플레이스홀더 이름입니다.
어떤 타입이 들어올지 모를 때, 연관 타입을 통해 '종류는 알 수 없지만, 어떤 타입이 여기에 쓰일 것이다'라고 표현해주는 역할을 프로토콜에서 수행할 수 있도록 만들어진 기능입니다.

protocol Container {
associatedtype ItemType // 연관 타입
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
struct DoubleStack: Container {
var items = [Double]()
mutating func push(_ item: Double) {
items.append(item)
}
mutating func pop() -> Double {
return items.removeLast()
}
// Container 프로토콜 준수를 위한 구현
mutating func append(_ item: Double) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Double {
return items[i]
}
}

만약 ItemType을 어떤 타입으로 사용할지 조금 더 명확히 해주고 싶다면,
구현부에 typealias ItemType = Double 라고 타입 별칭을 지정해줄 수 있습니다.

struct DoubleStack: Container {
typealias ItemType = Double
var items = [ItemType]()
mutating func push(_ item: ItemType) {
items.append(item)
}
mutating func pop() -> ItemType {
return items.removeLast()
}
// Container 프로토콜 준수를 위한 구현
mutating func append(_ item: ItemType) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> ItemType {
return items[i]
}
}

책에서는 count 타입과 서브스크립트 매개변수 i 의 타입 또한 ItemType 으로 명시되어있는데, 아이템의 개수와 인덱스 값을 의미하므로 Int 로 명시해주는 것이 더 좋을 것같아 예제를 조금 수정했습니다.

22.6 제네릭 서브스크립트

제네릭 메서드를 구현할 수 있었던 것처럼 서브스크립트도 제네릭을 활용할 수 있습니다.

extension Stack {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Iterator.Element == Int {
var result = [ItemType]()
for index in indices {
result.append(self[index])
}
return result
}
}
var integerStack: Stack<Int> = Stack<Int>()
integerStack.append(1)
integerStack.append(2)
integerStack.append(3)
integerStack.append(4)
integerStack.append(5)
print(integerStack[0...2]) // [1, 2, 3]

야곰, 스위프트 프로그래밍

 

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

17. 서브스크립트  (0) 2021.01.30

클래스, 구조체, 열거형에는 컬렉션, 리스트, 시퀀스 등 타입의 요소에 접근하는 단축 문법인 서브스크립트(Subscript)를 정의할 수 있다.

// 함수 사용
someArray.element(at: index)
someDictionary.value(forKey: key)
// 서브스크립트 사용
someArray[index]
someDictionary[key]

17.1 서브스크립트 문법

서브스크립트 정의 문법

subscript(index: Int) -> Int {
get {
// 적절한 서브스크립트 결괏값 반환
}
set(newValue) { // 매개변수를 따로 명시해주지 않으면 설정자의 암시적 전달인지 newValue 사용 가능
// 적절한 설정자 역할 수행
}
}

읽기 전용 서브스크립트 정의 문법

subscript(index: Int) -> Int {
get {
// 적절한 서브스크립트 결괏값 반환
}
}
subscript(index: Int) -> Int {
// 적절한 서브스크립트 결괏값 반환
}

17.2 서브스크립트 구현

서브스크립트는 자신이 가지는 컬렉션, 리스트, 시퀀스 등의 요소를 반환하고 설정할 때 주로 사용합니다.

함수와 마찬가지로 서브스크립트는 여러 개의 매개변수를 가질 수 있고, 매개변수 기본값을 가질 수 있습니다.

그렇지만 입출력 매개변수(in-out parameters)[각주:1]는 가질 수 없습니다.

struct Student {
var name: String
var number: Int
}
class School {
var number: Int = 0
var students: [Student] = [Student]()
func addStudent(name: String) {
let student: Student = Student(name: name, number: self.number)
self.students.append(student)
self.number += 1
}
func addStudents(names: String...) {
for name in names {
self.addStudent(name: name)
}
}
subscript(index: Int = 0) -> Student? {
if index < self.number {
return self.students[index]
}
return nil
}
}
let highSchool: School = School()
highSchool.addStudents(names: "MiJeong","JuHyun", "JiYoung", "SeongUk", "MoonDuk")
print(highSchool[1]?.name) // Optional("JuHyun")
print(highSchool[]?.name) // Optional("MiJeong")

17.3 복수 서브스크립트

클래스와 구조체는 필요한 만큼 얼마든지 서브스크립트를 구현할 수 있습니다.

서브스크립트를 여러 개 구현해도 외부에서 서브스크립트를 사용할 때 전달한 값의 타입을 유추하여 적절한 서브스크립트를 선택하여 실행합니다.

이렇게 여러 서브스크립트를 한 타입에 구현하는 것을 서브스크립트 중복 정의(Subscript Overloading)라고 합니다.

struct Student {
var name: String
var number: Int
}
class School {
var number: Int = 0
var students: [Student] = [Student]()
func addStudent(name: String) {
let student: Student = Student(name: name, number: self.number)
self.students.append(student)
self.number += 1
}
func addStudents(names: String...) {
for name in names {
self.addStudent(name: name)
}
}
// 1.
subscript(index: Int) -> Student? {
get {
if index < self.number {
return self.students[index]
}
return nil
}
set {
guard var newStudent: Student = newValue else {
return
}
var number: Int = index
if index > self.number {
number = self.number
self.number += 1
}
newStudent.number = number
self.students[number] = newStudent
}
}
// 2.
subscript(name: String) -> Int? {
get {
return self.students.filter{ $0.name == name }.first?.number
}
set {
guard var number: Int = newValue else {
return
}
if number > self.number {
number = self.number
self.number += 1
}
let newStudent: Student = Student(name: name, number: number)
self.students[number] = newStudent
}
}
// 3.
subscript(name: String, number: Int) -> Student? {
return self.students.filter{ $0.name == name && $0.number == number }.first
}
}
let highSchool: School = School()
highSchool.addStudents(names: "MiJeong","JuHyun", "JiYoung", "SeongUk", "MoonDuk")
// 1.
// subscript(index: Int) -> Student?
print(highSchool[1]) // Optional(Student(name: "JuHyun", number: 1))
// 2.
// subscript(name: String) -> Int?
print(highSchool["MiJeong"]) // Optional(0)
print(highSchool["DongJin"]) // nil
highSchool[0] = Student(name: "HongEui", number: 0)
highSchool["MangGu"] = 1
print(highSchool["JuHyun"]) // nil
print(highSchool["MangGu"]) // Optional(1)
// 3.
// subscript(name: String, number: Int) -> Student?
print(highSchool["SeongUk", 3]) // Optional(Student(name: "SeongUk", number: 3))
print(highSchool["HeeJin", 3]) // nil

이처럼 서브스크립트는 메서드인듯 아닌듯, 연산 프로퍼티인 듯 아닌 듯 중간 형태를 띄며 인스턴스 이름 뒤에 대괄호만 써서 편리하게 내부 값에 접근하고 설정해줄 수 있습니다.

17.4 타입 서브스크립트

타입 서브스크립트는 인스턴스가 아니라 타입 자체에서 사용할 수 있는 서브스크립트입니다.

타입 서브스크립트를 구현하려면 정의할 때 static 키워드를 붙여주면 됩니다. 클래스의 경우에는 class 키워드를 사용할 수도 있습니다.[각주:2]

enum School: Int {
case elementary = 1, middle, high, university
static subscript(level: Int) -> School? {
return Self(rawValue: level)
// return School(rawValue: level)와 동일
}
}
let school: School? = School[2]
print(school) // School.middle

야곰, 스위프트 프로그래밍

  1. 값이 아닌 참조를 전달하려면 입출력 매개변수를 사용 (C언어의 포인터와 유사함) [본문으로]
  2. 클래스에서 static 키워드와 class 키워드의 차이:
    static 으로 정의하면 상속 후 메서드 재정의가 불가능
    class 으로 정의하면 상속 후 메서드 재정의가 가능 [본문으로]

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

22. 제네릭  (0) 2021.01.30

+ Recent posts