***멀티태스킹
이 하나의 운영체제 안에서 여러 프로세스가 실행되는 것이라면,
멀티쓰레드
는 하나의 프로세스가 여러 작업을 여러 쓰레드를 사용해 동시에 처리하는 것을 의미한다.
스레드 하나가 프로세스 내 자원을 망쳐버린다면 모든 프로세스가 종료될 수 있다.
자원을 공유하기 때문에 필연적으로 동기화 문제가 발생할 수 밖에 없다. 교착상태가 발생하지 않도록 주의해야 한다.
멀티스레드를 사용하면 각각의 스레드 중 어떤 것이 어떤 순서로 실행될지 그 순서를 알 수 없다.
만약 A 스레드가 어떤 자원을 사용하다가 B 스레드로 제어권이 넘어간 후 B 스레드가 해당 자원을 수정했을 때, 다시 제어권을 받은 A 가 해당 자원에 접근하지 못하거나, 바뀐 자원에 접근하게 되는 오류가 발생할 수 있다.
이처럼 여러 스레드가 함께 전역 변수를 사용할 경우 발생할 수 있는 충돌을
동기화 문제
라고 한다. 스케줄링은 운영체제가 자동으로 해주지 않기 때문에 프로그래머가 적절한 기법을 직접 구현해야 하므로 프로그래밍할 때 멀티스레드를 사용하려면 신중해야 한다.
🌟🌟🌟Asynchronous에서는 요청에 대한 응답이 나중에 오기 때문에 보통 Callback 함수로 들어옴
여기서 Bundle 구조와 Bundle은 다른 개념
Bundle 구조에서 Bundle은 뒤에서 설명하겠지만 디렉토리이고, Bundle은 Swift3에서 NSBundle의 명칭이 바뀐 용어
NSBundle은 Bundle 디렉토리 안에 포함된 리소스에 쉽게 접근할 수 있도록 도와주는 객체
Package는 사용자에게 제공하는 디렉토리.
지금 Mac에서 응용 프로그램을 보면 모두 아이콘들이다. 이는 모두 파인더가 단일 파일인 것처럼 사용자에게 제공하는 것일뿐 실제로는 디렉토리.
단일 파일처럼 취급되는 이유는 일반 사용자가 Package에 부정적인 영향을 줄 수 있는 변경을 방지하기 위해서.
Bundle은 개발자에게 제공되는 디렉토리. 단순히 폴더 역할 뿐만 아니라 위에서 설명한 여러 기능들을 추가로 제공.
🌟 그럼 .app같은 경우엔 왜 같은 의미로 언급되는 것일까? 파인더는 아래의 조건 중 하나라도 해당된다면 디렉토리를 Package로 취급.
위의 이유로 많은 유형의 Bundle이 Package. 따라서 .app은 Package이자 Bundle.
Core Graphics
-> Core Animation
-> UIKIt
, AppKit
순으로 발전했고 iOS 앱 개발시 해당 프래임워크들을 필요한 기능에 따라 선택적으로 사용할 수 있다. 저수준의 프래임워크일수록 많은 기능을 제공하지만 전체적인 코드량이 많다. 고수준의 프레임 워크일수록 저수준보다 유연성은 떨어지지만 사용이 간편하고 코드가 적다.@MainActor
라는 어트리뷰트는 Swift 5.5 부터 도입되는 개념으로, 메인 쓰레드에서 실행되는 클래스임을 가리킨다.앱의 콘텐츠를 화면에 보여주기 위하여 Main window 를 제공
추가적인 콘텐츠를 보여주기 위해 추가적인 window 를 생성해야 할 때 (external display 를 사용할 경우)
만약 스토리 보드를 사용하지 않고 코드로 UI 를 구성한다면, 윈도우를 직접 생성해야한다!
@main
struct Mail: App {
var body: some Scene {
WindowGroup { // <-
MailViewer()
}
Settings { // <-
SettingsView()
}
}
}
Scene은 뷰 계층에서 가장 root에 해당되며, 시스템에 의해 라이프 사이클을 가지고 있는 형태
Scene타입에서만 WindowGroup, Settings 등을 사용
WindowGroup 개념
- Window라는 개념은 뷰들의 컨테이너 역할을 하면서 동시에 터치 이벤트와 같은 이벤트를 가장 먼저 수신하여 subview들에게 이벤트를 전달하는(responder chain) 기능
- macOS와 iPadOS와 같이 그룹으로부터 여러개의 window를 띄울 수 있는 형태일때 WindowGroup을 여러개 정의하여 사용
popToRootViewController
메서드를 통해 기존에 생성된 모든 내비게이션 스택을 지우고, 한 번에 루트 뷰로 이동NavigationView {
/* content */
}
.navigationViewStyle(.stack)
NavigationStack {
/* content */
}
Stack은 항상 제거되지 않은 가장 최근에 추가된 View를 표시하며, RootView는 제거할 수 없다!
NavigationView -> NavigationStack 참고 : https://zeddios.tistory.com/1376
//collection view
class PostCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var postThumbnailImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
public func get(data: ProfilePostImg) {
let urlString = data.postImgUrl
postThumbnailImageView.kf.setImage(with: URL(string: urlString))
}
}
class MyPostViewController: BaseViewController {
private func setupPostColletionView() {
postCollectionView.register(UINib(nibName: postCell, bundle: nil), forCellWithReuseIdentifier: postCell)
postCollectionView.delegate = self
postCollectionView.dataSource = self
}
}
extension MyPostViewController : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
print(indexPath.row)
let postIdx = posts![indexPath.row].postIdx
postDataManager.getPostData(postIdx, delegate: self)
}
}
extension MyPostViewController : UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: postCell , for: indexPath) as! PostCollectionViewCell
let cellData = posts![indexPath.row]
cell.get(data: cellData)
return cell
}
}
//table view
class RecommendFeedViewController: UIViewController {
let cellId = "RecommendPostTableViewCell"
var feedResult : [RecommendFeedResult] = []
@IBOutlet weak var recommendFeedTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setUpTableView()
}
func setUpTableView() {
self.recommendFeedTableView.delegate = self
self.recommendFeedTableView.dataSource = self
self.recommendFeedTableView.register(UINib(nibName: cellId, bundle: nil), forCellReuseIdentifier: cellId)
}
}
extension RecommendFeedViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedResult.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! RecommendPostTableViewCell
let cellData = feedResult[indexPath.row]
cell.get(data: cellData)
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (recommendFeedTableView.bounds.height) * 0.8
}
}
@IBOutlet weak var firstTableView: UITableView!
@IBOutlet weak var secondTableView: UITableView!
// data source, delegate 등록
firstTableView.dataSource = self
firstTableView.delegate = self
firstTableView.register(UINib(nibName: cellId, bundle: nil), forCellReuseIdentifier: cellId)
secondTableView.dataSource = self
secondTableView.delegate = self
secondTableView.register(UINib(nibName: cellId, bundle: nil), forCellReuseIdentifier: cellId)
// dataSource의 함수인, cellForRowAt에서, 인자로 받는 tableView로 TableView의 이름을 확인 할 수 있다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == firstTableView {
return firstDataArray.count
} else if tableView == secondTableView {
return secondDataArray.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch tableView {
case firstTableView:
let cell = firstTableView.dequeueReusableCell(withIdentifier: "firstCell") as! UITableViewCell
cell.textLabel?.text = data1[indexPath.row]
return cell
case secondTableView:
let cell = secondTableView.dequeueReusableCell(withIdentifier: "secondCell") as! UITableViewCell
cell.textLabel?.text = data2[indexPath.row]
return cell
default:
return UITableViewCell()
}
}
firstTableView.tag = 1
secondTableView.tag = 2
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if tableView.tag == 1 {
return "First TableView Header"
} else {
return "Second TableView Header"
}
}
uikit 관련
앱이 이벤트 처리를 마치는 시점이 Update Cycle 지점
이 시점에서 시스템은 디스플레이 뿐만 아니라 레이아웃과 제약조건을 업데이트
class CircleView: UIView {
private var x: Int = 0 {
didSet {
self.setNeedsDisplay()
}
}
private var y: Int = 0 {
didSet {
self.setNeedsDisplay()
}
}
private var radius: Int = 0 {
didSet {
self.setNeedsDisplay()
}
}
}
복잡한 화면을 오토 레이아웃으로 모두 설정해주기 위해서는 많은 조건들이 필요하다. 하지만 stackview를 쓴다면 주어진 설정값에 맞게 stackview가 자동으로 조건을 조절해주기 때문에 개발자가 직접 설정해줘야하는 조건이 엄청 줄어든다.
stackview는 자체 컨텐츠가 없기 때문에 렌더링하는 수고가 들어가지 않는다. 따라서 일반 뷰보다 훨씬 빠르고 가볍다.