iOS
UIKit
β’
iOS μ±μμ νμν UI μμλ€μ μ 곡νλ νλ μμν¬
β’
μμ£Όμ¬μ©νλ View: UILabel, UIButton, UIImageView, UIView, UIStackView, UIScrollView, UITableView, UICollectionView, UIViewController, UINavigationViewController
// μμ£Ό μ¬μ©νλ View νμ© μμ
import UIKit
// UIView
let view = UIView()
view.layer.cornerRadius = 70
view.layer.shadowOffset = CGSize(width: 5, height: 5)
view.layer.shadowOpacity = 0.7
view.layer.shadowRadius = 5
view.layer.shadowColor = UIColor.gray.cgColor
view.translatesAutoresizingMaskIntoConstraints = false
// UILabel
let label = UILabel()
label.text = "ν
μ€νΈ"
label.font = .systemFont(ofSize: 14)
label.textColor = .black
// UIButton
let button = UIButton()
button.setTitle("λ²νΌ", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
button.setImage(UIImage(systemName: "heart"), for: .normal)
button.addTarget(self, action: #selector(onClickButton), for: .touchUpInside)
button.titlePadding = 10 // iOS 15 μ΄μ
@objc func onClickButton() {
print("λ²νΌμ΄ ν΄λ¦λμ΅λλ€.")
}
// UIImageView
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.contentMode = .scaleAspectFit
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true //λμΉλ μμ μλΌλ΄κΈ°
// UIStackView
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .equalSpacing
stackView.spacing = 8
stackView.addArrangedSubview(label, button, imageView)
// UITableView
import UIKit
class MyTableViewController: UIViewController {
@IBOutlet weak var myTableView: UITableView!
let friendNames: [String] = ["Henry", "Leeo", "Jay", "Key"]
override func viewDidLoad() {
super.viewDidLoad()
myTableView.delegate = self
myTableView.dataSource = self
}
}
extension MyTableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "MyFirstCell", for: indexPath)
cell.textLabel?.text = friendNames[indexPath.row]
return cell
}
}
Swift
볡μ¬
SwiftUI
β’
UIKit κ³Ό κ°μ UI κ°λ° νλ μμν¬λ‘ iOS 13 μ΄μ λ²μ λΆν° μ¬μ© κ°λ₯ν©λλ€.
β’
SwiftUIλ UIμ μνμ λν΄ μ μΈνκ³ μνκ° λ³κ²½λλ©΄ μλμΌλ‘ UIλ₯Ό μ
λ°μ΄νΈνλ μ μΈμ νλ‘κ·Έλλ°μ νκ² λ©λλ€.
ViewController Lifecycle
β’
ViewControllerμ λΌμ΄νμ¬μ΄ν΄ λ©μλλ₯Ό ν΅ν΄ νλ©΄μ΄ μ΄λ¦¬κ³ λ«νλ μμ μ λ§κ² μ λλ©μ΄μ
, API μμ² λ±μ μ²λ¦¬λ₯Ό ν μ μμ΅λλ€.
β’
init β loadView β viewDidLoad β viewWillAppear β viewIsAppear β viewIsAppearing β viewWillDisappear β viewDidDisappear β deinit
import UIKit
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
print("viewDidLoad")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear")
}
override func viewIsAppearing(_ animated: Bool) {
super.viewIsAppearing(animated)
print("viewIsAppearing")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("viewWillDisappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("viewDidDisappear")
}
private func configureUI() {
view.addSubview(button)
view.backgroundColor = .white
button.setTitle("νμ΄μ§ μ΄λ", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchDown)
button.backgroundColor = .red
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 30)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 200),
button.heightAnchor.constraint(equalToConstant: 120),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
@objc
private func buttonTapped() {
self.navigationController?.pushViewController(AdamViewController(), animated: true)
}
}
Swift
볡μ¬
λ μ΄μμ
β’
AutoLayout: iPhone κΈ°κΈ° μ¬μ΄μ¦μ λ§μΆ° μλμΌλ‘ μ¬μ΄μ¦λ₯Ό λ§μΆ°μ£Όλ κ². μ μ½ μ‘°κ±΄(Constraints)μ λ°λΌ λ·° κ³μΈ΅(View Hierachie) ꡬ쑰μ μλ λͺ¨λ λ·°μ ν¬κΈ°μ μμΉλ₯Ό λμ μΌλ‘ κ³μ°νλ κ²
β’
Safe area: iPhoneμμ μ±μ 컨ν
μΈ κ° μμ μ μΌλ‘ 보μ΄κΈ° μν μμμΌλ‘ μμ΄ν° κΈ°κΈ°λ§λ€ λͺ¨μ΅μ΄ μ‘°κΈμ© λ€λ₯΄κΈ° λλ¬Έμ Safe Area λ₯Ό κ³ λ €νμ¬ λ·°λ₯Ό λ°°μΉν΄μΌν©λλ€.
β’
Constraint: Constraintλ λ·°μ λ·° μ¬μ΄μ μ μ½μ‘°κ±΄μ μλ―Έν©λλ€.
β¦
left / right / top / bottom: κ°κ° μ’ / μ° / μ / μλ μ μ½ μ‘°κ±΄μ μλ―Έν©λλ€.
β¦
leading / trailing: μ§μνλ 쑰건μΌλ‘ μΌμͺ½μμ μ€λ₯Έμͺ½μΌλ‘ μ½λ κ² μμ°μ€λ¬μ΄ μ§μ 쑰건μ κΈ°κΈ°μμλ leadingμ μΌμͺ½, trailingμ μ€λ₯Έμͺ½μΌλ‘ λμλκ³ λ°λμ κ²½μ°μ leadingμ μ€λ₯Έμͺ½, trailingμ μΌμͺ½μΌλ‘ λμλ©λλ€.
GCD(Grand Central Dispatch)
β’
iOS, macOS λ±μ Apple νλ«νΌμμ λ©ν°μ€λ λ νλ‘κ·Έλλ°μ κ°λ¨νκ³ ν¨μ¨μ μΌλ‘ ꡬννκΈ° μν κΈ°μ . GCDλ λ€μν μμ
μ κ΄λ¦¬νκ³ μ€ννκΈ° μν κ°λ ₯ν APIλ₯Ό μ 곡νμ¬ λ³λ ¬μ±μ νμ©ν μ μκ² ν΄μ€λλ€.
β’
νΉμ§
1.
ν(Queue) κΈ°λ°μ μμ
κ΄λ¦¬: GCDλ νλ₯Ό μ¬μ©νμ¬ μμ
μ κ΄λ¦¬ν©λλ€. μ΄λ₯Ό ν΅ν΄ μμ
μ μΈλΆννκ³ , μ€νν νμ λ°λΌ μμ
μ μμ±μ κ²°μ ν μ μμ΅λλ€.
2.
κ°νΈν λΉλκΈ° μμ
μ²λ¦¬: GCDλ₯Ό μ¬μ©νλ©΄ λΉλκΈ°μ μΌλ‘ μμ
μ μνν μ μμ΅λλ€. μ΄λ νλ‘κ·Έλλ¨Έκ° λ³λμ μ€λ λλ₯Ό κ΄λ¦¬νμ§ μμλ λλ―λ‘ κ°λ°μ κ°νΈνκ² λ§λ€μ΄μ€λλ€.
3.
νμ μ’
λ₯μ λ°λ₯Έ λ€μν λμ λ°©μ: GCDμλ Serial Queueμ Concurrent Queueκ° μμ΅λλ€. Serial Queueλ μμ
μ΄ μμ°¨μ μΌλ‘ μ€νλλ©°, Concurrent Queueλ μ¬λ¬ μμ
μ΄ λμμ μ€νλ μ μμ΅λλ€.
4.
μ½λ°±μ ν΅ν μμ
μλ£ μ²λ¦¬: μμ
μ΄ μλ£λλ©΄ μ½λ°±(closure)μ μ¬μ©νμ¬ κ²°κ³Όλ₯Ό μ²λ¦¬ν μ μμ΅λλ€.
5.
QoS(Quality of Service) μ§μ: GCDλ κ° νμ λν μ°μ μμ μ€μ μ μ§μνμ¬ μμ€ν
μ΄ μμ
μ μ μ ν κ΄λ¦¬νκ³ μ΅μ μ μ±λ₯μ μ μ§ν μ μλλ‘ ν©λλ€.
β’
DispatchQueue.main: λ©μΈ μ€λ λμμ λμνλ©°, μ£Όλ‘ μ¬μ©μ μΈν°νμ΄μ€(UI)λ₯Ό μ
λ°μ΄νΈνκ±°λ μ¬μ©μμμ μνΈμμ©μ μ²λ¦¬ν λ μ¬μ©λ©λλ€.
β’
DispatchQueue.global(): μ μ(λ°±κ·ΈλΌμ΄λ) μ€λ λμμ μμ
μ μ²λ¦¬νλλ° μ¬μ©λλ©°, μ¬λ¬ μ€λ λμμ λμμ μ€νλλ λΉλκΈ° μμ
μ μ²λ¦¬ν λ μ μ©ν©λλ€.
// λκΈ°μ μΌλ‘ μ€νλλ μμ
DispatchQueue.global().sync {
print("Synchronous Task")
}
// λΉλκΈ°μ μΌλ‘ μ€νλλ μμ
DispatchQueue.global().async {
print("Asynchronous Task")
}
// λ°±κ·ΈλΌμ΄λμμ λΉλκΈ° μμ
μ€ν
DispatchQueue.global().async {
// μ¬κΈ°μ λ°±κ·ΈλΌμ΄λμμ μ€νλ μμ
μ μνν©λλ€.
for i in 1...5 {
print("Background Task \(i)")
}
// μμ
μ΄ μλ£λμμμ λ©μΈ μ€λ λλ‘ μ립λλ€.
DispatchQueue.main.async {
print("Background Task Completed, Updating UI")
// UI μ
λ°μ΄νΈ λ±μ μνν μ μμ΅λλ€.
}
}
Swift
볡μ¬
λ€νΈμνΉ
β’
iOSμμ λ€νΈμν¬ ν΅μ μ μ£Όλ‘ URLSessionμ μ¬μ©νμ¬ μνλ©λλ€. λ€νΈμνΉμ μΈλΆ μλ² λλ μΈν°λ· 리μμ€μ λ°μ΄ν°λ₯Ό μ£Όκ³ λ°λ μμ
μ μλ―Ένλ©°, λΉλκΈ°μ μΈ λ°©μμΌλ‘ μ²λ¦¬νλ κ²μ΄ μΌλ°μ μ
λλ€.
//μμ
func fetchData<T: Decodable>(for url: URL, completion: @escaping (Result<T, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
do {
let object = try JSONDecoder().decode(T.self, from: data)
completion(.success(object))
} catch let decoderError {
completion(.failure(decoderError))
}
}
}.resume()
}
// fetchData ν¨μ νΈμΆ
let url = URL(string: "URL μ£Όμ")
fetchData(for: url) { (result: Result<[WeatherMain], Error>) in
switch result {
case .success(let weather):
print(weather)
case .failure(let error):
print(error)
}
// λͺ¨λΈ
struct WeatherMain: Codable {
let temp: Double
let temp_min: Double
let temp_max: Double
let humidity: Int
}
Swift
볡μ¬
UserDefaults
β’
κ°λ¨ν λ°μ΄ν°λ₯Ό μ±κΈν΄(Singleton) κ°μ²΄μ Key-Value μμΌλ‘ μ μ₯νλ λ°©μμΌλ‘ μ£Όλ‘ μ¬μ©μ μΈν
κ° κ°μ μμ λ°μ΄ν°λ₯Ό μ μ₯νλ λ°μ μ ν©νλ©° μ¬μ©νκΈ° μ½κ³ μ κ·ΌνκΈ° λΉ λ₯Έ μ₯μ μ΄ μμ΅λλ€.
β’
μ μ₯λλ λ°μ΄ν°κ° μνΈνλμ§ μμΌλ©° μ£Όλ‘ λ¬Έμμ΄, μ«μ, λ μ§μ κ°μ κΈ°λ³Έ λ°μ΄ν° νμ
λ§ μ μ₯ν μ μμ΅λλ€.
CoreData
β’
μ±μμ λͺ¨λΈ κ³μΈ΅ κ°μ²΄λ₯Ό κ΄λ¦¬νλ λ° μ¬μ©νλ νλ μμν¬. κ°μ²΄ κ·Έλν κ΄λ¦¬μλ‘, κ°μ²΄λ₯Ό μ§μ μ μΌλ‘ μ°κ²°ν΄μ κ΄λ¦¬ν©λλ€.
β’
5κ°μ§ κΈ°λ₯
1.
μμμ±(Persistence)
2.
λ³κ²½μ¬νμ Undo, Redo
3.
λ°±κ·ΈλΌμ΄λ λ°μ΄ν° μμ
κΈ°λ₯
4.
λκΈ°ν κΈ°λ₯
5.
λ²μ κ΄λ¦¬ λ° λ§μ΄κ·Έλ μ΄μ
(Migration)
λ°μν νλ‘κ·Έλλ°(Reactive Programming)
β’
λ°μ΄ν° μ€νΈλ¦Ό λλ λ°μ΄ν°μ λ³νμ λ°λΌ μ½λκ° μλμΌλ‘ λ°μνλ νλ‘κ·Έλλ° ν¨λ¬λ€μ. μ΄ ν¨λ¬λ€μμμλ λ°μ΄ν°μ λ³κ²½ μ¬νμ κ°μ§νκ³ μ΄μ λ°λΌ μ°μμ μΌλ‘ λ°μνλ λ°©μμΌλ‘ νλ‘κ·Έλ¨μ μμ±ν©λλ€.
β’
ν΅μ¬ κ°λ
1.
λ°μ΄ν° μ€νΈλ¦Ό(Data Stream): μ΄λ²€νΈ μ€νΈλ¦Ό, κ°μ νλ¦ λ±κ³Ό κ°μ΄ μκ°μ λ°λΌ μ°μμ μΌλ‘ λ°μνλ λ°μ΄ν°μ νλ¦μ λνλ
λλ€. μ¬μ©μ μ
λ ₯, μΌμ λ°μ΄ν°, μΈλΆ APIμ μλ΅ λ± λ€μν μμ€μμ λμ¬ μ μμ΅λλ€.
2.
μ΅μ λ²(Observer) ν¨ν΄: λ°μ΄ν°μ λ³νλ₯Ό κ°μνκ³ , λ³νμ λ°λΌ νΉμ μμ
μ μννλ λμμΈ ν¨ν΄μ
λλ€. λ³νκ° μΌμ΄λλ©΄ μ΅μ λ²λ ν΄λΉ λ³νμ λ°μνμ¬ μλ¦Όμ λ°κ³ , νμν μμ
μ μνν©λλ€.
3.
μ€νΈλ¦Όμ λ³νκ³Ό μ‘°μ(Transforming and Manipulating Streams): λ°μ΄ν° μ€νΈλ¦Όμ μ‘°μνμ¬ νν°λ§, λ§€ν, κ²°ν©, λ³ν λ±μ μννμ¬ μλ‘μ΄ μ€νΈλ¦Όμ μμ±νλ μμ
μ
λλ€. μ΄λ₯Ό ν΅ν΄ λ°μ΄ν° μ€νΈλ¦Όμ ν¨κ³Όμ μΌλ‘ μ²λ¦¬νκ³ νμν ννλ‘ κ°κ³΅ν μ μμ΅λλ€.
4.
λ°μΈλ©(Binding): λ°μ΄ν°μ λ³νμ μ΄μ λ°λ₯Έ μμ
μ μ°κ²°μ λνλ
λλ€. λ°μ΄ν°μ UI μμ λλ λ°μ΄ν°μ μμ
μ¬μ΄μ μ°κ²°μ μ€μ νμ¬ λ°μ΄ν°μ λ³κ²½μ΄ λ°μνλ©΄ μ΄μ λ§μΆ° UIλ λ€λ₯Έ μμ
μ μλμΌλ‘ μ
λ°μ΄νΈν©λλ€.
Combine
β’
λΉλκΈ°μ μΈ μ΄λ²€νΈ μ€νΈλ¦Όμ μ²λ¦¬νκ³ μ‘°μνλ λ° μ¬μ©νλ Swift νλ μμν¬.
β’
ν¨μν νλ‘κ·Έλλ°κ³Ό λ°μν νλ‘κ·Έλλ° κ°λ
μ κΈ°λ°μΌλ‘ νλ©°, λ°μ΄ν° μ€νΈλ¦Όμ κ°λ¨νκ² μ‘°μνκ³ μ‘°ν©ν μ μλ λꡬλ₯Ό μ 곡ν©λλ€.
β’
μ£Όμ κ°λ
1.
Publisher: λ°μ΄ν° μ€νΈλ¦Όμ μμ±νκ³ , μ΄λ²€νΈλ₯Ό λ°©μΆνλ νμ
. κ°μ λ°©μΆν μ μμΌλ©°, μ€λ₯λ₯Ό λ°©μΆνκ±°λ μμ
μ΄ μλ£λμμμ μ릴 μ μμ΅λλ€.
2.
Subscriber: Publisherμμ λ°©μΆλλ μ΄λ²€νΈλ₯Ό λ°μ μ²λ¦¬νλ νμ
. κ°μ λ°μ μ²λ¦¬νκ±°λ, μ€λ₯λ μμ
μλ£ μ΄λ²€νΈλ₯Ό μ²λ¦¬ν©λλ€.
3.
Operators: λ°μ΄ν° μ€νΈλ¦Όμ μ‘°μνκ³ λ³ννκΈ° μν λ€μν μ°μ°μκ° μ 곡λ©λλ€. map, filter, flatMap λ±μ μ°μ°μλ₯Ό μ¬μ©νμ¬ λ°μ΄ν° μ€νΈλ¦Όμ μ‘°μνκ³ μλ‘μ΄ ννλ‘ λ³νν μ μμ΅λλ€.
4.
Cancellable: ꡬλ
μ μ·¨μν μ μλ νμ
. ꡬλ
μ μ·¨μν¨μΌλ‘μ¨ λ μ΄μ μ΄λ²€νΈλ₯Ό λ°μ§ μλλ‘ μ€μ ν μ μμ΅λλ€.
import Foundation
import Combine
class DataModel {
@Published var textValue: String = ""
}
let dataModel = DataModel()
let cancellable = dataModel.$textValue.sink { newValue in
print("Value changed to: \(newValue)")
}
dataModel.textValue = "Hello, Combine!"
dataModel.textValue = "Another value"
/*
μΆλ ₯
Value changed to:
Value changed to: Hello, Combine!
Value changed to: Another value
*/
Swift
볡μ¬
RxSwift
β’
ReactiveX(Reactive Extensions) ν¨ν΄μ Swift λ²μ μΌλ‘, Swiftλ‘ μμ±λ λ°μν νλ‘κ·Έλλ°μ μν λΌμ΄λΈλ¬λ¦¬μ
λλ€. RxSwiftλ λ°μ΄ν° μ€νΈλ¦Όκ³Ό μ΄λ₯Ό λ€λ£¨λ μ°μ°μλ€μ ν΅ν΄ λΉλκΈ° λ° μ΄λ²€νΈ κΈ°λ° νλ‘κ·Έλλ°μ μ§μν©λλ€.
β’
μ£Όμ κ°λ
1.
Observable: λ°μ΄ν° μ€νΈλ¦Όμ λνλ΄λ νμ
μΌλ‘, λ°μ΄ν°μ λ³νλ μ΄λ²€νΈλ₯Ό λ°©μΆ(emit)ν©λλ€. μ΄λ²€νΈ μνμ€λ₯Ό λ°μμν€λλ° μ¬μ©λκ³ μ΄ μ΄λ²€νΈλ next, error, completedμ κ°μ μ’
λ₯κ° μμ΅λλ€.
2.
Observer: Observableμμ λ°©μΆλ λ°μ΄ν°λ μ΄λ²€νΈμ λ°μνλ κ°μ²΄λ‘, μ΄λ₯Ό ꡬλ
(subscribe)νμ¬ λ°μ΄ν°μ λ³νλ₯Ό κ°μνκ³ μ²λ¦¬ν©λλ€.
3.
Operator: Observableμ λ³ννκ±°λ μ‘°μνλ ν¨μλ‘, λ°μ΄ν° μ€νΈλ¦Όμ μ‘°μνκΈ° μν΄ μ¬μ©λ©λλ€. RxSwiftμλ λ§μ λ€μν μ°μ°μλ€μ΄ ν¬ν¨λμ΄ μμ΄μ λ°μ΄ν°λ₯Ό νν°λ§νκ±°λ λ³ν, κ²°ν©, μ‘°μνλ λ±μ μμ
μ μνν μ μμ΅λλ€.
4.
Schedulers: λΉλκΈ° μ½λμ μ€νμ κ΄λ¦¬νλλ° μ¬μ©λλ©°, μμ
μ΄ μ΄λ μ€λ λμμ μ€νλλμ§ μ μ΄ν©λλ€. λ©μΈ μ€λ λμμ UI μ
λ°μ΄νΈλ₯Ό μννκ±°λ λ°±κ·ΈλΌμ΄λ μ€λ λμμ μμ
μ μννλ λ±μ μΌμ μ€μΌμ€λ§ν λ μ¬μ©λ©λλ€.
// RxSwiftμ μμ
import Foundation
import RxSwift
import RxCocoa
class DataModel {
let textValueSubject = BehaviorSubject<String>(value: "")
var textValue: Observable<String> {
return textValueSubject.asObservable()
}
}
let dataModel = DataModel()
let disposable = dataModel.textValue.subscribe(onNext: { newValue in
print("Value changed to: \(newValue)")
})
dataModel.textValueSubject.onNext("Hello, RxSwift!")
dataModel.textValueSubject.onNext("Another value")
/*
μΆλ ₯
Value changed to:
Value changed to: Hello, RxSwift!
Value changed to: Another value
*/
Swift
볡μ¬