Swift 입문 4일차

개요

오늘도 Swift 문법을 공부해보자!

Sorted & Sort

배열을 sortedsort 로 정렬할 수 있다. 차이점이라면 sorted 는 정렬된 배열을 리턴하고, sort 는 정렬할 배열 자체를 변경한다.

import UIKit

// sort, sorted
var myArray = [3, 4, 55, 989, 10, 2, 1, 9]

// 정렬된 배열 반환 (오름차순)
var ascendingArray = myArray.sorted() // [1, 2, 3, 4, 9, 10, 55, 989]
// 정렬된 배열로 바뀜 (오름차순)
myArray.sort() // [1, 2, 3, 4, 9, 10, 55, 989]

var descendingArray = myArray.sorted(by: >) // [989, 55, 10, 9, 4, 3, 2, 1]
myArray.sort(by: >) // [989, 55, 10, 9, 4, 3, 2, 1]

기본값은 오름차순이다. 내림차순은 > 로 수행할 수 있다.

Private(Set)

ClassStruct 에서 사용하는 변수는 외부에서 변경할 수 있다. public 이 기본값이기 때문이다. private 로 선언한다면 그 변수는 외부에서 변경할 수 없다.

만약 변경하려면 Class 또는 Struct 가 갖고있는 메소드를 활용하여 변경해야 한다.

import UIKit

// MyPet struct 내에서만 사용
struct MyPet {
    private (set) var name : String = "이름 없음"
    var title: String = "타이틀없음"
    // struct 내에서 name 변수를 변경하려면 mutating 키워드 필요
    mutating func setName(to newName: String) {
        self.name = newName
    }
}

var myPet = MyPet()
myPet.name // 이름없음
myPet.title // 타이틀없음

// private 키워드가 없으면 (public) 이면 외부에서 접근 가능
myPet.title = "타이틀 있음"
myPet.title // 타이틀 있음

//myPet.name = "윤수"
//myPet.name // 접근 불가능 에러

// private 이기 때문에 직접 변경은 불가능, 메소드를 통한 접근으로 struct 내부에서 변경하는 것으로 변경 가능
myPet.setName(to: "윤수")
myPet.name // 윤수


Foreach 에서 인덱스 가져오기

forEach 반복문을 실행할 때 index 도 같이 가져오려면 배열을 enumerated 로 만들어 첫번째 인자로 index 를 가져올 수 있다.

import UIKit

let myFriendArray : [String] = ["윤수", "철수", "수진", "나나"]

var friendsWithIndex : [String] = []

for (index, aFriend) in myFriendArray.enumerated() {
    print("index : \(index), item : \(aFriend)")
    friendsWithIndex.append("\(index)\(aFriend)")
}

//index : 0, item : 윤수
//index : 1, item : 철수
//index : 2, item : 수진
//index : 3, item : 나나


Map

Map 은 콜렉션의 데이터의 형태를 변경하기 위한 고차함수 메소드이다.

import UIKit

// Map
// 콜렉션 - 데이터들의 모음
// 콜렉션 안에는 배열, 딕셔너리, Set 등이 있다.

// Map 이란 콜렉션 내 데이터들의 형태를 변경하는 것
let friendArray : [String] = ["윤수", "철수", "수진", "나나"]

let myFriends = friendArray.map { aFriend in
    return "내 친구 " + aFriend
}

myFriends // ["내 친구 윤수", "내 친구 철수", "내 친구 수진", "내 친구 나나"]

let myBestFriend = friendArray.map {
    return "베프 \($0)"
}

myBestFriend // ["베프 윤수", "베프 철수", "베프 수진", "베프 나나"]


compactMap

nil 이 존재하는 옵셔널 변수의 경우 Map 을 수행하면 원하는 결과를 얻어낼 수 없다.

옵셔널 변수를 언래핑하거나, nil 인 요소를 제거한 후 다시 Map 해야 한다. 이를 compactMap 으로 수행할 수 있다.

// nil 이 있는 optional 변수는 언래핑이 필요하다.
let bestFriendArray : [String?] = ["윤수", nil,"철수", "수진", "나나", nil]
let myBFs = bestFriendArray.map { aBF in
    return "내 BF \(aBF ?? "")"
} // ["내 BF 윤수", "내 BF ", "내 BF 철수", "내 BF 수진", "내 BF 나나", "내 BF "]

// optional 변수를 compactMap 으로 편하게 제외할 수 있다.
let myBFss = bestFriendArray.compactMap { aBF in
    return aBF
} // ["윤수", "철수", "수진", "나나"]

compactMap 메소드는 콜렉션 내 데이터 값이 nil 이라면 그 요소를 제외한다.

flatMap

flatMap 은 콜렉션 안에 콜렉션이 들어있는 2차원 형태의 구조를 1차원으로, 즉 하나의 콜렉션으로 묶는다.

import UIKit

let myFriends = [["철수"], ["윤수"], ["짱구", "짱아"], ["맹구", "유리"]]

let flatMapped = myFriends.flatMap{
    (item: [String]) in
    return item
}

flatMapped // ["철수", "윤수", "짱구", "짱아", "맹구", "유리"]


class func & static func

지금까지 Class 에서 함수를 만들고 사용할 땐 Class 내부에 함수를 만들고 변수에 그 인스턴스를 담아 선언한 후(메모리에 올린 후) 호출하면 되었다.

class func 는 메모리에 올리지 않고도 바로 호출할 수 있는 메소드를 의미한다.

import UIKit

class Friend {
    func sayHi() {
        print("HELLO")
    }
    class func sayBye() {
        print("BYE BYE")
    }
    static func saySiu() {
        print("SIUUUUUUUUU")
    }
}

let myFriend = Friend()
myFriend.sayHi() // HELLO
Friend.sayBye() // BYE BYE

static funcclass func 와 동일해보인다. 그러나 static funcclass func 의 결정적인 차이는 상속 가능 여부이다.

static funcfinal class func 와 동일한 개념이다. 즉 상속이 불가능하다.

class BestFriend : Friend {
    class override func sayBye() {
        print("OVERRIDE BYE BYE")
    }
    func saySiu() {
        print("OVERRIDE SIUUUUUUUU")
    }
}

BestFriend.sayBye() // OVERRIDE BYE BYE
BestFriend.saySiu() // SIUUUUUUUUU

정리하자면 static funcclass func 를 사용하는 이유는 메모리에 올리지 않고도 메소드를 사용할 수 있다는 점이 있다. 차이점이라면 static func 는 상속이 불가능하다는 것이다.

부모가 갖고있는 메소드를 호출하고자 한다면 super 키워드를 사용하면 된다.

class override func sayBye() {
        super.sayBye()
        print("OVERRIDE BYE BYE")
    }

메모리에 올리지 않고 메소드를 사용 가능하다는 것은 어떤 위치에서도 메소드를 사용할 수 있다는 뜻이다.

예를 들면 헬퍼 메소드 (Int -> String 변환 등) 같은 것들을 모아두는 유틸리티 클래스를 만들고 static funcclass func 를 사용하여 메소드를 만들 수 있다.

그렇게 만들어진 메소드를 다른 파일에서 간편하게 사용할 수 있다.

Dictionary Grouping

딕셔너리를 이용하여 원하는 항목 별로 그룹핑을 할 수 있다.

날짜 별, 카테고리 별 거대한 양의 데이터를 그룹핑하여 생산성을 높일 수 있다.

import UIKit

enum FriendType {
    case normal, best
}

struct Friend {
    var name : String
    var type : FriendType
}

var friendList = [
    Friend(name: "철수", type: .normal),
    Friend(name: "영희", type: .best),
    Friend(name: "짱구", type: .best),
    Friend(name: "윤수", type: .normal)
]

//let groupedFriends = Dictionary(grouping: friendList, by: {$0.type})

let groupedFriends = Dictionary(grouping: friendList, by: {(friend) -> FriendType in
    return friend.type
})

print(groupedFriends)

groupedFriends[.normal] // [{name "철수", normal}, {name "윤수", normal}]
groupedFriends[.best] // [{name "영희", best}, {name "짱구", best}]


의존성 주입 (Dependency Injection, DI)

의존성 주입은 protocol 을 사용해 지켜야 할 약속을 변수에 할당하는 것이다.

import UIKit

// protocol : 약속, 무언가를 강제하는 것.
protocol Talking {
    var saying: String { get set }
    func sayHi()
}

class TalkingImplementation: Talking {
    var saying: String = "토크 "
    func sayHi() {
        print("안녕~")
    }
}

// Talking 이라는 약속은 지킨다. Talking implementation
// 하는 행동에 따라 값이 다르게 나오도록 만든다.
class BestTalk : Talking {
    var saying: String = "베스트 토크 "
    func sayHi() {
        print("찐친 안녕~")
    }
}

class OldTalk : Talking {
    var saying: String = "올드 토크 "
    func sayHi() {
        print("오랜만이야")
    }
}


class Friend {
    // BestTalk, OldTalk 모두 가능하다.
    var talkProvider: Talking
    var saying: String {
        get {
            talkProvider.saying
        }
    }

    init(_ talkProvider: Talking){
        self.talkProvider = talkProvider
    }

    func setTalkProvider(_ newProvider : Talking) {
        self.talkProvider = newProvider
    }

    func sayHi() {
        talkProvider.sayHi()
    }
}

// BestTalk 의존성
let myBestFriend = Friend(BestTalk())
myBestFriend.sayHi() // 찐친 안녕~
myBestFriend.saying // 베스트 토크

// OldTalk 의존성
let myOldFriend = Friend(OldTalk())
myOldFriend.sayHi() // 오랜만이야

// OldTalk 를 새로운 프로바이더로 변경
myOldFriend.setTalkProvider(TalkingImplementation())
myOldFriend.saying // 토크
myOldFriend.sayHi() // 안녕~


Getter & Setter

GetterSetter 는 말 그대로 어떤 것을 가져오는 것, 그리고 어떤 것을 설정하는 것이다.

// getter : 가져오는 것
// setter : 설정하는 것
class Friend {
    var name: String = ""
    var age: Int

    var detailInfo : String = ""

    var info: String {
        // info를 가져온다.
        get {
            return "내 친구 \(name) 나이는 \(age)"
        }
        set {
            detailInfo = "info에서 설정됨 : " + newValue
        }
    }

    init(_ name: String, _ age: Int){
        self.name = name
        self.age = age
    }
}

let myFriend = Friend("윤수", 20)
myFriend.info // "내 친구 윤수 나이는 20"
myFriend.info = "새로운 친구"
myFriend.detailInfo // "info에서 설정됨 : 새로운 친구"

myFriend.info 값을 가져올 수도 있고, 새로운 값을 할당하여 설정할 수 있다.

Codable

Codable 이란 DecodableIncodable 이 합쳐진 typealias 이다.

Encodable 은 데이터를 json 화 할 수 있다는 것이며, Decodablejsonclass 또는 struct 로 변환할 수 있다는 의미이다.

import UIKit

// Codable 이란 Decodable 과 Incodable 이 합쳐진 typealias 이다.
// Encodable : json화 한다.
// Decodable : json을 class or struct화 한다.
let jsonFromServer = """
{
    "nick_name": "개발자 배윤수",
    "job": "개발자",
    "user_name": "mniyunsu",
}
"""

struct User : Decodable {
    var nickname: String
    var job: String
    var myUserName: String

    enum CodingKeys: String, CodingKey {
        case nickname = "nick_name"
        case job
        case myUserName = "user_name"
    }

    static func getUserFromJson(_ json: String) -> Self? {
        guard let jsonData : Data = json.data(using: .utf8) else {
            return nil
        }
        do {
            let user = try JSONDecoder().decode(User.self, from: jsonData)
            print("user : \(user)")
            return user
        } catch {
            print("ERROR!! \(error.localizedDescription)")
            return nil
        }
    }
}


let user = User.getUserFromJson(jsonFromServer) // User(nickname: "개발자 배윤수", job: "개발자", myUserName: "mniyunsu")

Codable 은 주로 REST API 핸들링에서 사용된다.

멀티 트레일링 클로저

멀티 트레일링 클로저란 매개변수 클로저를 여러개 가지는 메소드이다.

func someFunctionWithClosures(first: () -> Void, second: (String) -> Void, third: (Int) -> Void) {
    first()
    second("하이")
    third(3)
}

someFunctionWithClosures(first: {print("첫번째")}, second: {print("두번째 \($0)")}, third: {print("세번째 \($0)")})

someFunctionWithClosures {
    print("first")
} second: { string in
    print("second \(string)")
} third: { number in
    print("third \(number)")
}


Convenience Init

Convenience Init 은 추가 생성자로서, 클래스가 갖고 있는 생성자에 추가로 로직을 실행할 수 있다.

// 추가 생성자
class Friend {
    var name: String
    var age: Int
    init(name: String){
        self.name = name
        self.age = 10
    }
    // 기존에 내가 갖고 있는 생성자에 추가해 무언가를 더 할 수 있다.
    convenience init(name: String, age: Int) {
        self.init(name: name)
        self.age = age
    }
}

let myFriend = Friend(name: "윤수", age: 20)
myFriend.age // 20


디자인 패턴 - 빌더

객체 생성 관련 디자인 패턴 중 하나인 Builder 패턴이다.

// 만들어 주는 것을 만든다.
// 핵심은 자기 자신을 뱉는다.

struct Pet {
    var name: String? = nil
    var age: Int? = nil
}

class PetBuilder {
    private var pet : Pet = Pet()
    func withName(_ name: String) -> Self {
        pet.name = name
        return self
    }
    func withAge(_ age: Int) -> Self {
        pet.age = age
        return self
    }
    func build() -> Pet {
        return self.pet
    }
}

let myPet = PetBuilder().withName("헬로우").withAge(10).build()
myPet

핵심은 만드려는 객체에 원하는 값들을 넣어서 객체를 구현하고, 마지막에 빌드라는 것을 통해 객체 스스로를 리턴하여 변수에 할당하는 것이다.

콜렉션 합치기

콜렉션을 합칠 때 변수에 append 를 통해 합칠 수 있지만, 단순히 + 로 사용할 수 있다.

// 콜렉션 - list [], set<>, dictionary[:}
let myFriends = ["철수", "영희", "윤수"]
let otherFriends: Set<String> = ["영수", "짱구", "강인"]

let totalFriends = myFriends + otherFriends
totalFriends // ["철수", "영희", "윤수", "영수", "짱구", "강인"]


Reduce

Reduce 는 콜렉션에 사용할 수 있는 고차함수 중 하나로, 값들을 축적해가며 합치는 것이다.

struct Friend: Hashable {
    var name: String
    var age: Int
}

let myFriends = [
    Friend(name: "철수", age: 10),
    Friend(name: "민수", age: 10),
    Friend(name: "짱구", age: 20),
    Friend(name: "맹구", age: 20),
    Friend(name: "윤수", age: 30),
]

// reduce - 축적하여 합친다. 초기값 0으로
// partialResult - 축적된 값
let totalAge = myFriends.reduce(0) { partialResult, aFriend in
    partialResult + aFriend.age
}
totalAge // 90

// dictionary 로 같은 나이별로 그룹핑하겠다.
let groupedFriends = myFriends.reduce(into: [:]) { partialResult, aFriend in
    partialResult[aFriend.age] = myFriends.filter{
        $0.age == aFriend.age
    }
}
groupedFriends


콜렉션 간 변형

콜렉션 간 변형을 통해 중복요소 제거, 정렬 등을 수행할 수 있다.

let numbers = [1,1,1,5,5,9,7]

// list -> set
let uniqueNumbers = Set(numbers)
uniqueNumbers // {7, 5, 1, 9}

// set 을 리스트로 정렬
var uniqueNumbersArranged = Array(uniqueNumbers) // [5, 7, 9, 1]
uniqueNumbersArranged.sort() // [1, 5, 7, 9]


Optional chaining

Optional Chaining 은 ? 연산자를 통해 처리할 수 있다.

struct Friend {
    let nickname: String
    let person: Person?
}

struct Person {
    let name: String
    let pet: Pet?
}

struct Pet {
    let name: String?
    let kind: String
}

let pet = Pet(name: "냥냥", kind: "고양이")
let person = Person(name: "윤수", pet: pet)
let friend = Friend(nickname: "mni", person: nil)

// 데이터가 있을 수 있고 없을 수 있다.

// 옵셔널 체이닝
if let petName = friend.person?.pet?.name{
    print("petname \(petName)")
} else {
    print("petname 없음") // petname 없음
}

func getPetName() {
    guard let petName = friend.person?.pet?.name else {
        print("petname 없음")
        return
    }
    print("petname \(petName)")
}

getPetName() // petname 없음

// 옵셔널 바인딩
//if let person = friend.person, let pet = person.pet, let petName = pet.name {
//    // 모든 데이터가 존재함
//    print("petName \(petName)")
//} else {
//    print("데이터 없음")
//}

//if let person: Person = friend.person {
//    if let pet = person.pet {
//        if let petName = pet.name {
//            print("petname : \(petName)") // petname : 냥냥
//        } else {
//            print("no petname")
//        }
//    }
//}

if let 은 먼저 모든 데이터를 언래핑 했을 때 존재할 때의 로직을 먼저 적고, guard letelse 문 즉 nil 데이터가 있을 때의 로직을 처리 후 리턴한다.

Equatable

Equatablestruct 간 비교가 가능하도록 편리하게 만들어주는 프로토콜이다.

// equatable protocol
// struct 간 비교 가능하도록 만들어준다.
struct Pet : Equatable {
    let id: String
    let name: String
    // 같은지 체크
    static func == (lhs: Pet, rhs: Pet) -> Bool {
        return lhs.id == rhs.id
    }
    static func != (lhs: Pet, rhs: Pet) -> Bool {
        return lhs.id != rhs.id
    }
}

let myPet1 = Pet(id: "01", name: "고양이")
let myPet2 = Pet(id: "02", name: "강아지")
let myPet3 = Pet(id: "01", name: "냥이")

if myPet1.id == myPet3.id {
    print("같은 Pet")
}

if myPet1 == myPet3 {
    print("같은 Pet임")
}

if myPet1 != myPet2 {
    print("다른 Pet임")
}


Zip

zip 은 두 콜렉션을 묶을 때 사용한다.

let friends = ["철수", "영희", "짱구"]
let pets = ["고양이", "강아지"]

// 두 콜렉션을 묶고 싶을때 사용 - 크기가 같아질 때 까지
let friendAndPetPairs = zip(friends, pets)

for aPair in friendAndPetPairs {
    print("\(aPair.0), \(aPair.1)")
//    철수, 고양이
//    영희, 강아지
}


Range

Range 를 통해 변수를 범위로 만들 수 있고, 원하는 범위만큼 반복을 수행할 수 있다.

import UIKit

let myRange = 0...2 // 0, 1, 2
let mySecondRange = 0..<2 // 0, 1
let myThirdRange = 0...Int.max

let myFriends = ["철수", "짱구", "맹구", "윤수"]

myFriends[mySecondRange] // ["철수", "짱구"]

if mySecondRange.contains(2) {
    print("conatin")
} else {
    print("not contain")
}


Open

open class 는 외부 모듈에 있는 것을 상속 받기 위해 사용한다.

// 외부 모듈에 있는 것을 상속 받기 위해 사용하는 것 - open
open class Utils {
    open class func sayHello() {
        print("안녕하세요~~")
    }
}

struct 기본 생성자

struct 는 생성자 메소드가 자동 탑재되어 있다. struct 안에서 생성자를 따로 지정 가능하지만 extension 으로 빼서 기본 생성자 지정이 가능하다

struct Pet {
    var name: String
}

// struct 는 생성자 메소드가 자동 탑재되어 있음
// struct 안에서 생성자를 따로 지정 가능하지만
// extension 으로 빼서 기본 생성자 지정이 가능함

extension Pet {
    init(){
        name = "고양이"
    }
}

let myPet = Pet() // 고양이
let myCat = Pet(name: "강아지") // 강아지


Singleton 패턴

싱글톤 패턴은 디자인 패턴 중 하나로, 객체 생성 디자인 패턴 중 하나이며, 가장 유명하다.

메모리를 하나만 사용하여 객체를 생성한다는 의미이다.

import UIKit

// 한 메모리 공간에 값을 올릴 수 있다.
 final class Pet {
    static let shared = Pet()
    private init() {}

    var name: String = "고양이"
}


Pet.shared.name = "강아지"
// myCat과 myDog 는 메모리 주소가 같아진다.
let myCat = Pet.shared
myCat.name
let myDog = Pet.shared


Toggle

toggle 이란 Bool 값을 간단하게 스위칭할 수 있도록 하는 메소드이다.

import UIKit

var isDarkMode : Bool = false

isDarkMode.toggle() // true


프로토콜 조건 적용

protocol 에 조건을 적용하여 지정한 클래스의 메소드만 사용 가능하도록 만들 수 있다.

import UIKit

protocol Naming{
    var name: String {get set}
}

class Cat : Naming {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Dog : Naming {
    var name: String
    init(name: String) {
        self.name = name
    }
}

extension Naming  {
    func sayName() where Self : Cat {
        print("\(self.name) 냥냥")
    }
    func sayName() where Self : Dog {
        print("\(self.name) 멍멍")
    }
}

let myDog = Dog(name: "멍멍이")
let myCat = Cat(name: "야옹이")
myCat.sayName()
myDog.sayName()


자료형 체크

자료형 체크는 is 로 할 수 있다.

if , guard, if case, switch 등으로 자료형을 체크할 수 있다.

import UIKit

class Cat {}
class Dog {}

let myCat = Cat()
let myDog = Dog()

if myCat is Cat {
    print("고양이다")
}

func checkIfSheIsCat () {
    guard myCat is Cat else {
        print("고양이가 아니다")
        return
    }
    print("고양이다")
}

checkIfSheIsCat()

switch myCat {
case is Cat:
    print("고양이다")
default :
    print("고양이 아니다")
}

if case is Cat = myCat {
    print("고양이다")
}

func checkIfSheIsDog(){
    guard case is Dog = myDog else {
        print("강아지가 아니다")
        return
    }
    print("강아지다")
}

checkIfSheIsDog()


2023년 07월 20일에 수정됨
YUNSU BAE

YUNSU BAE

주니어 웹 개발자 배윤수 입니다!

예술의 영역을 동경하고 있어요. 🧑‍🎨