튜토리얼을 진행하며 생긴 궁금증, 깨달음, 알게된 팁들
SwiftUI Tutorial: 1.2. Building Lists and Navigation
https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation
Section 1. Create a Landmark Model
Drag landmarkData.json in the downloaded files’ Resources folder into your project’s navigation pane; in the dialog that appears, select “Copy items if needed” and the Landmarks target, and then click Finish.
struct Landmark: Hashable, Codable {
var id: Int
var name: String
var park: String
var state: String
var description: String
}
이 코드는 Landmark라는 이름의 구조체를 정의하고 있습니다.
이 구조체는 id, name, park, state, description의 5개 저장 프로퍼티를 가지며, Hashable과 Codable 프로토콜을 준수합니다.
이 구조체는 데이터 모델의 일부로 사용될 수 있으며, 앱에서 랜드마크를 나타내는 데 사용될 수 있습니다. 이 구조체의 Hashable 프로토콜 준수는, 구조체를 해시 테이블에서 사용할 수 있게 하여 더 효율적인 검색이 가능하도록 합니다. 또한 Codable 프로토콜 준수는, 구조체를 JSON 형식으로 직렬화하거나 역직렬화할 수 있도록 합니다.
Hashable과 Codable은 Swift에서 이미 정의되어 있는 프로토콜입니다.
Hashable 프로토콜은 객체를 해시 가능한 형태로 변환하는 데 사용됩니다. 이를 통해 객체를 해시 테이블에 저장하거나 비교하는 등의 연산이 가능하게 됩니다.
Codable 프로토콜은 객체를 다양한 데이터 형식으로 인코딩하거나 디코딩하는 데 사용됩니다. 예를 들어 JSON, XML, Property List 등 다양한 데이터 형식으로 인코딩하거나 디코딩할 수 있습니다. 이를 통해 객체를 파일에 저장하거나 네트워크를 통해 전송하는 등의 용도로 사용됩니다.
이러한 프로토콜들은 Swift에서 제공하는 다양한 기능들을 사용할 수 있도록 만들어진 것입니다. 이외에도 Equatable, Comparable 등 다양한 프로토콜들이 존재합니다.
정확히 말하면, Hashable과 Codable은 프로토콜이 아니라 프로토콜 요구사항입니다.
Hashable과 Codable은 각각 객체가 해시 가능하도록 하고, 데이터를 인코딩하거나 디코딩할 수 있도록 하는 데 필요한 요구사항들을 가지고 있습니다. 따라서 우리는 객체가 Hashable하거나 Codable하다고 말할 수 있습니다.
위의 예제에서 Landmark는 Hashable과 Codable 프로토콜 요구사항을 충족하는 타입으로 선언되었습니다. 즉, Landmark 타입은 객체가 해시 가능하며 데이터를 인코딩하거나 디코딩할 수 있다는 것을 보장합니다.
프로토콜은 추상화된 인터페이스이며, 구체적인 구현 내용은 제공하지 않습니다. 즉, 프로토콜 자체로는 아무런 기능을 수행하지 않습니다.
프로토콜 요구사항은 해당 프로토콜을 채택한 구조체, 클래스, 열거형 등의 타입이 구현해야 하는 메서드, 프로퍼티, 이니셜라이저 등을 의미합니다. 예를 들어, Hashable 프로토콜을 채택한 타입은 hashValue 프로퍼티를 구현해야 합니다. Codable 프로토콜을 채택한 타입은 인코딩과 디코딩을 위한 특별한 이니셜라이저를 구현해야 합니다.
따라서 Hashable과 Codable은 추상화된 인터페이스인 프로토콜이 아니라, 이들 프로토콜이 요구하는 구체적인 동작(프로토콜 요구사항)을 구현하는 것이 중요합니다.
SwiftUI에서 콜론(:)은 타입 어노테이션(type annotation)을 정의할 때 사용됩니다.
타입 어노테이션은 변수, 상수, 함수, 매개변수 등에 대한 타입 정보를 제공합니다.
즉, 콜론을 사용하여 변수나 상수의 타입을 지정하거나 함수나 메서드의 매개변수와 반환 타입을 지정할 수 있습니다.
var count: Int = 0
저장 프로퍼티(Stored Property)는 클래스 또는 구조체 내에 변수나 상수를 선언하여 값을 저장하는 프로퍼티를 말합니다. 저장 프로퍼티는 선언된 변수나 상수의 형식에 따라 값을 저장합니다. 예를 들어, 정수 값을 저장하는 Int 타입의 저장 프로퍼티는 다음과 같이 선언할 수 있습니다.Method (함수)에서 매개변수와 반환 타입 지정
func greet(name: String) -> String {
return "Hello, \(name)!"
}
위의 코드에서 name 매개변수는 String 타입이며, greet 함수는 String 타입의 값을 반환합니다.
프로토콜 선언
class나 struct에서 프로토콜을 준수하도록 선언할 때
struct Landmark: Hashable, Codable {
var id: Int
var name: String
var park: String
var state: String
var description: String
}
private var imageName: String
var image: Image {
Image(imageName)
}
해당 코드는 imageName이라는 String 타입의 private 저장 프로퍼티를 가진 Landmark 구조체의 멤버입니다.
imageName은 이미지 파일의 이름을 저장하며, Image 타입의 연산 프로퍼티인 image은 imageName을 이용하여 해당 이미지를 로드한 후 반환합니다.
이 때, imageName이 private으로 선언된 이유는 해당 프로퍼티를 외부에서 직접적으로 수정하지 못하도록 하기 위함입니다. 대신, Landmark 구조체 내부에서만 수정할 수 있도록 하여 정보 은닉을 유지할 수 있습니다.
사용자는 이미지의 이름 등의 세부적인 구현 사항에 대해서는 신경쓰지 않고, 오직 이미지 자체만을 사용하므로, imageName 프로퍼티를 외부에서 접근할 수 없도록 private으로 선언하는 것입니다. 이렇게 하면 구조체 외부에서 imageName을 변경하거나 사용자에게 노출되는 것을 방지할 수 있습니다.
private를 쓰는 이유가 정확히 이해가 안된다.
private var coordinates: Coordinates
struct Landmark: Hashable, Codable {
...
//좌표
private var coordinates: Coordinates
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
위 코드는 Landmark 구조체에 Coordinates라는 중첩 타입을 선언하고, coordinates라는 Coordinates 타입의 저장 프로퍼티를 추가하는 코드입니다. 이때 Coordinates 타입은 Hashable과 Codable 프로토콜을 따르도록 선언되어 있습니다.
구조체 Landmark 내에 Coordinates라는 중첩 타입
Landmark 구조체 안에서만 사용되는 latitude와 longitude라는 속성들을 묶어서 하나의 새로운 타입으로 정의한 것입니다.
Landmark 구조체 안에서만 Coordinates 타입이 사용되도록 제한하는 것.
코드의 의도를 명확하게 표현할 수 있고, 코드의 가독성과 유지보수성을 높일 수 있다.
이후 단계에서 coordinates 프로퍼티를 기반으로 한 공개된 계산 프로퍼티를 생성할 것이기 때문에, 외부에서 직접적으로 접근하지 않아도 된다는 의미입니다.
import Foundation
import SwiftUI
import CoreLocation
위 코드에서 import 키워드를 통해 다음과 같은 모듈을 불러왔습니다.
모듈(Module)은 Swift에서 코드의 묶음 단위이며, 함수, 타입, 프로토콜, 클래스, 구조체 등의 코드를 포함할 수 있습니다. 모듈은 Swift 컴파일러가 코드를 처리하는 단위이기도 합니다.
Swift에서 모듈은 프레임워크(Framework)나 라이브러리(Library)와 같은 형태로 존재할 수 있으며, 다른 모듈에서 해당 모듈의 코드를 재사용할 수 있습니다. 또한, Swift는 기본 모듈인 Swift가 내장되어 있어, 별도의 import 없이도 기본적인 Swift 타입 및 함수를 사용할 수 있습니다.
프레임워크와 라이브러리는 코드를 재사용하기 위해 만들어진 소프트웨어 모듈입니다.
재사용 가능한 단위로 패키징하는 방법이다. 더 높은 생산성과 유지보수성을 제공한다.
프레임워크
특정 문제를 해결하기 위한 더 큰 개념적 프레임워크의 일부로 사용되는 API의 모음
특정 기능을 수행하기 위해 필요한 다양한 기능들이 미리 구현되어 있는 소프트웨어 패키지
프레임워크는 보통 개발자가 만들어야 할 코드의 양을 줄여줍니다.
개발자는 프레임워크에서 제공하는 API를 사용하여 자신이 원하는 기능을 구현하면 됩니다.
대표적인 예로는 iOS에서 UIKit 프레임워크가 있습니다.
라이브러리
개발자가 필요할 때 필요한 기능을 가져올 수 있는 단일 코드 모음
일련의 함수, 클래스, 구조체, 인터페이스 등으로 구성된 코드 모음입니다.
라이브러리를 사용하면 해당 코드를 직접 작성하지 않고도 다양한 기능을 구현할 수 있습니다.
개발자는 라이브러리에서 제공하는 함수 등을 호출하여 자신이 원하는 기능을 수행할 수 있습니다.
대표적인 예로는 Swift에서 Foundation 라이브러리가 있습니다.
라이브러리는 보통 프레임워크보다 더 작고 구체적인 용도를 가지며, 이에 따라 더 큰 자유도를 제공합니다.
예를 들어, Swift에서 문자열 처리 기능을 구현하기 위해 Foundation 프레임워크에서 제공하는 문자열 관련 API 뿐만 아니라, 추가적으로 정규표현식 라이브러리 등을 사용할 수 있습니다.
따라서, 프레임워크는 애플리케이션의 구조를 구성하고, 라이브러리는 구조의 구성요소를 구현하는 데 사용됩니다. 또한, 프레임워크는 라이브러리를 포함하는 개념으로 볼 수도 있습니다.
프로그램끼리 뭔가를 주고받는 규약
다른 프로그램에서 기능, 데이터를 가져와서 사용
ex. 회원가입 정보를 서버에 저장하고, 로그인 정보를 띄우기 위해 서버에서 가져와서 사용
ex. 페이스북에서 제공하는 페이스북 로그인 기능을 가져와서 사용
API는 Application Programming Interface의 약자로, 프로그램끼리 상호작용하는데 사용되는 인터페이스입니다. 쉽게 말해, 어떤 프로그램에서 제공하는 기능을 다른 프로그램에서도 사용할 수 있도록 하는 규약이라고 할 수 있습니다. 이 인터페이스는 일반적으로 웹 서비스, 라이브러리 또는 프레임워크 형태로 제공됩니다.
웹 API의 경우, HTTP 프로토콜을 사용하여 클라이언트 애플리케이션이 서버에 요청하고, 서버는 이에 대한 응답을 제공합니다. 이를 통해 클라이언트 애플리케이션은 서버에서 제공하는 데이터나 기능을 사용할 수 있습니다.
앱에서 서버와 통신하는 경우, 서버에서 제공하는 API를 호출하여 데이터를 요청하거나 기능을 수행할 수 있습니다. 이를 통해 앱은 서버에서 제공하는 기능을 사용하거나 데이터를 가져와서 화면에 표시할 수 있습니다.
예를 들어, 특정 앱에서 사용자가 회원가입을 하면, 해당 회원 정보를 서버에 저장하고, 이후 로그인 등에 활용해야 할 것입니다. 이를 위해서는 앱과 서버 간에 통신이 필요하며, 이때 사용되는 것이 API입니다. 앱은 서버로부터 회원 정보를 저장하기 위한 API를 호출하고, 서버는 해당 요청을 받아서 회원 정보를 저장하고, 처리 결과를 응답합니다. 이렇게 앱과 서버 간에 API를 이용하여 데이터를 주고받으면, 앱이나 서버를 업데이트해도 데이터 교환에 문제가 없습니다. 이처럼 API는 앱과 서버 간에 원활한 데이터 교환을 위해 매우 중요한 역할을 합니다.
API는 프로그램 간의 상호작용에 있어 중요한 역할을 합니다. 예를 들어, 페이스북에서 제공하는 API를 사용하여 다른 앱에서도 페이스북의 로그인, 친구 목록 조회, 포스팅 등의 기능을 사용할 수 있습니다. 이렇게 API를 사용하면 기존에 이미 개발되어 있는 기능을 재사용하여 개발 시간과 비용을 절감할 수 있습니다.
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
해당 코드에서 locationCoordinate는 MapKit 프레임워크와 상호작용하기 위한 속성입니다.
coordinates 프로퍼티의 값을 CLLocationCoordinate2D 타입으로 변환하여 반환합니다.
이렇게 하면 이 구조체를 MapKit 프레임워크와 함께 사용할 때 편리합니다.
CLLocationCoordinate2D는 CoreLocation 프레임워크에서 사용되는 구조체로, 지리 좌표 시스템에서 위도(latitude)와 경도(longitude) 값을 나타냅니다. 이 구조체를 사용하여 지도 앱에서 특정 위치를 표시하거나 특정 위치를 검색하는 등의 작업을 수행할 수 있습니다. 이 구조체의 latitude 및 longitude 속성은 Double 값입니다.
"편리하다"는 기준은 맥락에 따라 다를 수 있습니다. 일반적으로 프로그래머들은 코드를 작성할 때 생산성과 가독성, 유지보수성 등 여러 가지 요소를 고려하여 코드를 작성합니다. 따라서, 코드를 작성하는 목적과 상황에 따라 "편리하다"는 기준이 달라질 수 있습니다.
예를 들어, 이전에 말씀드린 MapKit을 사용하는 경우, locationCoordinate 프로퍼티를 사용하면 지도 위에 표시할 위치를 쉽게 계산할 수 있습니다. 이는 MapKit에서 제공하는 함수와 데이터 타입들과 함께 사용될 때 매우 편리하다고 할 수 있습니다.
하지만 만약 일반적인 앱에서 locationCoordinate와 같은 프로퍼티가 필요하지 않다면, 이는 불필요한 코드가 될 수 있으며, 오히려 코드를 복잡하게 만들 수 있습니다. 따라서 "편리하다"는 기준은 맥락에 따라 다르며, 코드 작성 목적과 상황에 따라 달라질 수 있습니다.
import Foundation
var landmarks: [Landmark] = load("landmarkData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
위 코드는 JSON 파일에서 디코딩하여 모델 객체로 변환하는 데 사용됩니다.
제네릭 함수 load는 Decodable 프로토콜을 준수하는 모든 타입에 대해 작동하며, JSON 데이터를 읽어와서 해당 타입의 인스턴스를 반환합니다.
함수의 첫 번째 매개변수 filename은 로드할 JSON 파일의 이름을 나타냅니다.
함수는 먼저 filename을 가진 파일 URL을 찾은 후, 파일이 존재하지 않으면 오류를 내보내고 종료합니다. 그렇지 않으면 파일을 읽고 Data 객체에 저장합니다.
그 다음, JSON 디코딩을 위해 JSONDecoder 인스턴스를 만들고, decode 메서드를 사용하여 Data 객체를 해당 타입의 인스턴스로 디코딩합니다. 만약 디코딩 과정에서 오류가 발생하면, 오류를 내보내고 종료합니다.
이 코드는 주어진 이름으로부터 앱의 메인 번들(main bundle)에서 JSON 데이터를 가져오는 load(_:) 메서드를 생성합니다.
load 메서드는 Decodable 프로토콜을 준수하는 반환 타입에 의존합니다. Decodable 프로토콜은 Codable 프로토콜의 구성 요소 중 하나입니다. 이는 JSON 데이터를 Swift 객체로 디코딩하기 위한 인코딩 및 디코딩 프로토콜입니다. 따라서 반환 타입은 JSONDecoder를 사용하여 JSON 데이터를 디코딩해야 합니다. 만약 디코딩에 실패하면, 앱이 비정상적으로 종료됩니다(fatalError).
func load<T: Decodable>(_ filename: String) -> T
이 코드는 Generics(제네릭)을 사용하여, filename으로 전달된 파일 이름을 사용하여 Bundle에서 파일을 로드하고, Decodable 프로토콜을 준수하는 T 타입의 객체를 반환합니다. (-> 로 반환타입 정의함)
여기서 T는 타입 매개변수(type parameter)입니다. 이 함수는 T 타입의 객체를 반환하며, 호출 시점에 T가 어떤 타입인지에 따라 반환되는 객체의 타입이 달라집니다.
예를 들어, let landmarks: [Landmark] = load("landmarkData.json")와 같이 호출하면, T는 [Landmark] 타입이 되며, 반환되는 객체 역시 [Landmark] 배열이 됩니다.
타입 매개변수는 제네릭 함수나 제네릭 타입에서 사용되는, 실제 타입을 대신하여 사용되는 변수입니다. 제네릭 함수나 제네릭 타입을 정의할 때, 실제로 사용될 타입이 정해지지 않는데, 이때 타입 매개변수를 사용하여 추상화된 타입을 나타냅니다.
타입 매개변수는 대개 <T>와 같은 형태로 선언되며, 여기서 T는 임의의 이름으로 지정할 수 있습니다. 함수나 타입을 사용할 때, 타입 매개변수를 실제 타입으로 대체하여 사용합니다. 이렇게 함으로써, 제네릭 함수나 제네릭 타입은 더욱 유연하게 사용될 수 있습니다.
함수 또는 메서드의 매개변수에서 "argument label"을 생략하도록 허용하는 기능
예를 들어, load 함수의 경우, T의 타입 파라미터가 추론될 수 있는 경우, 함수를 호출할 때 T에 대해 타입을 명시하지 않고 _를 사용할 수 있습니다. 이렇게 하면 컴파일러가 T의 타입을 자동으로 추론하고 반환 값을 적절한 타입으로 캐스팅합니다.
즉, load("landmarkData.json")을 호출하면 load 함수에서 제네릭 타입 T가 Array로 추론되므로 반환값은 Array가 됩니다. _는 제네릭 타입 T의 타입 파라미터에 대한 축약형입니다.
하지만 함수 또는 메서드의 매개변수에서 _를 사용하면 argument label이 생략되므로 해당 매개변수에 대한 argument label을 지정할 수 없습니다.
반면, 제네릭 함수나 메서드에서는 argument label 자체가 필요 없으므로, 매개변수 이름과 매개변수 타입 사이에 _를 사용하여 매개변수 이름을 생략할 수 있습니다. 예를 들어, 다음과 같은 swapTwoValues(::) 함수를 보실 수 있습니다. 이 함수는 T라는 제네릭 타입을 사용하며, a와 b 두 매개변수는 각각의 argument label을 가지지 않고, _로 대체됩니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
func greet(person name: String) {
print("Hello, \(name)!")
}
greet(person: "John")
여기서 "person"은 매개변수의 이름이며, 함수 호출 시 인자로 전달되는 값을 설명하는 라벨 역할을 합니다. 함수를 호출할 때는 함수 이름 뒤에 매개변수 이름과 함께 값을 전달합니다. 예를 들어 greet(person: "John")는 name 매개변수에 "John"을 전달합니다.
매개변수 이름(parameter name)과 argument label(전달인자 레이블)은 서로 다른 것입니다.
매개변수 이름은 함수 내에서 해당 매개변수를 사용할 때 사용되는 이름입니다. 함수 호출 시 전달하는 인자를 매개변수 이름으로 받아 처리합니다.
argument label(전달인자 레이블)은 함수 호출 시 사용되는 레이블입니다. 함수 호출 시 전달하는 인자에 대한 설명을 제공하는 역할을 하며, 함수 호출 시 외부에서 사용됩니다.
Swift에서는 함수 정의 시, 매개변수 이름 앞에 argument label을 명시하여 외부에서 사용되는 이름을 지정할 수 있습니다. 이를 통해 함수를 사용하는 코드의 가독성을 높이고, 함수 호출 시 전달하는 인자의 의미를 명확히 할 수 있습니다.
아래는 매개변수 이름과 argument label이 모두 명시된 함수의 예시입니다.
func printName(for name: String) {
print("My name is \(name)")
}
printName(for: "John") // "My name is John" 출력
여기서 for가 argument label이 되며, 함수 내부에서 사용되는 매개변수 이름은 name입니다.
제네릭 타입(Generic type)은 타입에 대한 추상화(abstraction)를 제공하는 타입입니다. 제네릭 타입을 사용하면 함수나 클래스, 구조체 등을 정의할 때 특정한 타입 대신 타입 매개변수(Type parameter)를 사용하여 일반화된 코드를 작성할 수 있습니다.
타입 매개변수는 일반적으로 대문자로 표기하며, 각 매개변수는 "<>"로 둘러싸인 형태로 작성합니다. 예를 들어, Array 타입은 제네릭 타입으로 구현되어 있으며, 다음과 같이 작성됩니다. (아래와 같이 Array 타입을 선언하면서 제네릭을 사용할 수 있습니다.)
struct Array<Element> { ... }
여기서 "Element"는 타입 매개변수이며, 이를 사용하여 배열(Array)의 요소 타입을 지정할 수 있습니다.
Array 구조체를 선언할 때 구체적인 타입으로 바뀝니다. 예를 들어, Array<Int>를 선언하면 Element가 Int로 바뀌어 Int 타입의 배열을 나타냅니다. 예를 들어, Int 타입의 배열은 다음과 같이 작성할 수 있습니다.
let numbers: Array<Int> = [1, 2, 3, 4, 5]
제네릭을 사용하면 다양한 타입에 대해 하나의 함수나 타입을 정의할 수 있습니다. 함수를 선언할 때도 제네릭을 사용하여 타입 안정성을 유지할 수 있습니다.
예를 들어, 제네릭을 사용하지 않는 함수를 선언하면 다음과 같습니다.
func swapTwoInts(a: inout Int, b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
이 함수는 두 개의 Int 값을 서로 바꿉니다. 하지만 이 함수는 Int 타입에 한정되어 있기 때문에, Double이나 String 등의 다른 타입에 대해서는 사용할 수 없습니다. 이 때 제네릭을 사용하여 함수를 선언하면 다양한 타입에 대해 사용할 수 있습니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
<T>는 swapTwoValues 함수의 제네릭 타입 매개변수입니다. T는 swapTwoValues 함수를 호출할 때 실제 타입으로 대체됩니다. 이제 swapTwoValues 함수는 다양한 타입에 대해 사용할 수 있습니다. 예를 들어, 다음과 같이 Int, Double, String 값에 대해서 사용할 수 있습니다.
var a = 10
var b = 20
swapTwoValues(&a, &b)
print(a, b) // 20 10
var c = 3.14
var d = 2.71
swapTwoValues(&c, &d)
print(c, d) // 2.71 3.14
var e = "Hello"
var f = "World"
swapTwoValues(&e, &f)
print(e, f) // World Hello
제네릭 타입은 코드의 재사용성과 유연성을 높이는 데에 큰 도움을 줍니다. 일반적으로, 제네릭 타입을 사용하면 타입 안정성을 보장할 수 있으며, 코드 중복을 방지하고, 적은 코드로 많은 기능을 제공할 수 있습니다.
타입에 대한 추상화는 어떤 데이터 타입을 사용하는지에 대한 구체적인 정보를 숨기고, 해당 타입이 가지는 공통적인 특징과 기능에 초점을 맞춰서 코드를 작성하는 것입니다. 이를 통해 코드를 보다 추상적으로 작성하여 재사용성과 유연성을 높일 수 있습니다.
예를 들어, Swift에서 제공하는 Array 타입은 다양한 데이터 타입을 담을 수 있습니다. 만약 특정한 타입의 Array를 다루는 코드를 작성할 경우, 이를 일일히 모든 타입에 대해 작성하는 것은 번거로울 뿐 아니라 유지보수도 어렵습니다. 따라서 Array 타입을 추상화하여 Element라는 제네릭 타입 파라미터로 대체할 수 있습니다. 이렇게 추상화된 Array 타입을 사용하면 모든 타입에 대해 작성하지 않아도 됩니다.
func printElements<T>(of array: [T]) {
for element in array {
print(element)
}
}
위 코드에서 printElements 함수는 Array 타입 대신 Element라는 추상화된 타입을 사용합니다. 따라서 다양한 타입의 배열을 파라미터로 받을 수 있습니다. 이렇게 추상화된 타입을 사용하면, 코드를 더 유연하고 재사용 가능하게 만들 수 있습니다.
모델 객체는 데이터를 나타내는 객체로, 앱에서 사용되는 데이터를 관리하고 조작하는 데 사용됩니다. 모델 객체는 데이터에 대한 저장소 또는 API와 상호 작용하여 데이터를 가져와서 변환하는 역할을 수행합니다. 이러한 모델 객체는 일반적으로 앱의 다른 부분과 독립적으로 테스트 및 유지 관리하기 쉽게 설계됩니다. 예를 들어, 이 코드에서는 landmarkData.json 파일에 저장된 데이터를 가져와서, 해당 데이터를 사용하여 Landmark 객체를 만들고, 이러한 객체를 배열로 저장하는 역할을 수행합니다. 이렇게 구성된 모델 객체는 다른 뷰나 컨트롤러에서 사용될 수 있으며, 데이터를 변경하거나 조작하는 데 사용됩니다.
MVC(Model-View-Controller) 디자인 패턴에서, 앱의 유저 인터페이스의 버튼(View)을 누르면 컨트롤러(Controller)가 이벤트를 처리하고, 모델(Model)을 업데이트하여 결과를 다시 뷰(View)에 표시
뷰(View)는 앱의 유저 인터페이스를 구성하는 것으로, 버튼, 레이블, 이미지 등을 표시하는 등 사용자와 상호작용하는 모든 것을 포함합니다.
컨트롤러(Controller)는 뷰를 제어하고 앱의 비즈니스 로직을 처리하는데 사용되는 객체입니다. MVC(Model-View-Controller) 디자인 패턴에서는 컨트롤러가 모델(Model)과 뷰(View)를 연결하는 중간자 역할을 합니다.
예를 들어, 앱의 유저 인터페이스에 버튼을 누르면 컨트롤러가 이벤트를 처리하고, 모델을 업데이트하여 결과를 다시 뷰에 표시합니다. 이렇게 컨트롤러는 뷰와 모델 간의 상호작용을 관리하고, 앱의 전반적인 동작을 제어합니다.
인스턴스는 클래스, 구조체, 열거형 등의 타입으로부터 생성된 객체를 의미합니다. 이 경우, 제네릭 함수 load는 JSON 데이터를 디코딩하여 해당 타입의 인스턴스를 반환하게 됩니다. 예를 들어, load("landmarkData.json")는 "landmarkData.json" 파일에 있는 JSON 데이터를 디코딩하여 Landmark 구조체 인스턴스의 배열을 반환합니다.
인스턴스(Instance)는 객체(Object)의 실체화된(concrete) 형태
객체 인스턴스를 생성하면, 메모리 상 구체적인 데이터로 할당된다.
객체(Object)는 클래스 또는 구조체와 같은 데이터 타입으로 정의되어 있는 추상적인 개념입니다. 이와 비슷하게 인스턴스(Instance)는 객체(Object)의 실체화된(concrete) 형태로, 메모리 상에 할당된 구체적인 데이터를 말합니다.
예를 들어, 클래스로 정의된 Person이라는 객체(Object)가 있다고 가정해봅시다. 이 때, Person 클래스를 사용하여 메모리 상에 할당된 실체(concrete)가 인스턴스(Instance)가 됩니다. 따라서 인스턴스는 객체(Object)의 실체화된 형태라고 볼 수 있습니다.
컴퓨터의 메모리 상에 구체적으로 할당된 값만 다룰 수 있다
메모리 상에 구체적으로 할당된 데이터인지 아닌지는 중요한 이유가 있습니다.
먼저, 프로그램이 실행될 때 모든 데이터는 메모리 상에 할당됩니다. 따라서 메모리 상에 할당된 데이터만을 다룰 수 있습니다.
이 말은 프로그래밍에서 변수나 상수 등을 사용해 값을 다룰 때, 이 값들이 컴퓨터의 메모리 상에 구체적으로 할당되어 있어야만 해당 값을 다룰 수 있다는 뜻입니다. 메모리 상에 할당된 데이터가 없는 상태에서는 해당 값에 대한 작업을 수행할 수 없습니다. 따라서 인스턴스 또한 메모리 상에 할당되어 있는 데이터이어야 해당 인스턴스에 대한 작업을 수행할 수 있습니다.
또한, 객체의 인스턴스는 메모리 상에 할당된 데이터의 표현입니다. 객체의 인스턴스를 생성하면 해당 객체의 속성값이 메모리 상에 할당됩니다. 이후 객체의 인스턴스를 다룰 때는 해당 인스턴스에 대한 참조를 통해 메모리 상의 데이터에 접근하게 됩니다.
따라서 객체의 인스턴스를 다룰 때는 해당 인스턴스가 메모리 상에 할당된 데이터임을 인지하고, 해당 데이터에 대한 참조를 다루어야 합니다.
Xcode > Settings > General > File Extensions > Show All