: key-value
쌍으로 디바이스에 데이터를 저장하는 기능을 제공하는 인터페이스
: Apple에서 제공하는 데이터관리 프레임워크
Cloudkit
을 사용해 여러 장치에 데이터를 동기화하는 것도 가능하다.SQLite
에 데이터를 저장하고 관리하는 프레임워크SQLite
와 같이 테이블을 이용하지 않고 객체를 생성하여 데이터를 운영하기에 더 많은 저장공간과 메모리를 필요로 한다.: 서버가 아니라 응응프로그램에 넣어 사용하는 비교적 가벼운 데이터베이스
SQLite
는 iOS, Android, Linux, Window 등과 같이 다양한 운영체제에서 사용된다.: 오픈 소스 데이터베이스 관리시스템(DBMS)으로, 모바일 환경을 주요 타깃으로 삼은 데이터베이스
SQLite
, Core Data
보다 속도가 빠르고 성능면에서 더 우수하다.NoSQL
데이터베이스를 지향하며, 데이터 모델 구조 자체가 객체 컨테이너로 구성되어 있다.Realm
을 사용하면 Realm 모바일 플랫폼을 구축할 수 있다. 이 경우 Real-time data sync 즉, 라이브 오브젝트를 통한 서버와 모바일 디바이스 간의 실시간 동기화가 가능해진다.UIViewController: UIKit 앱의 뷰 계층 구조를 관리하는 객체
Contents View Controller
: 화면을 구성하는 View를 직접 구현하고 관련된 이벤트를 처리하는 View Controller
Container View Controller
: 하나 이상의 ViewController를 child Viewcontroller로 관리하는 View Controller.
: 애플리케이션이 디바이스에 설치될 때, 앱 스토어와 운영체제가 디바이스의 특성에 맞게 설치되도록 하는 설치 최적화 기술.
→ 최소한의 디스크 사용과 빠른 다운로드를 제공할 수 있다.
App thinning은 슬라이싱(slicing), 비트코드(bitcode), 그리고 주문형 리소스(on-demand resource)가 있다.
앱이 지원하는 여러 디바이스에 대해 각각 조각 애플리케이션 번들을 생성하고, 해당 디바이스에 가장 적합한 조각을 전달하는 기술이다.
개발자가 App store connect
에 업로드하면, 앱 스토어에서 디바이스 특성에 따라 다양한 버전의 조각들을 생성한다. 그리고 사용자가 그 조각 중에서 가장 알맞은 조각(app variant)을 다운로드 받는 것이다.
기계언어로 번역되기 이전 단계의 중간표현(Intermediate Representation)을 말한다.
현재 iOS에서는 옵션이나 기본 설정으로 되어 있으며, 개발자가 프로젝트 옵션에서 선택할 수 있다. 비트코드를 사용하여 업로드를 하면 애플이 애플리케이션을 재컴파일하여 앱 바이너리(app binary)를 생성한다. 비트코드를 사용하지 않으면, 모든 경우의 디바이스 경우에 따라 바이너리로 생성하여 합쳐져서 fat binary
라는 것이 업로드되지만, 비트코드를 사용하면 필요 경우에 따라 재컴파일하게 되므로 여기에서 최적화할 수 있다.
쉽게 말해서, 필요할 때 다운로드 받는 것이다.
예를 들어 사용자가 게임을 할 때, 현재 레벨보다 상위레벨은 필요하지 않으므로 갖고 있을 필요가 없다. 사용자의 레벨이 필요할 때 다운로드 받는 것이다! 인앱 구매도 사용자의 선택에 따라 다운로드를 받는 또다른 예다.
→ UIApplication 객체가 생성된다.
UIApplication 객체란?
: iOS에서 실행중인 앱의 제어와 조정을 담당하는 중앙지점.
시스템과 앱의 여러 객체들간의 대화를 가능하게 해준다.
main()
함수가 실행된다main()
함수는 UIApplicationMain()
함수를 호출한다UIApplicationMain()
함수가 UIApplication 객체를 생성한다. info.plist
파일로부터 앱 구성에 필요한 정보들을 로드한다.application(_:didFinishLaunchingWithOptions:)
메소드가 호출된다.Swift는 main 함수가 없지만 @main
이라는 어노테이션 표기가 있다. 이 표기를 통해서 object-c의 1-5 과정이 대체된다.
1~5. @main
어노테이션을 찾고 그 클래스를 실행한다
6. App Delegate클래스의 application(:didFinishLaunchingWithOptions:)
메소드를 호출한다 (앱이 실행될때 처리할 내용이 있으면 여기에 작성)
7. 이벤트 루프 실행, 작성한 코드들 실행
8. 앱이 종료될때 앱에 대한 메모리 제거를 위해서 aplicationWillTerminate(:)
메소드를 호출한다 (앱이 종료될때 처리할 내용이 있으면 여기에 작성)
: 프로그램 실행 시작 시 진입점으로 타입을 지정하기 위한 Swift 언어의 기능
사용자는 탑 레벨의 코드를 작성하는 대신 싱글타입에 @main
속성을 사용할 수 있다.
탑 레벨 코드(Top Level Code)란?
Swift 소스 파일 안에서
탑 레벨 코드
는 0개 이상의 선언이나 정의, 그리고 표현식으로 구성이 된다.
기본적으로 소스파일의 탑 레벨에서 선언된 것들은 같은 모듈 내의 모든 소스파일에서 접근이 가능하다.탑 레벨 코드에는
선언문
과실행문
두 가지가 있다.🌱 선언문 (top-level declarations)
- 모든 Swift Source File에서 사용이 가능하다.
- 변수, 함수 등의 선언문은 전역으로 얼마든지 선언이 가능하며 같은 모듈 내에서 자유롭게 접근 가능하다.
🌱 실행문 (excutable top-level code)
- 선언을 제외한 모든 구문을 의미한다.
- 실행문은 Top-Level Entry Point에서만 사용이 가능하다.
라이브러리와 프레임워크는 프로토콜과 클래스 상속을 통해 앱의 진입점을 커스터마이징할 수 있다.
Swift 5.3 버전 이전에는 @main
대신 @UIApplicationMain
이라는 키워드를 사용하였다.
왜 키워드가
@UIApplicationMain
에서@main
으로 바꼈나?
- 구조체에서
@main
대신@UIApplicationMain
을 쓴다면 class 선언에만 사용이 가능하다는 오류가 발생한다.- 타입 기반의 Swift 코드에서 이상적인 진입점을 알려주며,
main()
함수는 일반 정적 메서드이므로 프로토콜에서 확장 메서드 또는 기본 클래스로 제공 할 수 있다.
→ 자유롭게 Entry Point를 지정할 수 있다.
//애플리케이션이 실행된 직후 사용자의 화면에 보여지기 직전에 호출
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
//애플리케이션이 최초 실행될 때 호출되는 메소드
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
//애플리케이션이 InActive 상태로 전환되기 직전에 호출 task 일시정지, 타이머 비활성화, 일시정지(게임)
func applicationWillResignActive(_ application: UIApplication)
//애플리케이션이 백그라운드 상태로 전환된 직후 호출
func applicationDidEnterBackground(_ application: UIApplication)
//애플리케이션이 Active 상태가 되기 직전, 화면에 보여지기 직전에 호출
func applicationWillEnterForeground(_ application: UIApplication)
//애플리케이션이 Active 상태로 전환된 직후 호출
func applicationDidBecomeActive(_ application: UIApplication)
//애플리케이션이 종료되기 직전에 호출
func applicationWillTerminate(_ application: UIApplication)
✅ 시나리오
- 다른 상태로 넘어가기 전에 앱은 반드시 이 상태를 거친다.
- 전화나 메시지 같은 interrupt 발생 시
- 미리 알림 같은 특정 알림창이 화면을 덮어서 앱이 실질적으로 event를 받지 못하는 상태 등이 여기에 해당
: 제약들간의 우선순위
다수의 뷰들에 여러 제약이 걸려있을 때, 보통은 제약간의 충돌이 일어나지 않게끔 제약들을 설계하는게 일반적이지만,
상황에 따라서는 뷰들의 크기가 유동적으로 변하는 경우가 있는데, 이럴때 어떤 제약들이 서로간에 충돌이 일어나는 경우가 있을 수 있다.
이럴때에는 어떤 제약의 우선순위를 더 우위에 둘것이냐를 결정함으로써 이러한 충돌을 해결할 수 있다.
: 제약을 주고 싶은 item의 anchor property에 접근해서 제약을 정의하는 방식
NSLayoutConstraint.Attribute
(ex. left, right, leading, trailing) 서브 클래스는 추가적으로 Type 검사를 제공하여 유효하지 않은 제약 조건을 생성하는 것을 방지한다.let margins = view.layoutMarginsGuide
subview.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
subview.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
: NSLayoutConstraint
class의 convenience
method를 통해 직접 생성하는 방식
👍🏻 편리한 방식
시각적
으로 정의하기 때문에 표현성이 좋음ex) myView를 superview의 leading과 trailing constraint에 constant 값 0으로 딱 붙이겠다
뷰에 제약사항을 걸어줄 때 공간이 남게 되면 hugging
이 높은 뷰는 자신의 크기를 유지하고, 낮은 뷰는 남은 공간만큼 뷰의 크기가 커지게 된다.
반대로, 공간이 부족하게 되면 resistance
가 높은 뷰는 자신의 크기를 유지하고, 낮은 뷰는 부족한 공간만큼 크기가 작아지게 된다.
UIButton
의 Intrinsic Size
= Title Size
+ margin
Intrinsic Size
를 갖는 것은 아니다.Intrinsic Size
가 적용되는 방식이 다르다.XML
이기 때문에 협업 시에 Merge Conflict가 발생하고, 해결하기 어렵다.아이폰X 등장 이후 4개의 라운드 코너, 노치 디자인으로 인해 TV OS에서만 사용되던 Safearea
가 iOS에 처음 도입되었다.
Portrait 모드
: 상단 statusbar, navigation bar 하단 home indicator를 제외한 모든 부분이 Safearea
이다.
Landscape 모드
: 상하좌우 모두 Safearea
inset margin이 생긴다.
Left Constraint
: 어떤 객체의 왼쪽
Leading Constraint
: 어떤 객체의 앞쪽 가장자리
Left/Right Constraint
는 절대적이며 항상 화면 또는 컨트롤의 왼쪽 / 오른쪽을 참조한다. Leading/Trailing Constraint
는 device locale의 영향을 받는다. (장치별 국가 설정)
따라서 읽기 방향이 오른쪽에서 왼쪽인 locale(예: 히브리어, 아랍어)에서는 leading이 오른쪽이 되고 trailing이 왼쪽이 된다.
👍🏻 Left/Right
보다 Leading/Trailing
을 사용하게 되면 코드를 조금 더 유연하게 작성할 수 있다.
value
type (대입 연산 시 값 자체가 복제되어 할당됨(공유가 불가능))stack
메모리 영역에 저장reference
type (대입 연산 시 레퍼런스가 복사되어 할당됨. (공유 가능))heap
메모리영역에 저장되며 ARC
로 객체의 메모리해제가 관리된다.stack
메모리 영역에 저장하고, 참조되는 값은 heap
메모리 영역에 저장)nil
을 리턴. 컴파일타임에 가능/불가능 여부를 알 수 없음여러 뷰를 가로방향 또는 세로방향으로 배치할 때, 복잡한 constraint 설정 없이, 또는 constraint 만으로 설정하기 어려운 뷰의 배치등을 구현할 때 쓰일 수 있는 뷰.
arrangedSubview
로 하위 뷰들이 관리되며, 이 하위뷰들에 Axis
(가로 세로 방향), Alignment
(세로방향 정렬), Distribution
(가로방향 정렬), Spacing
(하위 뷰들간의 간격) 의 규칙을 적용할 수 있다.
Frame
: SuperView(상위뷰) 좌표시스템 내에서의 view 의 위치(origin) 과 크기(size)
Bounds
: view 자기 자신의 좌표시스템에서의 위치와 크기.
loadView
: 컨트롤러가 관리하는 뷰를 만든다. viewDidLoad
: 컨트롤러의 뷰가 메모리에 올라간 뒤에 호출된다. 리소스를 초기화하거나 초기 화면을 구성하는 용도로 주로 사용된다. 화면이 처음 만들어질 때 한 번만 실행된다. viewWillAppear
: 화면에 뷰가 표시될때마다 호출된다. 이 단계는 뷰는 정의된 바운드를 가지고 있지만 화면회전은 적용되지않는다.viewWillLayoutSubviews
: 뷰컨트롤러에게 그 자식뷰의 레이아웃을 조정하는 것에 대한 것을 알려주기위해 호출된다. 이 메소드는 frame이 바뀔때마다 호출된다.viewDidLayoutSubviews
: 뷰가 그 자식 뷰의 레이아웃에 영향을 준 것을 뷰컨트롤러에게 알려주기 위해 호출된다. 뷰가 그 자식 View의 레이아웃을 바꾸고난 뒤에 추가적인 변경을 하고 싶을때 사용하는 이벤트 함수viewDidAppear
: 뷰가 나타났다는 것을 컨트롤러에게 알리는 역할을 한다. 호출되는 시점으로는 뷰가 화면에 나타난 직후에 실행된다.viewWillDisappear
: 뷰가 사라지기 직전에 호출되는 함수이다. 뷰가 삭제 되려고하고있는 것을 ViewController에게 알린다.viewDidDisappear
: ViewController에게 View가 제거되었음을 알린다. 호출시점은 viewWillDisAppear
다음에 호출된다.고차함수: 매개변수로 함수를 받는 함수
let numbers = [0, 1, 2, 3, 4]
var doubledNumbers = [Int]()
var strings = [String]()
// map
doubledNumbers = numbers.map{ $0 * 2 }
strings = numbers.map{ "\($0)" }
map
과 마찬가지로 새로운 컨테이너를 생성하여 반환한다.let numbers = [0, 1, 2, 3, 4, 5]
let evens = numbers.filter{ $0 % 2 == 0 } // [0, 2, 4]
let odds = numbers.map{ $0 + 3 }.filter{ $0 % 2 != 0 } // [3, 5, 7]
$0
으로 사용된다.let numbers = [1, 2, 3]
var sum = numbers.reduce(10) { $0 + $1 } // 16
dynamic dispatch
보다 static dipatch
를 지향한다.Dispatch란?
Dispatch
: 어떤 메서드를 호출할 것인지를 결정하여, 그것을 실행하는 메커니즘
Static Dispatch (Direct Call)
: "컴파일 타임"에 호출될 함수를 결정하여, 런타임 때 그대로 실행한다 (성능상 이점)Dynamic Dispatch (Indirect Call)
: "런타임"에 호출될 함수를 결정한다 (성능상 손해)...
class는 상속이 가능하기 때문에 하위클래스에서 메서드가 오버라이드 될 가능성이 존재한다. 그렇기 때문에
Dynamic Dispatch
방식으로 메서드를 호출한다.Dynamic Dispatch
는 런타임시에 일어나기 때문에 성능상 손해를 보게 된다.하지만
final
키워드를 붙여주게 되면 상속의 가능성을 배제할 수 있기 때문에Static Dispatch
로 함수를 호출하게 된다. 결과적으로 컴파일 타임에 함수를 호출하게 되고, 성능은 올라가게 된다.또한
private
키워드를 사용할 경우 참조할 수 있는 곳이 현재 파일로 제한 되기 때문에 컴파일러는 자동적으로private
키워드가 참조될 수 있는 곳에서 오버라이딩이 되는지 안되는지 판단 가능하다.쉽게 말해
private
키워드가 있는 객체는final
키워드가 없더라도 오버라이딩이 되지 않고 있다면, 자동적으로Static Dispatch
로 동작한다.
heap
보다는 stack
메모리 할당을 지향한다.✅ Stack
Stack
은 LIFO(Last In First Out)의 단순한 구조로 메모리 할당과 해제가 편리하다. 시간복잡도는 O(1)로 속도가 매우 빠르다. Stack Pointer를 사용하여 할당과 해제를 처리한다.✅ Heap
Heap
은Stack
보다 좀 더 복잡하다. 다이나믹한 할당 방법을 사용하는데,Heap
영역에서 사용하지 않은 블록을 찾아 메모리 할당을 처리한다. 할당을 해제하기 위해, 해당 메모리를 적절한 위치로 다시 삽입한다. 여러 스레드가 동시에Heap
에 접근할 수 있기에 Locking 또는 다른 동기화 메커니즘으로 무결성을 보호해야 한다.[String: Any]
딕셔너리에 String 타입의 키를 사용하는 것은 성능에 좋지 않다. String 타입은 value 타입이지만,
Heap
에 Character 타입으로 문자들을 간접적으로 저장하기 때문이다. 사용하게 되면 heap allocation이 발생한다.이를 해결하는 방법으로는,
struct
을 만들어서 key로 사용하는 것이다. 이때Hashable
이라는 프로토콜을 채택해야 하는데, 커스텀 객체를 collection에 사용하기 위해 필요하다.struct Attributes: Hashable { var color: Color var orientation: Orientation var tail: Tail } let key = Attributes(color: color, orientation: orientation, tail: tail)
struct label {
var text: String
var font: UIFont
func draw() {}
}
String 타입은 Character들을
Heap
에 저장하며, UIFont 등은 클래스로 만들어진 객체이므로 래퍼런스 카운트가 필요하다.
→ uuid를 String 타입이 아닌 UUID로 변경하여 reference counting을 줄인다. UUID는 struct 타입이다. String 대신 enum을 사용하여 해결할 수도 있다.
value 타입의 데이터는 값을 참조하지 않고 복사한다. 하지만 이렇게 매번 복사를 할 경우 값이 변경될 필요가 없음에도 매번 새로운 메모리 공간을 할당하여 복사를 수행하게 된다. 따라서 메모리가 낭비되고, 오버헤드까지 발생한다. 그래서 사용되는 것이 Copy on write
이다.
Copy On Write
는 A라는 변수에 B라는 변수를 할당해주었을 때, 새로 메모리에 할당하는 것이 아니라, B의 메모리를 A가 공유하는 형태로 구성된다. 그러다가 A가 값이 수정될 때 새로 메모리에 할당이 되는 식으로 동작한다.
초기화의 종류에는 크게 2가지로 Designated
, Convenience
가 있다.
: 클래스의 모든 프로퍼티를 초기화
: 보조 이니셜라이저로, 클래스의 원래 이니셜라이저인 init
을 도와주는 역할
convenience init
은 같은 클래스에서 다른 이니셜라이저를 호출해야한다는 규칙이 있다.init
의 파라미터 중 일부를 기본값을 설정해서, convenience init
안에서 init
을 호출하여 초기화 진행할 수 있다.class Person {
var name: String
var age: Int
var gender: String
init(name: String, age: Int, gender: String) {
self.name = name
self.age = age
self.gender = gender
}
convenience init(age: Int, gender: String) {
self.init(name: "zedd", age: age, gender: gender)
}
}
Swift
는 Type에 민감한 언어기 때문에 특정하지 않은 타입에 대해 동작하도록 특별한 타입 두가지를 제공한다.
원래는 타입 캐스팅을 수행할 때, 일반적으로 상속 관계에 있는 클래스끼리만 캐스팅이 가능하지만, Any
, AnyObject
타입을 사용할 경우, 상속 관계에 있지 않아도 타입 캐스팅이 가능하다.
: 함수타입을 포함하여 모든 타입의 인스턴스를 나타낼 수 있다.
value
타입(구조체, 열거형), reference
타입(클래스, 클로저)이건 상관 없이 저장이 가능하다. 한마디로, Any
는 모든 타입을 포함할 수 있는 범용 타입!
: 모든 클래스 타입의 인스턴스를 나타낼 수 있는 프로토콜
AnyObject
로 선언 시, "클래스 타입"만 저장할 수 있다.
(따라서 클래스 타입이 아닌 구조체, 열거형, Reference Type인 클로저는 AnyObject
에 해당하지 않는다)
매번 타입 체크 및 형변환을 해야하기때문에 필요에 의한 것이 아니라면 사용하지 않는 것이 좋다. 그럼에도 불구하고 사용한다면, switch문을 활용하여 각 타입마다 as
를 통해 캐스팅이 성공하면 case문이 실행하게 만들어준다.
var name: Any = "Somang"
if var name = name as? String {
name.append("다운캐스팅")
}
: 값이 있을 수도 없을 수도 있는 enum 타입.
(some
: 값이 있고 래핑된 상태, none
: 값이 없는 nil 상태)
?
를 붙여 Optional
을 사용한다.var test: Int?
Optional 변수의 값 가져오는 방법
✅ Forced Unwrapping (강제 언래핑)
: 옵셔널에 값이 있다고 가정하고 값에 바로 접근할 수 있도록 강제하는 방법
느낌표(!)
키워드를 붙여 사용한다.- 그러나 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 런타임 에러가 발생한다. 느낌표를 사용하여 강제 언래핑 하기 전에 항상 옵셔널 값이 nil 이 아니라는 것을 확실히 해야 한다.
var optionalEmail: String? print(optionalEmail!)
✅ Optional Binding
: 옵셔널의 값이 존재하는 지를 검사한 뒤, 존재한다면 그 값을 다른 변수에 대입시켜준다.
if let
if var
구문을 사용한다.- 옵셔널을 바인딩할 때
,
를 사용해서 조건도 함께 지정할 수 있다.var optionalAge: Int? = 22 if let age = optionalAge, age >= 20 { print(age) }
✅ Optional Chaining
: 옵셔널의 속성에 접근할 때, 옵셔널 바인딩 과정을
?
키워드로 줄여주는 역할을 한다.let isEmptyArray = array?.isEmpty == true
Struct: 인스턴스의 값(property)
을 저장하거나 기능(method)
를 제공하고, 이를 캡슐화할 수 있도록 Swift가 제공하는 타입
struct
키워드로 정의한다.struct [구조체 이름] {
[프로퍼티와 메서드들]
}
value type
: 주소값을 넘겨주는 reference type
과는 다르게 현재 값을 복사하는 값의 복사가 일어난다.클래스
, 구조체
, 열거형
에서 정의해서 사용하는 스크립트subscript(index: Int) -> Int {
get {
// 적절한 반환 값
}
set(newValue) {
// 적절한 set 액션
}
}
subscript(index: Int) -> Int {
// 적절한 반환 값
}
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
// TimesTable구조체의 multiplier를 3으로 설정
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// "six times three is 18" 출력
다른 언어들에서는 Int 자료형 index로 문자열에 접근이 가능하지만,
Swift에서는 String에서 Int 형식으로 인덱스 참조가 불가능하다. Int로 서브스크립트를 사용할 수 없고 String.index
로 사용해야 한다.
Swift에서 Character는 1개 이상의 Unicode Scalar로 이루어져 있어 크기가 가변적이기 때문이다.
ARC (Automatic Reference Counting)
란, 자동 레퍼런스 카운팅으로서 자동으로 메모리를 관리해주는 방식을 말한다. 참조 카운팅이 0이 될 때만 메모리에서 해제한다는 뜻이다.
클래스의 새로운 인스턴스를 만들 때 ARC
는 인스턴스의 정보를 저장하기 위해 메모리를 할당한다. 또한 ARC
는 인스턴스가 더 이상 사용되지 (참조 카운팅 0) 않는다고 판단하면 메모리를 해제한다.
레퍼런스 프로퍼티에 인스턴스를 할당하면 ARC
는 참조되는 프로퍼티의 개수를 카운트하여 참조하는 모든 변수가 인스턴스를 해제하기 전에 ARC
는 인스턴스를 메모리에서 해제하지 않는다.
ARC
는 retain, release의 호출을 통해 메모리 관리를 수행하는 것이고,Reference Counting(Retain Counting)
이라고 한다.🎈 retain
객체의 reference count(retain count)를 증가시킨다.
객체가 메모리에서 해제되지 않도록 이 함수를 호출하여 카운트를 증가시키는 것🎈 release
객체의 reference count(retain count)를 감소시킨다.
객체를 더이상 필요로 하지 않을 때 이 함수를 호출하여 카운트를 감소시키는 것
1) compile time
에는 코드를 분석하고 예측하여 적절한 위치에 retain, release를 삽입해준다.
2) run time
에 삽입된 코드가 실행되면서 retain, release에 의해 reference count를 증감하고,
count가 0이 되었을 때 메모리에서 제거한다.!
strong
은 객체를 소유하여 레퍼런스 카운트가 증가하는 프로퍼티이다. 값 지정 시점에 retain이 되고 참조가 종료되는 시점에 release가 된다.weak
은 객체를 소유하지 않고 주소값만 가지고 있는 포인터 개념이다. 자신이 참조는 하지만, weak
메모리를 해제할 수 있는 권한은 다른 클래스에 있다. 값 지정 시 retain이 발생하지 않는다. 따라서 릴리즈도 발생하지 않는다. 그래서 언제 어떻게 메모리가 해제될 지 알 수가 없다. 다만 메모리가 해제될 경우 자동으로 레퍼런스가 nil로 초기화를 해 준다. 그렇기 때문에 weak
속성을 사용하는 객체는 항상 옵셔널타입이여야 한다.nil
값이 할당된다. 대표적으로 retain cycle에 의해 메모리가 누수되는 문제를 막기 위해 사용되며, iOS 프레임워크에서 대표적인 예로는 Delegate패턴이 있다.weak
와 매우 비슷한 역할을 한다. 차이점으로는 Unowned
로 선언된 변수는 nil이 될 수가 없다. 그러므로 Unowned
변수는 옵셔널로 선언되어서는 안된다. 해제된 메모리 영역을 접근하지 않는다는 확실한 경우에만 사용해야한다.[weak self]
의 역할로는 ARC
가 프로퍼티의 갯수를 카운팅 하지 않도록 만들며, 카운팅이 되지 않기에 순환참조가 일어나지 않도록 만드는 역할을 한다. 그 이유로는 weak
참조는 ARC
에 의해 참조되는 인스턴스가 메모리에서 해제 될 때 프로퍼티의 값을 nil
로 만들기 때문에 순환 참조가 발생하지 않는다.
(* 순환 참조: 두 가지 이상의 객체가 서로에 대한 Strong Reference(강한 참조)
상태를 가지고 있을 때 발생하며, 순환 참조가 발생하게 되면 서로에 대한 참조가 해제되지 않기 때문에 메모리에서 유지되며 이로 인해 메모리 릭이 발생하게 된다.)
두 개 이상의 객체가 서로 상호 소유를 할 경우에 강한 순환 참조가 발생한다.
하나의 인스턴스를 참조하는 변수가 nil
이 되어서 메모리 해제가 되어야하는 시점에서도 인스턴스를 참조하고 있는 인스턴스가 있어서 reference count
는 여전히 1
이기 때문에 메모리 해제가 안되어서 메모리 릭이 발생한다.
class ClassA {
var objB: ClassB!
deinit { print("A 객체 해제") }
}
class ClassB {
var objA: ClassA!
deinit { print("B 객체 해제") }
}
var a: ClassA! = ClassA() // -> A 객체 R.C = 1
var b: ClassB! = ClassB() // -> B 객체 R.C = 1
// 서로 객체 소유
**a.objB = b** // -> B 객체 R.C = 2
**b.objA = a** // -> A 객체 R.C = 2
a = nil // -> A 객체 R.C = 1
b = nil // -> B 객체 R.C = 1
🌱 강한 순환 참조를 방지하는 방법
:
weak
,unowned
참조를 사용!
(*unowned
참조는 non-optional 이기 때문에 런타임 에러 발생 가능)
: 하나의 프로세스 내에서 실행되는 작업흐름의 단위.
프로세스가 시작하는 동시에 동작하는 스레드를 메인 스레드
라 하고, 이외의 추가로 생성되는 스레드를 서브 스레드
라 부른다.
: 여러 개의 스레드가 동시에 진행되는 것.
하나의 프로세스 내에서 여러 개의 스레드가 존재하고, 스레드들이 프로세스의 자원을 공유하되, 실행은 독립적으로 이루어지는 구조
: 주어진 명령을 차례대로 처리하되 하나의 업무가 완료될 때 까지는 다른 업무로 넘어가지 않는 방식.
중간에 대기하는 시간때문에 효율은 떨어지지만, 일관된 업무 보장과 동시다발적 업무가 발생하지 않으므로 대응이 불필요하여 업무구성이 단순화됨
: 주어진 명령을 차례대로 처리하되, 시간이 걸리는 업무는 진행해둔 채 기다리는 동안 다른 업무를 처리하는 방식.
일관적인 업무 흐름이 깨지고, 응답에 대한 대응이 필요하다.
: Grand Central Dispatch(GCD)
는 멀티코어와 멀티 프로세싱 환경에서 최적화된 프로그래밍을 할 수 있도록 애플이 지원하는 저수준 API.
기본적으로 스레드 풀의 관리를 프로그래머가 아닌 운영체제에서 관리하기 때문에 프로그래머가 태스크(작업)을 비동기적으로 쉽게 사용할 수 있다. 프로그래머가 실행할 태스크(작업)을 생성하고 Dispatch Queue
에 추가하면 GCD
는 태스크(작업)에 맞는 스레드를 자동으로 생성해서 실행하고 작업이 종료되면 해당 스레드를 제거한다.
Dispatch Queue (디스패치 대기열)
- GCD 기술의 일부
- 작업을 연속적 혹은 동시에 진행하기는 하지만, 언제나 먼저 들어오면 먼저 나가는 순서로 실행된다.
Serial Dispatch Queue
: 한 번에 하나의 작업만을 실행하며, 해당 작업이 대기열에서 제외되고 새로운 작업이 시작되기 전까지 기다린다.
Concurrent Dispatch Queue
: 이미 시작된 작업이 완료될 때까지 기다리지 않고 가능한 많은 작업을 진행한다.
: 어떤 하나의 작업을 나타낸다. NSOperation은 모델링 상태, 우선순위, 의존성 그리고 관리를 지원하는 유용하고 Thread safe한 추상 클래스이다.
예를 들어 네트워크 요청, 이미지 리사이즈, 텍스트 처리, 또는 기타 다양한 반복처리 등 오래 걸리는 작업을 처리해야하는 NSOperation
이 있다고 할 때, 이 특정 작업이 담겨있는 객체는 감독 없이 많은 일을 할 수 없다. 이러한 작업을 진행하는 감독을 NSOperationQueue
가 담당한다.
API란?
Application Programming Interface(API)
는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의한다.웹 API는 클라이언트와 웹 리소스 사이의 게이트웨이라고 생각할 수 있다.
REST(Representational State Transfer)
: API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처.
RESTful API
: 두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스
RESTful 웹 서비스
: REST 아키텍처를 구현하는 웹 서비스
RESTful API의 기본 기능은 인터넷 브라우징과 동일하다!
모든 REST API 호출에 대한 일반 단계
- 클라이언트가 서버에 요청을 전송한다. 클라이언트가 API 문서에 따라 서버가 이해하는 방식으로 요청 형식을 지정한다.
- 서버가 클라이언트를 인증하고 해당 요청을 수행할 수 있는 권한이 클라이언트에 있는지 확인한다.
- 서버가 요청을 수신하고 내부적으로 처리한다.
- 서버가 클라이언트에 응답을 반환한다. 응답에는 요청이 성공했는지 여부를 클라이언트에 알려주는 정보가 포함된다. 응답에는 클라이언트가 요청한 모든 정보도 포함된다.
JavaScript
와 PHP
등이 있다. (JavaScript
는 클라이언트 측 스크립트이고, PHP
는 서버 측 스크립트)PHP
프로그램은 내려 받기 전에 웹 서버에서 실행된다.PHP
가 포함되지않은 HTML
코드이다.🌱 SQL 데이터베이스 사용이 더 좋을 때
- 관계를 맺고 있는 데이터가 자주 변경되는 애플리케이션의 경우
(NoSQL에서는 여러 컬렉션을 모두 수정해야 하기 때문에 비효율적)- 변경될 여지가 없고, 명확한 스키마가 사용자와 데이터에게 중요한 경우
🌱 NoSQL 데이터베이스 사용이 더 좋을 때
- 정확한 데이터 구조를 알 수 없거나 변경/확장 될 수 있는 경우
- 읽기를 자주 하지만, 데이터 변경은 자주 없는 경우
- 데이터베이스를 수평으로 확장해야 하는 경우 (막대한 양의 데이터를 다뤄야 하는 경우)