본 글에서는 iOS Application 개발의 가장 기초가 되는 Swift Language에 대해 다룹니다.
1. 변수(var)
•
값(데이터)를 메모리에 저장하는 공간, 메모리 할당 후 변할 수 있는 값
var greeting: String = "Hello, Playground."
var age: Int = 26
age = 27
age = 28
...
Swift
복사
2. 상수(let)
•
값(데이터)를 메모리에 저장하는 공간, 메모리 할당 후 변하지 않는 값
let name: String = "John" // 변하지 않음
Swift
복사
3. Type Annotation
•
변수 혹은 상수의 타입을 프로그래머가 직접 지정해줌으로써 Compile Time을 단축 시킬 수 있습니다.
•
Swift에서는 Character와 String 모두 “” 를 사용하여 값을 초기화 하지만, 별도의 Type Annotation이 없을 경우 Default는 String이기 때문에, 하나의 문자(Character)만 사용할 경우에는 Type Annotation을 해주어야 합니다.
let screentHeight: Float = 560
print(type(of: screenHeight))
let character: Character = "A"
let string = "A"
print(type(of: character), type(of: string))
Swift
복사
4. Tuple
•
하나의 쌍을 이루는 값
var httpError = (statusCode: 404, value: "Page Not Found")
httpError.statusCode
httpError.value
var title = ("메인화면", "mainIcon.png")
title.0 // 메인화면
title.1 // mainIcon.png
Swift
복사
5. 옵셔널(Optional)
•
옵셔널은 값이 있을 수도 있고, 없을 수도 있는 것을 의미합니다.
•
Swift에서는 값이 없는 것을 null 대신 nil을 사용합니다.
•
보다 안전하게 코드를 작성할 수 있습니다.
// 값이 있을 수도 있고, 없을 수도 있다.
// 0은 값이 없는 것이 아닌, 다 쓴 값으로 생각
// 값이 없는 것은 nil
var myAge: Int?
if myAge == nil {
// Do Something.
}
Swift
복사
6. 옵셔널 추출(Optional Unwrapping)
•
Swift에서 Optional은 enum 타입으로 Wrapping 되어 있기 때문에 값을 사용하기 위해서 Unwrapping(추출) 하는 과정이 필요합니다.
•
옵셔널 바인딩, 강제 추출 등이 있는데 강제 추출의 경우 값이 있다는 확신이 있을 때만 사용하는 것이 좋습니다.(값이 없는데 강제로 추출할 경우, Runtime Error가 발생합니다.)
// 1. Coalesce - ??
var a: Int? = 10
var b: Int? = 20
var c = (a ?? 0) + (b ?? 0)
// 2. Force Unwrapping - !
// Runtime Error가 발생할 수 있기 때문에, 값이 있다는 확신이 있을 때 사용하는 것을 권고
var d = a! + b!
// 3. Optional Binding
// if Statements
// if let
// if var
// guard let
// guard var
if let hansNumber = a {
// Do Something.
}
if var hasNumber = a {
// Do Something.
}
func functionA() -> Void {
guard let hasNumber = a else { return }
}
func functionB() -> Void {
guard var hasNumber = a else { return }
}
Swift
복사
7. 기본 연산자(Basic Operator)
•
Swift의 기본 사칙연산과 여러 연산자에 대해 다룹니다.
•
Swift에서는 별도의 증감 연산자(++, — 등)가 없습니다. 사용하고 싶은 경우 사용자 정의 연산자(Custom Operator)로 직접 구현해서 사용해야 합니다.
// +, -, *, /, %
var someNumber: Int = 20
someNumber += 2
someNumber -= 2
someNumber *= 2
someNumber /= 2
someNumber %= 2
// Swift는 Type Safe한 Programming Language
let number1: Int = 20
let number2: Int = 30
let number3 = number1 / number2 // Integer
let number4: Double = 20
let number5: Double = 30
let number6 = number4 / number5 // Double
let number7: Int = 20
let number8: Double = 30
// let sum = number7 + number8 // Error
let sum = number7 + Int(number8)
let str1: String = "Hi"
let str2: String = " Hello"
let str3: String = str1 + str2
// Comparison Operator
number1 == number2
number1 != number2
number1 > number2
number1 < number2
number1 >= number2
number1 <= number2
if number1 > number2 {
// Do Something.
} else {
// Do Something.
}
Swift
복사
8. Unicode
•
Unicode를 통해, 기본 문자, 특수 문자 등을 사용할 수 있습니다.
// 숫자만 입력 받아야 하는 경우
let inputValue = "19"
if inputValue >= "\u{30}" && inputValue <= "\u{39}" {
print("숫자")
} else {
print("숫자가 아님")
}
Swift
복사
9. 문자열(String)
•
Swift의 String에 대해 다룹니다.
•
Swift는 타입에 엄격한(Type Safety)한 언어이지만, “\()”과 같이 문자열 보간법(String Interpolation)으로 다른 타입을 문자열 타입으로 편리하게 Casting할 수 있습니다.
let myName: String = "lee"
for character in myName {
// Do Something.
}
let myLongStr: String =
"""
hi
hello
welcome
"""
let isOn: Bool = true
print(isOn.description) // "true\n"
let myNumber: Int = 123
print(String(myNumber)) // "123\n"
print("My number is \(myNumber)") // "My number is 123\n"
Swift
복사
10. Collection Type - 배열(Array)
•
Swift의 Collection Type 중, 동일한 타입의 값을 메모리상에 연속적으로 저장하는 Array(배열)에 대해 다룹니다.
•
배열은 index기반으로 메모리 주소에 접근하기 때문에, 전체를 탐색하는 경우 O(N), 특정 index를 탐색하는 경우 O(1)이라는 시간 복잡도로 데이터 탐색이 빠르다는 장점을 가지고 있습니다.
•
반면에, 데이터를 삽입, 삭제 하는 경우 메모리 상에 연속적으로 존재하기 때문에, 값을 뒤로 미루거나 앞으로 땡기는 작업을 필요로 하기 때문에 하나의 데이터를 삽입 혹은 삭제할 때 마다, 최악의 경우 O(N)이라는 시간복잡도를 가지게 되므로, 다소 비효율적이라고 말할 수 있겠습니다.
var myNames: Array<String> = Array()
var myAges: [Int] = []
myNames.append("kim")
myNames.append("lee")
myNames.append("jin")
// myNames[3] // Index out of range
// 방어적 코드
// 안전한 코딩 스타일
let index: Int = 2
if myNames.count > index {
print(myNames[index])
}
myNames.append(contentsOf: ["hi", "hello"])
if myNames.count == 0 {
// Do Something.
}
if myNames.isEmpty {
// Do Something.
}
myNames.insert("hahaha", at: 2)
for name in myNames {
print(name + "님")
}
for (index, name) in myNames.enumerated() {
print(index, name)
}
Swift
복사
11. Collection Type - Set
•
Swift의 Collection Type 중, Set(집합)에 대해 다룹니다.
•
Array와 유사하지만, 차이점이라고 하면, 순서라는 개념이 없습니다.
•
또, 중복된 값은 추가되지 않으며 집합이기 때문에, 교집합, 합집합, 차집합, 여집합 등과 같은 연산을 할 수 있습니다.
// Array와 달리 순서가 없음
var names: Set<String> = Set()
names.insert("kim")
names.insert("min")
// 중복된 값은 추가 되지 않음
names.insert("lee")
names.insert("lee")
names.insert("lee")
var names2: Set = ["lee", "kim", "min"]
var numbers1: Set = [1, 2, 3, 4, 5]
var numbers2: Set = [4, 5, 6, 7, 8]
// 교집합
numbers1.intersection(numbers2)
// 합집합
numbers1.union(numbers2)
// 합집합 - 교집합
numbers1.symmetricDifference(numbers2)
// 여집합
numbers1.subtracting(numbers2)
Swift
복사
12. Collection Type - Dictionary
•
Swift의 Collection Type 중, Dictionary에 대해 다룹니다.
•
이 또한 순서적인 개념이 없으며, Key - Value의 형태를 이룹니다.
// 순서적인 개념이 없음
// Key-Value 형태
var namesOfStreet: Dictionary<String, Any> = Dictionary()
namesOfStreet["302ro"] = "1st Street"
namesOfStreet["303ro"] = "2nd Street"
namesOfStreet["304ro"] = 3
namesOfStreet.keys
for (key, value) in namesOfStreet {
print(key, value)
}
Swift
복사
13. 흐름 제어(Control Flow)
•
조건문, 반복문과 같은 흐름 제어(Control Flow)에 대해 다룹니다.
let name: String = "anna"
for char in name {
// Do Something.
}
let numberOfLegs = ["ant" : 6, "dog" : 4]
for dic in numberOfLegs {
// Do Something.
}
for index in 0...5 {
// Do Something.
}
var some: Int = 0
while some < 10 {
print("계속 실행")
some += 1
}
// Switch
// 조건에 케이스를 만들어서 분기
let someAlpha: String = "b"
switch someAlpha {
case "b":
// Do Something.
default:
// Do Something.
}
Swift
복사
14. 함수(Function)
•
어떠한 연산 과정에 대한 결과를 반환하는 함수에 대해 다룹니다.
func simpleFunc() -> Void {
print("Simple Func")
}
func plus(num1: Int, num2: Int) -> Int {
return num1 + num2
}
func minus(_ num1: Int, _ num2: Int) -> Int {
return num1 - num2
}
func multiply(_ num1: Int, _ num2: Int) -> Int {
return num1 * num2
}
// 반환 타입이 없는 경우(Void)는 생략 가능
func calc(result: (Int, Int) -> Int) {
print("연산 결과", result(10, 20))
}
var inputButtonType = "+"
if inputButtonType == "+" {
calc(result: plus)
} else if inputButtonType == "-" {
calc(result: minus)
} else if inputButtonType == "*" {
calc(result: multiply)
}
Swift
복사
15. 클로저(Closure)
•
Swift의 클로저는 코드의 블록이라고도 하며, 이름이 없고 함수와 유사합니다.
•
이름 있는 클로저를 함수라고 말할 수 도 있습니다.
•
고차 함수, 비동기 처리와 같은 작업 등에 사용할 수 있겠습니다.
•
다양한 축약 문법이 존재하므로, 과도한 축약보다는 적절한 축약 문법을 사용하는 것이 좋겠습니다.
// Closure - 코드의 블럭이라고도 하며, 이름이 없고 함수와 유사
// Function Version
func myScore(a: Int) -> String {
return "\(a)점"
}
// Closure Version
let myScore2: (Int) -> String = { (a: Int) -> String in return "\(a)점" }
// Closure 축약
// 1. return 생략 - body가 한 줄일 경우
let myScore3: (Int) -> String = { (a: Int) -> String in "\(a)점" }
// 2. 반환 값을 추론하여 반환 타입 생략
let myScore4: (Int) -> String = { (a: Int) in "\(a)점" }
// 3. 파라미터 타입 생략
let myScore5: (Int) -> String = { a in "\(a)점" }
// 4. 단축 인자 이름, in 키워드 생략
let myScore6: (Int) -> String = { "\($0)점" }
// Closure 실전
let someNames = ["apple", "air", "brown", "red", "orange", "blue", "candy", "hobby"]
let isContainsSomeText: (String, String) -> Bool = { name, find in
if name.contains(find) {
return true
}
return false
}
let isStartSomeText: (String, String) -> Bool = { name, find in
if name.first?.description == find {
return true
}
return false
}
// Function Version
func findByFunction(find: String) -> [String] {
var newNames: [String] = []
for name in someNames {
if name.contains(find) {
newNames.append(name)
}
}
return newNames
}
// Closure Version
func findByClosure(findString: String, _ condition: (String, String) -> Bool) -> [String] {
var newNames: [String] = []
for name in someNames {
if condition(name, findString) {
newNames.append(name)
}
}
return newNames
}
findByFunction(find: "a")
findByClosure(findString: "a", isContainsSomeText)
findByClosure(findString: "a", isStartSomeText)
let result = findByClosure(findString: "y") { $0.last?.description == $1 ? true : false }
print(result)
var someArr = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
someArr.sort { $0 < $1 }
someArr.sort(by: { $0 < $1 } )
someArr.sort(by: < )
Swift
복사
16. 열거형(Enumeration, enum)
•
Swift의 enum에 대해 다룹니다.
•
유사한 타입들을 열거할 때 주로 사용하며, 타입 분류를 더 쉽게 하기 위해 사용합니다.
•
Value Type이기 때문에 전달인자로 전달 될 때 값이 복사돼서 전달 됩니다.(Call By Value)
// Enum
// Value Type(값 타입) - 값이 복사돼서 전달됨, 타입 분류를 더 쉽게 하기 위해 사용
enum BookType {
case fiction(title: String, price: Int, year: Int), comics(title: String, price: Int, year: Int), workbook(title: String, price: Int, year: Int)
}
var bookStyle: BookType?
var books: [BookType] = []
func saveBook(book: BookType) {
books.append(book)
}
saveBook(book: .fiction(title: "aaa", price: 5000, year: 2020))
saveBook(book: .comics(title: "bbb", price: 6000, year: 2021))
saveBook(book: .comics(title: "ccc", price: 7000, year: 2010))
saveBook(book: .workbook(title: "ddd", price: 7000, year: 2010))
saveBook(book: .fiction(title: "eee", price: 4000, year: 202))
saveBook(book: .fiction(title: "fff", price: 8000, year: 2015))
for book in books {
if case let BookType.workbook(_, _, year) = book {
print(year)
}
switch book {
case let .comics(_, price, _):
print(price)
case let .fiction(title, _, _):
print(title)
default:
break
}
}
Swift
복사
17. 클래스(Class)
•
Swift의 class에 대해 다룹니다.
•
iOS Framework는 주로 class로 이루어져 있습니다.
•
Enum, Struct은 상속이 불가능 하지만, Class는 상속이 가능하며, 단일 상속만 가능합니다.
•
또, Reference Type이기 때문에 전달인자로 전달 될 때 참조가 전달 됩니다.(Call By Reference)
// Class
// Reference Type(참조 타입) - 참조가 전달됨
class MyInfo {
enum GenderType {
case male
case female
}
var genderType: GenderType?
var name: String = ""
var age: Int = 0
func isAdult() -> Bool {
return self.age > 19
}
init(gender: GenderType) {
self.genderType = gender
}
}
// 참조 예시
var myInfo: MyInfo = MyInfo(gender: .female)
myInfo.age = 20
var myInfo2: MyInfo = myInfo
print(myInfo2.age)
myInfo2.age = 30
print(myInfo.age)
print(myInfo2.age)
Swift
복사
18. 상속(Class - Inheritance)
•
Swift Class의 상속에 대해 다룹니다.
•
부모 클래스의 기능을 물려받아 사용하기도 하며, Override해서 사용하기도 합니다.
•
이는 코드의 재사용성을 늘리고 중복을 줄일 수 있습니다.
•
단, 단일상속만 가능합니다.
// 단일 상속
class SportsInfo {
var homeScore: Int = 0
var awayScore: Int = 0
func presentScore() -> String {
return homeScore.description + " : " + awayScore.description
}
}
class Soccer: SportsInfo {
var time: Int = 0
override func presentScore() -> String {
return "\(super.presentScore()), \(time)"
}
}
class Baseball: SportsInfo {
var round: Int = 30
override func presentScore() -> String {
return "\(super.presentScore()), \(round)"
}
}
class Football: SportsInfo {
}
let soccer: Soccer = Soccer()
soccer.time = 45
soccer.homeScore = 1
soccer.presentScore() // "1 : 0, 45"
let baseball: Baseball = Baseball()
baseball.presentScore() // "0 : 0, 30"
let football: Football = Football()
football.homeScore // 0
Swift
복사
19. 프로퍼티(Property)
•
Class, Struct, Enum 등에 정의된 변수 혹은 상수를 말합니다.
•
저장 프로퍼티, 연산 프로퍼티, 타입 프로퍼티(static) 등이 존재합니다.
•
Image와 같은 트래픽을 많이 요구하는 프로퍼티의 경우 lazy 키워드를 통해 사용할 때 메모리에 적재 되도록 지정할 수 있습니다.
class MyInfoClass {
// Stored Property(저장 프로퍼티)
var name: String = ""
var age: Int = 0
// Lazy Stored Property(Class가 메모리에 적재될 때 같이 되는 것이 아니고, 사용하려고 할 때 메모리에 load됨.)
lazy var myProfiles: [UIImage?] = [UIImage(named: "n") ?? nil, UIImage(named: "a") ?? nil]
// Computed Property(연산 프로퍼티)
var isAdult: Bool {
return age > 19
}
private var _email: String = ""
var email: String {
get {
return _email
}
set {
_email = newValue.hash.description
}
}
}
let myInfoClass: MyInfoClass = MyInfoClass()
myInfoClass.age = 27
myInfoClass.name = "lee"
myInfoClass.email = "lyJ@vaultmicro.com"
myInfoClass.myProfiles
print(myInfoClass.isAdult)
print(myInfoClass.email)
Swift
복사
20. 생성자(Initializer, init)
•
Swift의 Type의 인스턴스 생성 시, 프로퍼티에 대한 값을 초기화 하는 역할을 합니다.
class SomeClass {
var name: String
var id: String
init(name: String, id: String) {
self.name = name
self.id = id
}
// convenience initializer, 다른 init을 반드시 실행해야 함
convenience init() {
self.init(name: "", id: "")
}
}
var someClass: SomeClass = SomeClass(name: "lee", id: "abcd")
Swift
복사
21. 메모리 해제(Deinitialization, deinit)
•
Swift의 메모리 관리 기법은 ARC(Automatic Refernce Counting)를 사용합니다.
•
이는 Class 인스턴스가 강한 참조를 받게 되는 경우 ARC Count 값이 1 증가 하게 됩니다.
•
ARC Count 값이 1 이상인 경우에는 메모리에서 해당 인스턴스가 해제되지 않습니다.
•
ARC Count 값이 0 일 경우에 메모리에서 해제되게 됩니다.
•
주의할 점은, Class 타입의 인스턴스 끼리 강한참조 하게 될경우 강한 참조 순환 문제가 발생하며,
•
메모리 누수(Memory Leak)가 발생하게 됩니다.
•
따라서 이러한 경우 생명주기가 더 짧은 Class 타입에서 weak 키워드를 통해 참조하게 되면 ARC Count 값이 증가하지 않아 메모리 누수(Memory Leak)를 방지할 수 있겠습니다.
•
또, Class 타입 인스턴스가 메모리에서 해제 될 때 deinit 함수가 실행되게 됩니다.
var someInteger: Int? = 10
a = nil
class Game {
var score: Int = 0
var name: String = ""
var round: Round?
init() {
print("game init")
}
deinit {
print("game deinit")
}
}
class Round {
weak var gameInfo: Game? //메모리 누수(Memory Leak) 방지
var lastRound: Int = 10
var roundTime: Int = 20
deinit {
print("round deinit")
}
}
var game: Game? = Game()
var round: Round? = Round()
round?.gameInfo = game
game?.round = round
game = nil
round = nil
Swift
복사
22. 구조체(Structure, struct)
•
Swift의 struct에 대해 다룹니다.
•
Swift의 구조는 주로 struct으로 이루어져 있습니다.
•
struct의 경우 상속이 불가능하며, Value Type이기 때문에 전달인자로 전달될 때 값이 복사돼서 전달 됩니다.(Call By Value)
// Struct - 구조체
// Value Type(값 타입) - 값이 복사돼서 전달됨
struct SomeStruct {
var name: String = ""
}
var someStruct1: SomeStruct = SomeStruct()
var someStruct2: SomeStruct = someStruct1
someStruct1.name = "lee"
someStruct2.name = "kim"
print(someStruct1)
print(someStruct2)
Swift
복사
23. Extension
•
Swift의 Extension은 struct, class, enum, protocol과 같은 Type의 기능을 확장합니다.
•
이미 정의되어 있는 기능은 extension을 통해 재정의는 할 수 없습니다.
•
주로, delegate 패턴과 함께 extension을 사용할 수 있겠습니다.
// struct, class, enum, protocol 과 같은
// type들의 기능을 확장함, 이미 정의되어 있는 기능은 extension을 통해 재정의할 수 없음
extension Int {
var oddOrEven: String {
return self % 2 == 0 ? "짝수" : "홀수"
}
}
3.oddOrEven
4.oddOrEven
extension UIColor {
var mainColor1: UIColor {
UIColor(red: 50/255, green: 70/255, blue: 120/255, alpha: 1)
}
}
var button: UIButton = UIButton()
button.titleLabel?.textColor = UIColor().mainColor1
Swift
복사
24. 프로토콜(Protocol)
•
Swift는 객체지향, 함수형, 프로토콜지향 프로그래밍이라고 할 수 있습니다.
•
프로토콜은 네트워크에서도 많이 사용되는 용어로, 규격, 규약, 규칙이라고 말할 수 있겠습니다.
•
struct, enum, class 등이 프로토콜을 채택한다고 말하며, 구현했으면 하는 기능의 명세서라고도 말할 수 있겠습니다.
•
상속과 달리 여러 프로토콜을 채택할 수 있지만, SOLID 설계 원칙 중 인터페이스 분리 원칙(Interface segregation principle)에 의해 필요한 프로토콜만 채택해서 구현하는 것이 좋겠습니다.
•
단, Class의 경우, “:(콜론)” 뒤에 가장 처음으로 오는 것이 상속 이거나 프로토콜 채택일 수 있고, 그 뒤에는 모두 프로토콜 채택이라고 할 수 있겠습니다.
// 규격, 규약, 규칙
protocol UserInfo {
var name: String { get set }
var age: Int { get set }
func isAdult() -> Bool
}
// 프로토콜 지향 프로그래밍(Protocol Oriented Programming, POP)
extension UserInfo {
func isAdult() -> Bool {
return self.age > 19
}
}
// protocol 채택
class Guest: UserInfo {
var name: String = "kim"
var age: Int = 20
}
class Member: UserInfo {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
class VIPMember: UserInfo {
var name: String = "jane"
var age: Int = 10
}
class UserInfoPresenter {
func present() {
let guest: Guest = Guest()
let member: Member = Member(name: "lee", age: 27)
let vip: VIPMember = VIPMember()
let members: [UserInfo] = [guest, member, vip]
for element in members {
print(element.name)
}
}
}
let presenter: UserInfoPresenter = UserInfoPresenter()
presenter.present()
Swift
복사
25. 제네릭(Generic)
•
Swift의 Generic에 대해 다룹니다.
•
한 번의 정의를 통해, 여러가지 타입을 사용하고 싶을 때 Generic을 사용하면 좋습니다.
•
다만, Generic이 수용할 수 있는 타입에 대해 제한을 둘 수 있는데, 이 때 where 키워드를 사용합니다.
// Generic <Type>
// 타입을 여러가지 사용할 때 사용
// 타입에 제한을 둘 때 제네릭 명 옆에 where 키워드 사용
struct Stack<SomeType> {
var items: [SomeType] = []
mutating func push(item: SomeType) {
self.items.append(item)
}
mutating func pop() -> SomeType? {
if items.isEmpty {
return nil
}
return items.removeLast()
}
}
var myStack: Stack<Int> = Stack()
myStack.push(item: 4)
myStack.push(item: 5)
myStack.push(item: 6)
myStack.pop()
myStack.pop()
myStack.pop()
myStack.pop()
var myStack2: Stack<String> = Stack()
myStack2.push(item: "4")
myStack2.push(item: "5")
myStack2.push(item: "6")
myStack2.pop()
myStack2.pop()
myStack2.pop()
myStack2.pop()
Swift
복사
26. 고차 함수(Higher Order Function)
•
Swift의 Higher Order Function(고차 함수)에 대해 다룹니다.
•
고차 함수는 함수를 반환 하거나, 전달인자로 함수를 전달 받는 함수를 말합니다.
•
고차 함수를 사용하면, 코드를 편리하고 간결하게 작성할 수 있는 장점이 있습니다.
// 고차함수를 사용하면 코드를 편리하고 간결하게 작성할 수 있는 장점이 있음
// 1. Map
let nameArr: [String] = ["kim", "lee", "min", "john"]
let nameArrMap = nameArr.map { $0 + " 님" } // ["kim 님", "lee 님", "min 님", "john 님"]
let nameArrSMap2 = nameArr.map { name in
name.count // [3, 3, 3, 4]
}
// 2. Filter
let filterNames = nameArr.filter { name -> Bool in
name.count > 3 // ["john"]
}
// 3. Reduce
let reduceNames = nameArr.reduce("") { $0 + $1 } // "kimleeminjohn"
let numberArr = [1, 2, 3, 4, 5, nil, 6, nil, 8]
let sumNum = numberArr.reduce(0) { $0 + ($1 ?? 0) } // 29
// 4. CompactMap - nil을 제외함
let compactMap = numberArr.compactMap { $0 } // [1, 2, 3, 4, 5, 6, 8]
// 5. FlatMap
let numberArr2 = [[1, 2, 3], [4, 5, 6]]
let flatNum = numberArr2.flatMap { $0 } // [1, 2, 3, 4, 5, 6]
Swift
복사