이는 UIKit에서 메소드를 지칭하거나, 속성의 게터나 세터를 지칭할 때 사용합니다. 여기서 지칭한다는 뜻은, 메소드를 호출하는게 아니라 메소드를 가리키는 특별한 인스턴스를 얻는다는 뜻입니다. 속성의 접근하여 값을 가져오거나 바꾸는게 아니라 게터나 세터 자체를 가리키는 특별한 인스턴스를 얻는다는 뜻입니다.
// 문법
#selector(methodName)
#selector(getter: propertyName)
#selector(setter: propertyName)
class Figure {
@objc let color: UIColor = .blue
// obj-c 메소드로 선언됩니다. 구조체에서는 사용이 불가하다.
@objc func draw() {
print("draw some")
}
}
// draw 메소드를 지칭하는 selector
let selector = #selector(Figure.draw)
let colorSelector = #selector(getter: Figure.color)
이 개념도 selector와 똑같이, Objective-C 에서 만들어진 개념(@objc 선언해야함, 구조체 x)입니다. a.b.c
와 하나 이상의 키가 점으로 연결되고, 이를 통해서 특정 속성에 접근합니다. (위의 예시는 최종적으로, c로 접근한다)
class Person: NSObject {
@objc let name: String = "Jane Doe"
@objc var age: Int = 0
}
let p = Person()
p.value(forKey: "Naem") // 오타가 있다면 에러가 발생할 우려
// 이를 해결하는것이 키패스이다.
// 문법, 타입체크를 하는 장점이 있다,
var keypath = #keyPath(Person.name)
p.value(forKey: keypath)
하지만 keypath의 리턴타입은 Any이므로 타입캐스팅이 필요하고 이 과정에서 충돌이 일어날 수도 있다. 이를 해결하는 것이 Swift에서 제공하는 Keypath 표현식이다
이는 컴파일 타임 체크, 제네릭 타입, 다양한 방식으로 조합이 가능하고 클래스, 구조체에서 지원이 가능하다.
// 문법, TypeName은 루트타입, 베이스 타입으로 부릅니다.
\TypeName.propertyName.propertyName
struct Person {
let name: String = "hi"
var age: Int = 0
}
var p = Person()
let keyPathToName = \Person.name // Keypath<Person, String>
let keyPathToAge = \Person.age // WritableKeypath<Person, Int>
let nameValue = p[keyPAth: keyPathToName]
p[keyPath: KeyPathToName] = "hello" // 읽기 전용이라 불가능
p[keyPath: keyPathToAge] = 1 // 가능
// 키패스의 확장, 두가지 방법
var keyPathToLength = \Person.name.count
// 이 방법에서 String이 타입 추론이 가능하므로 생략이 가능하다. \.count
keyPathToLength = keyPathToName.appending(path: \String.count)
API의 가용성을 확인하는 방법에 대해 알아보겠습니다. OS버전에 맞게 코드를 설정이 가능하게 해줍니다.
// 문법
if #available(OS Version, Os Version, *) {}
while #available(OS Version, *) {}
guard #available(OS Version, *) else {return }
// 최소버전이 11.0이라는 의미, 애스터리스크(*)는 생략이 불가하고, 나머지 다른 플랫폼을 의미함.
if #available(iOS 11.0, *)
값의 타입을 표현하는 타입을 의미하는 Metatype에 대해 알아보겠습니다.
func checkType(of value: Any) {
// typeOfValue는 다이나믹 타입입니다.
// 메타 타입은 기존 타입 뒤에 Type을 붙여서 나타낸다.
let typeOfValue: Any.Type = type(of: value)
print("\(value) -> \(typeOfValue)")
}
let namae = "werwwer"
checkType(of: namae)
위의 코드에서 type(of)
의 리턴 타입은 Metatype
입니다. 런타임에 확인되는 값의 실제 타입을 다이나믹 타입이라고 부릅니다. 스태틱 타입은 컴파일 타임에 사용하는 값의 타입입니다. 타입에 대해 정리하자면, 코드에서 선언한 타입은 스태틱 타입, 런타임에 실제로 전달되는 타입은 다이나믹 타입입니다.
실제로 사용되는 예시로, 어떤 함수의 파라미터가 메타 타입으로 선언되어 있다면 호출 시에 해당 타입의 인자 이름을 작성후 뒤에 .self
를 붙여줘야한다. 이때의 self는 메타타입 인스터스를 리턴하는 self 입니다.
// 이곳에 저장된 문자열과 동일한 스토리보드 아이디의 뷰컨트롤러가 있다면, 인스턴스를 생성하고 실제 타입으로 캐스팅해서 리턴하는 함수
// 호출시에 메타타입으로 인자를 선언해야함 abc.self
extension UIViewController {
func chk<VC: UIViewController>(ofType type: VC.Type) -> VC? {
// 메타타입은 String이 제공하는 생성자를 통해서 문자열로 바꿀 수 있다.
let vcName = String(describing: type)
return storyboard?.instantiateViewController(withIdentifier: vcName) as? VC
}
}
용어를 먼저 정리해보겠습니다.
실행 단위 - cpu core에서 실행하는 하나의 단위로 프로세스와 스레드를 포괄하는 개념
프로세스 - 하나의 스레드만 가지고 있는 단일 스레드 프로세스
동시성 - 한 순간에 여러가지 일이 아니라, 짧은 전환으로 여러가지 일을 동시에 처리하는 것처럼 보이는 것
프로그램이 실행 된 것을 프로세스라고 부릅니다.
스레드는 한 프로세스 내에서 나누어진 하나 이상의 실행 단위입니다.
스레드는 경량화된 프로세스 입니다. 프로세스의 자원을 공유합니다.
프로세스 내에서 분리하여 여러 쓰레드로 나뉘어서 실행단위가 나누어지면 멀티 스레드가 되는 거라고 말합니다.
멀티스레드의 단점은 하나의 자원에서 문제가 생기면 전체에 영향이 일어나는 경우가 있다
병렬처리 : 둘 이상의 코어에서 동시에 하나 이상의 프로세스(혹은 스레드)가 한꺼번에 진행되는 것
새로 추가된 비동기 메소드 키워드
// 문법
// async 키워드만 있어도 비동기 메소드가 된다. 리턴형이 있다면 뒤에 이어서 작성하면 된다.
func someThing() async throws {}
func syncFunction() {
// 동기 코드에서 비동기 메소드를 사용하려면 Task로 감싸줘야 한다
Task{
// 호출 시 try await 키워드를 사용해야함
try await someThing()
}
}
await (Suspension Point): 해당 메소드의 스레드를 다른 스레드에서 실행하게 하고, 현재 스레드에서 잠시 중단후 제어권을 OS에게 넘기게 해준다.
SwiftUI에 구조에 대해서 알아 보겠습니다.
속성으로 body
를 받습니다. 이는 Scene
프로토콜을 채용하며 뷰 계층을 담고있ㄴ는 컨테이너 입니다.
컴파일러가 자동으로 메인 함수를 합성합니다.
여기서 ObservableObject 프로토콜을 채용한 모델을, @StateObject로 선언하여 모든 View에서 사용이 가능하게 할 수 있습니다.
필요하다면 이제는 직접 구현해야 합니다. Delegate 파일을 연결하는 코드는 다음과 같습니다.
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
OS가 제공하는 기본 메뉴를 자동으로 추가해줍니다. 여기서 하는 작업은 크게 두가지가 존재합니다.
Scene Phase 라는 시스템을 공유 객체를 통해 확인이 가능합니다.
@Environment(\.scenePhase) private var scenePhase
위 코드는 Scene의 상태가 바뀌면 저장된 값이 바뀝니다. 값들은 다음과 같이 있습니다.
UI를 구성하는 가장 기본적인 요소 입니다. body
안에 계산 프로퍼티 형태로 추가하면 됩니다. 조심해야 될 부분으로 초기화 코드를 가볍게 해야합니다.(반복적으로 생성하기 때문) 따라서 멤버와이즈 이니셜라이저를 활용 하는게 좋습니다.
만약 다른 이니셜라이저와 같이 멤버 이니셜라이저를 사용하려면, 다른 이니셜라이저를 extension을 사용해서 선언해주면 됩니다.
View를 return하는 특징이 있습니다. 똑같은 수정자가 있다면 상위 계층에 적용 가능하고, 체이닝이 가능합니다.
드래그를 하여 화면을 닫는 동작
.interactiveDismissDisabled(Bool)
뷰의 환경으로부터 값을 읽게 해주는 속성래퍼 입니다. 다크모드 확인, 뷰 상태 확인 등 여러 기본 속성들이 존재 합니다. 뷰의 특정 속성을 변경하고 싶을 때 사용하면 됩니다. 값이 변경되면 SwiftUI는 값에 의존하는 뷰 들을 업데이트 합니다.
이들은 App과 Scene에 값을 저장하는 기능입니다. UserDefault
를 사용하고, 저장에는 약간의 텀이 존재합니다(SceneStorage가 좀 더 길다)
// 문법
@AppStorage(key: String) private var data: Type = Data
@SceneStorage(key: String) private var data: Type = Data
특정 커스텀 타입의 데이터를 저장하려면, RawRepresetable
프로토콜로 확장해야합니다.
되도록 아주 작은 Data를 저장하는 용도로 사용하는 것이 좋습니다.
잘봤읍니다.