let numbers = [1, 2, 3, 4, 5]
for number in numbers {
let formateedNumber = "\(number)$"
formateedNumbers.apend(formattedNumber)
}
let mappedNumbers = numbers.map { "\($0)$" }
FlatMap
Array의 차원을 1레벨 flat 해줌.
flatten() 메소드와 같은 결과 => flatten() + map = FlatMap
3가지 경우일 때 사용 1. non-nil인 결과들을 가지는 배열을 리턴 2. 주어진 Sequence내의 요소들을 하나의 배열로써 리턴 3. 주어진 Optional이 not-nil인지 판단 후 unwrapping하여 closure 파라미터로 전달
세 패턴 모두 특정 이벤트가 일어나면 원하는 객체에 알려주어 해당되는 처리를 하는 방법을 가지고 있다.
하지만, 다른 객체와 종속되어 동작하는 것은 '재사용성'과 '독립된 기능 요소' 측면에서 바람직하지 않다.
그러므로, Delegateion, Notification, KVO를 사용한다.
Delegate vs. Notification vs. KVO 장단점 비교
Delegate
Notification
KVO
장점
매우 엄격한 Syntax로 인해 프로토콜에 필요한 메소드들이 명확하게 명시됨.
컴파일 시 경고나 에러가 떠서 프로토콜의 구현되지 않은 메소드를 알려줌.
로직의 흐름을 따라가기 쉬움.
프로토콜 메소드로 알려주는 것뿐만이 아니라 정보를 받을 수 있음.
커뮤니케이션 과정을 유지하고 모니터링하는 제 3의 객체(ex: NotificationCenter 같은 외부 객체)가 필요없음.
프로토콜이 컨트롤러의 범위 안에서 정의됨.
많은 줄의 코드가 필요없이 쉽게 구현이 가능.
다수의 객체들에게 동시에 이벤트의 발생을 알려줄 수 있음.
Notification과 관련된 정보를 Any? 타입의 object, [AnyHashable: Any]? 타입의 userInfo로 전달할 수 있음.
두 객체 사이의 정보를 맞춰주는 것이 쉬움.
new/old value를 쉽게 얻을 수 있음.
key path로 옵저빙하기 때문에 nested objects도 옵저빙이 가능함.
단점
많은 줄의 코드가 필요.
delegate 설정에 nil이 들어가지 않게 주의해야함. 크래시를 일으킬 수 있음.
많은 객체들에게 이벤트를 알려주는 것이 어렵고 비효율적임.(가능은 하지만)
key 값으로 Notification의 이름과 userInfo를 서로 맞추기 때문에 컴파일 시 구독이 잘 되고 있는지, 올바르게 userInfo의 value를 받아오는지 체크가 불가능함.
추적이 쉽지 않을 수 있음.
Notificaiton post 이후 정보를 받을 수 없음.
NSObject를 상속받는 객체에서만 사용이 가능함.
dealloc될 때 옵저버를 지워줘야 함.
많은 value를 감지할 때는 많은 조건문이 필요.
Delegate
Protocol로 정의 (java의 interface => 규약)
Delegate 역할을 하려는 객체는 이 Protocol을 따르며 원형만 있던 메소드들의 구현을 함
이렇게 세팅 후 이전 객체(SomeView)는 어떤 이벤트가 일어났을 시 delegate로 지정한 객체(SomeController)에 알려줄 수 있다. => 종속되어 동작하는 것이 아님. 독립적으로 떨어져 있음.
// 1) Delegate 프로토콜 선언
protocol SomeDelegate {
func someFunction(someProperty: Int)
}
class SomeView: UIView {
// 2) 순환 참조를 막기 위해 weak으로 delegate 프로퍼티를 가지고 있음
weak var delegate: SomeDelegate?
func someTapped(num: Int) {
// 3) 이벤트가 일어날 시 delegate가 동작하게끔 함
delegate?.someFunction(someProperty: num)
}
}
// 4) Delegate 프로토콜을 따르도록 함
class SomeController: SomeDelegate {
var view: SomeView?
init() {
view = SomeView()
// 6) delegate를 자신으로 설정
view?.delegate = self
someFunction(someProperty: 0)
}
// 5) Delegate 프로토콜에 적힌 메소드 구현
func someFunction(someProperty: Int) {
print(someProperty)
}
}
let someController = SomeController()
// prints 0
Notification
Notification Center라는 싱글턴 객체를 통해서 이벤트들의 발생 여부를 옵저버를 등록한 객체들에게 Notification을 post하는 방식으로 사용
// 1) Notification을 보내는 ViewController
class PostViewController: UIViewController {
@IBOutlet var sendNotificationButton: UIButton!
@IBAction func sendNotificationTapped(_ sender: UIButton) {
guard let backgroundColor = view.backgroundColor else { return }
// Notification에 object와 dictionary 형태의 userInfo를 같이 실어서 보낸다.
NotificationCenter.default.post(name: Notification.Name("notification"), object: sendNotificationButton, userInfo: ["backgroundColor": backgroundColor])
}
}
// 2) Notification을 받는 ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 옵저버를 추가해 구독이 가능하게끔 함
NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived(notification:)), name: Notification.Name("notification"), object: nil)
}
// iOS 9 이상이 아닐 경우에는 removeObserver를 해줘야 함
deinit {
NotificationCetner.default.removeObserver(self)
}
@objc func notificationReceived(notification: Notification) {
// Notification에 담겨진 object와 userInfo를 얻어 처리 가능
guard let notificationObject = notification.object as? UIButton else { return }
print(notificationObject.titleLabel?.text ?? "Text is Empty")
guard let notificationUserInfo = notification.userInfo as? [String: UIColor],
let postViewBackgroundColor = notificationUserInfo["backgroundColor"] else { return }
print(postViewBackgroundColor)
}
}
Key Value Observing
메소드나 다른 액션에서 나타나는 것이 아니라 프로퍼티의 상태에 반응하는 형태
(Swift 4 전까지는 NSObject의 메소드인observeValue(forKeyPath:change:context:)를 오버라이드하여 옵저버를 추가했으나 Swift4부터는 구독하고 싶은 프로퍼티에 observe()를 추가하여 클로저로 사용할 수 있게 하였다. 그러나 Swift 상에서는 didSet이나 willSet 같은 것으로 충분히 대체가 가능할 것 같아(동감) 굳이 써야하나 싶은 패턴인 것 같다.)
어떤 content(status bar, navigation bar, toolbar, tab bar, etc) 에도 덮이지 않는 뷰를 그리기 위해서 제공
iOS 11에서 해당 property deprecated => Safe Area 소개함. (iPhoneX 대응으로 인해 left, right 정보도 필요해짐)
UIView
In iOS 11 the UIViewControllertopLayoutGuideand thebottomLayoutGuideproperties have been replaced by the new safe area properties in UIView: => safeAreaInserts, safeAreaLayoutGuide
@available(iOS 11.0, *)
open var safeAreaInsets: UIEdgeInsets { get }
// UIEdgeInserts - top, left, bottm, right : edge 크기
@available(iOS 11.0, *)
open var safeAreaLayoutGuide: UILayoutGuide { get }
// UILayoutGuide: A rectangular area that can interact with Auto Layout.
// bottomAnchor, heightAnchor, centerXAnchor, ...
The bounds rectange, which describes the view's location and size in its own coordinate system.
자신만의 좌표 시스템
기본 좌표: (0, 0)
bounds의 origin을 변경한다는 것은? 곧, subView들이 화면상에서 drawing 되는 위치가 변경됨을 의미 => subView들의 frame 값을 변화시키는 것이 아니다. 부모뷰 좌표축이 변하면서 subView가 그려져야 하는 위치가 달라졌기 때문 ScrollView/TableView 등을 스크롤할 때, scrollView.bounds가 변하고, 그리하여 subView들이 그려지는 위치가 대표적인 예 (subView들의 frame이 달라지는 게 아님)
ex ) targetView.bounds.origin.x = 60; targetView.bounds.origin.y = 50; 이라고 하면 targetView가 x축-> 60, y축-> 50으로 이동된 자식 뷰가 그려짐.!!!!!!!!!!
언제 사용? - View 내부에 그림을 그릴 때 (drawRect) - transformation 후, View의 크기를 알고 싶을 때 - 하위 View를 정렬하는 것과 같이 내부적으로 변경하는 경우
A stored property is a constant or variable that is stored as part of an instance of a particular class or structure.
Default property values
Assiging Constant properties during initalization (초기화 중에 할당되는 프로퍼티)
1-1) 상수 Structure 인스턴스의 stored property
If you create an instance of a structure and assign that instance to a constant, you cannot modify the instance’s properties, even if they were declared as variable properties:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6 // this will report an error, even though firstValue is a variable property
결론) Value type의 인스턴스를 상수로 생성하면, 모든 property는 변경할 수 없다. 반대로, Reference type의 인스턴스를 constant로 생성하면 변경 가능하다.
lazy stored property: 최초 사용될 때까지 초기 값이 계산되지 않는 프로퍼티
var keyword를 사용해야 함 => lazy stored property는 인스턴스 초기화가 완료될 때 까지 초기값을 가져올 수 없으므로 (let: 초기화가 완료되기 전에 값을 가져야 함.)
2. Computed Properties
Class, structure, enumeration은 computed property 정의 가능
실제로 값을 저장하는 것이 아님
getter와 optional setter를 제공하여 다른 property의 값을 간접적으로 세팅할 수 있다.
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
3. Property Observers
property observer는 property의 값의 변화에 관찰하고 반응한다!(observe and respond)
property의 값이 세팅되는 매 순간마다 호출됨. (같은 값으로 세팅되더라도)
lazy stored property를 제외한 모든 property(stored, computed property)에 추가 가능
overriding된 상속받은 property에도 property observer를 추가할 수 있다.
observer 종류 - willSetis called just before the value is stored. - didSetis called immediately after the new value is stored.
in-out 으로 observer를 가지는 property가 parameter로 전달되어도, willSet과 didSet observer는 항상 호출됨. => copy-in copy-out memory model이기 때문에!
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
4. Global and Local Variables
Global constants and variables are always computed lazily, in a similar manner toLazy Stored Properties. Unlike lazy stored properties, global constants and variables do not need to be marked with thelazymodifier.
Local constants and variables are never computed lazily.
5. Type Properties
클래스 변수
static keyword로 정의
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// Prints "7"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "7"
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// Prints "10"
print(AudioChannel.maxInputLevelForAllChannels)
// Prints "10"
- Swift provides signed and unsigned integers in 8, 16, 32, and 64 bit forms.
- UInt8, Int32, ...
Int
Swift provides an additional integer type,Int, which hasthe same size as the current platform’s native word size:
On a 32-bit platform,Intis the same size asInt32.
On a 64-bit platform,Intis the same size asInt64.
UInt
Swift also provides an unsigned integer type,UInt, which has the same size as the current platform’s native word size:
On a 32-bit platform,UIntis the same size asUInt32.
On a 64-bit platform,UIntis the same size asUInt64.
NOTE UseUIntonly when you specifically need an unsigned integer type with the same size as the platform’s native word size. If this isn’t the case,Intis preferred, even when the values to be stored are known to be nonnegative. => This aids codeconsistencyandinteroperability.