[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/Swift

[Swift] 기초 문법 정리

inu 2021. 7. 18. 00:57
반응형


optional

nil값과 optional이란

  • nil : 아예 값이 존재하지 않음을 의미
  • swift는 기본적으로 변수(상수)에 nil값을 보관하는 것을 허용하지 않는다.
  • 따라서 이를 보관하기 위해 사용되는 것이 optional이다.
  • optional은 말 그대로 변수의 값이 nil일 수도 있음을 의미한다.
enum Optional<Wrapped>: ExpressibleByNiliteral {
         case none
         case some(Wrapped)
}
  • optional은 enum이다. '값이 존재하지 않음' 혹은 '특정 값을 wrap해놓은 값' 두가지를 가지는 것이다.

optional 사용

var optionalValue: Int? = 100
optionalValue = nil
  • ? : 기본 옵셔널. 변수의 타입 뒤에 표현하며, 변수가 nil값을 포함할 수도 있음을 의미한다.
  • 하지만 변수처럼 자유롭게 사용은 불가능하다. (optionalValue = optionalValue + 1과 같은 실행 불가능)
var implicitlyUnwrappedOptionalValue: Int! = 100
implicitlyUnwrappedOptionalValue = nil
  • ! : 암시적 추출 옵셔널. 변수의 타입 뒤에 표현하며, 변수가 nil값을 포함할 수도 있음을 의미한다.
  • 변수처럼 자유롭게 사용이 가능하다. (optionalValue = optionalValue + 1과 같은 실행 가능)
  • 다만 이는 nil값이 아닐 것임에 대한 확신이 있을 때 사용한다. nil값을 포함할 수는 있지만 값을 항상 추출하여 변수처럼 사용하기 때문에 nil값이 존재할 경우 런타임 에러가 발생한다.

optional unwrapping

var number:Int? = 20
print(number!)
  • optional 변수를 unwrapping하여 사용할 수 있도록 한다.
  • 만약 nil값이 존재하는 변수였을 경우 바로 런타임 에러가 발생한다.
  • cf. !으로 처음으로 암시적 추출 옵셔널을 사용했을 경우 이를 수행하지 않아도 바로 변수처럼 사용이 가능하다.

optional binding

  • 옵셔널에 들어있는 값을 확인하고 추출하는 것이다.
var myName: String? = nil
var yourName: String! = nil

if let name: String = myName {
    print(name)
} else {
    print("myName is nil")
}

if let name = myName, let friend = yourName {
    print("\(name) and \(friend)")
}
  • if-let 방식 : if문과 let을 바로 연이어 사용. 옵셔널 안에 값이 있는지 확인하고 있으면 해당 값을 꺼내온다.
  • 값이 존재하지 않을 경우(nil) else문을 수행한다.
  • 이렇게 설정한 상수는 if-let 구문 내부에서만 사용이 가능하다.

optional chaining

  • optional값 내부에 연속해서 optional값이 존재할 때 유용하게 사용된다.
guardJob = man?.home?.guard?.job
  • man, home, guard가 모두 optional값이라고 가정했을 때 위와 같은 방식으로 전개해 값을 가져올 수 있다.
  • 하나씩 접근하다 중간에 nil값이 존재하면 접근을 멈추고 nil값을 반환한다.
guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
  • nil값이 반환되려 할 때 특정값이 대신 반환되도록 할 수 있다. 위의 경우 nil값이 반환되려 할 경우 대신 "슈퍼맨"이 반환된다.

class 메서드

  • swift에서 static 메서드는 다른 프로그래밍 언어와 같이 인스턴스를 생성하지 않고 바로 사용할 수 있는 메서드이다.
  • 다만 이런 static 메서드는 상속 시 재정의가 불가능하다.
  • 인스턴스를 생성하지 않고 바로 사용할 수 있지만 상속 시 재정의가 가능한 메서드를 class 메서드라고 한다.
class func classMethod() {
    print("type method - class")
}
  • class 내부에서 위와 같이 작성하여 정의한다.
  • cf. 구조체와의 차이 : 클레스는 참조타입(데이터를 전할 때 메모리 위치전달)이고 구조체는 값타입(데이터를 전할 때 값을 복사) / 클레스는 상속이 가능하지만 구조체는 불가능

enum

enum Weekday {
    case mon
    case tue
    case wed
    case thu, fri, sat, sun
}
  • 유사한 종류의 여러 값을 한 곳에 모아 정의한 것.
  • enum 자체가 하나의 데이터타입이 된다. (대문자 카멜케이스)
  • 각 case는 고유의 값이 된다. (소문자 카멜케이스)
  • 한 줄에 여러개도 정의될 수 있다.
var day: Weekday = Weekday.mon
day = .tue
  • enum 타입과 케이스를 사용해 값을 정의할 수 있다.
  • 타입이 정해진 변수라면 케이스만 표현해도 괜찮다.

클로저

{ (매개변수 목록) -> 반환타입 in
    실행 코드
}
  • 매개변수와 반환 값이 존재할 수 있는 실행가능한 코드 블럭
  • 이름을 필요로 하지 않는다.
  • 함수는 일종의 이름있는 클로저이다.
let add: (Int, Int) -> Int
add = { (a: Int, b: Int) in
    return a + b
}

func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
    return method(a, b)
}

var calculated: Int
calculated = calculate(a: 50, b: 10, method: add)
  • 이는 그대로 상수에 할당해 전달인자로 사용할 수 있다.
calculated = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
    return left + right
}
  • 후행클로저 : 클로저가 함수의 마지막 전달인자일 경우 이름을 생략한 후 외부에 클로저를 구현할 수 있다.
calculated = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
    return left + right
})
  • 이미 반환값이 정해져있는 경우 반환값 생략이 가능하다.
calculated = calculate(a: 10, b: 10, method: {
    return $0 + $1
})
  • 매개변수의 이름도 생략하고 $0,$1,$2,... 로 표현할 수 있다.
calculated = calculate(a: 10, b: 10) {
    $0 + $1
}
  • 반환값이 존재하는 클로저의 경우 마지막줄은 암시적으로 반환값으로 취급된다.

프로퍼티

  • swift에는 일반적인 프로퍼티와 다르게 연산 프로퍼티라는 개념이 존재한다.

연산 프로퍼티

struct Person {
    var koreanAge: Int = 0
    var westernAge: Int {
        get {
            return koreanAge - 1
        }

        set(inputValue) {
            koreanAge = inputValue + 1 
        }
    }

    var selfIntroduction: String {
        get {
            return "저는 \(koreanAge)살 입니다"
        }
    }

    static var selfIntroduction: String {
        return "사람입니다"
    }
}
  • 내부적으로 get, set을 가지며 저장 프로퍼티를 기반으로 연산하여 값을 저장 및 반환한다.
  • 읽기전용 연산 프로퍼티도 가능하며, 이 경우 get블럭은 생략할 수 있다.
  • 프로퍼티는 구조체,클래스,열거형 내부에 구현가능 (열거형 내부에는 연산프로퍼티만)

프로퍼티 감시자

struct Money {
    var currencyRate: Double = 1100 {
        willSet(newRate) {
            print("환율이 \(currencyRate)에서 \(newRate)으로 변경될 예정입니다")
        }

        didSet(oldRate) {
            print("환율이 \(oldRate)에서 \(currencyRate)으로 변경되었습니다")
        }
    }

    var dollar: Double = 0 {
        willSet {
            print("\(dollar)달러에서 \(newValue)달러로 변경될 예정입니다")
        }
        didSet {
            print("\(oldValue)달러에서 \(dollar)달러로 변경되었습니다")
        }
    }   
}
  • 특정 프로퍼티가 변경될때마다 특정 작업을 수행하도록 할 수 있다.
  • cf. 연산 프로퍼티에는 프로퍼티 감시자를 사용할 수 없다.

캐스팅

class Person {
}

class Student: Person {
}

class UniversityStudent: Student {
}
  • 예시에 사용할 클래스

업캐스팅

var mike: Person = UniversityStudent() as Person
  • 부모클래스의 인스턴스로 사용하고 싶은 경우 as를 활용해 업캐스팅을 진행할 수 있다.

다운캐스팅

var optionalCasted: Student?
optionalCasted = mike as? UniversityStudent
optionalCasted = Person() as? UniversityStudent // nil

var forcedCasted: Student
forcedCasted = mike as! UniversityStudent
  • as?를 통해 자식클래스로 다운캐스팅을 진행할 수 있다.
  • 캐스팅을 할 수 없는 경우 nil값을 반환한다. (결과의 타입은 옵셔널이어야함)
  • as!를 통해 강제적으로 다운캐스팅을 진행할 수 있다.
  • 캐스팅에 실패할 경우 바로 런타임오류가 발생한다.

assert

  • 예상하는 조건의 검증을 위해 사용된다.
  • 디버깅모드에서만 작동하며, 배포하는 애플리케이션에서는 제외된다.
var someInt: Int = 0

assert(someInt == 0, "someInt != 0")
  • 조건에 부합할 경우 지나가고, 실패할 경우 동작이 중지되고 설정한 메세지를 출력한다.

guard

func functionWithGuard(age: Int?) {

    guard let unwrappedAge = age,
        unwrappedAge < 130,
        unwrappedAge >= 0 else {
        return
    }

    print("당신의 나이는 \(unwrappedAge)세입니다")
}

var count = 1

while true {
    guard count < 3 else {
        break
    }
    print(count)
    count += 1
}
  • 잘못된 값 전달 시 실행 구문을 바로 종료한다.
  • else 블럭 내에 코드블럭을 종료하는 지시어(break,return 등)이 존재해야 한다.
guard let unwrapped: Int = someValue else {
         return 
}
unwrapped = 3
  • 옵셔널바인딩에도 사용가능
  • if-let 바인딩과는 다르게 외부에서도 사용이 가능하다는 장점이 있다.

프로토콜

protocol Talkable {

    // 프로퍼티 요구
    var topic: String { get set }
    var language: String { get }

    // 메서드 요구
    @objc optional func talk()

    // 이니셜라이저 요구
    init(topic: String, language: String)
}
  • java의 interface와 유사한 기능이다.
  • 다만 protocol은 초기값 설정이 허용되지 않으며, optional이 붙어있는 메서드는 구현하지 않아도 되고, 정적멤버 선언이 가능하다는 차이점이 있다. (java의 interface는 초기값 설정이 가능하고, 모든 메서드를 구현해야하며, 정적멤버 선언이 불가능하다.)
  • 상속형태로 클래스에서 받아와 작성한다. 단, 클래스와는 다르게 다중 상속이 가능하다.
someAny is Talkable 

if let someAny: Talkable = someAny as? Talkable {
    someAny.talk()
}
  • isas를 통해 인스턴스가 특정 프로토콜이 준수하는지 확인할 수도 있다.

익스텐션

  • extension을 통해 특정 클래스(혹은 자료형)에 새로운 기능을 추가할 수 있다.

연산 프로퍼티 추가

extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
    var isOdd: Bool {
        return self % 2 == 1
    }
}

print(1.isEven) // false
print(2.isEven) // true
print(1.isOdd)  // true
print(2.isOdd)  // false

메서드 추가

extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}
print(3.multiply(by: 2))  // 6
print(4.multiply(by: 5))  // 20

이니셜라이저 추가

extension String {
    init(int: Int) {
        self = "\(int)"
    }

    init(double: Double) {
        self = "\(double)"
    }
}

let stringFromInt: String = String(int: 100) 
// "100"

Generic

제네릭 사용

class Name<T> {
}
struct Name<T> {
}
enum Name<T> {
}
func Name<T> {
}
  • 제네릭 타입을 선언하여 내부에서 사용하는 타입을 일반화할 수 있다.
  • 클래스, 구조체, 열거형, 함수에 적용할 수 있다.
  • 이는 자바의 제네릭과 유사한 개념이라고 볼 수 있다.

타입 제약 (Type Constraints)

  • 하지만 해당 타입은 컴파일 전에는 정확히 알 수 없다.
  • 제네릭으로 들어올 타입에 제약을 걸어 들어올 수 있는 타입을 제한하는 것을 타입 제약이라고 한다.
  • 이를 통해 어떤 타입이 들어올지 알 수 없는 제네릭에도 특정 작업을 수행할 수 있게 된다.
func makeSet<T: Comparable>(inputArray: [T]) -> [T] {
    var result = [T]()
    for element in inputArray {
        if !result.contains(element) {
            result.append(element)
        }
    }
    return result.sorted()
}
  • 예를 들어 위와 같은 함수의 경우, !result.contains(element)에서 Equatable 제약이 필요하고, result.sorted()에서 Comparable 제약이 필요하다. 하지만 Comparable 제약을 만족하면 Equatable 제약도 만족하므로, Comparable 제약만 존재하면 된다.
  • 추가적으로 제약을 필요로 할 경우 함수 선언 부 뒤에 func makeSet<T: Comparable>(inputArray: [T]) -> [T] where T: FloatingPoint와 같이 추가하면 된다.
반응형

'🍎 Apple > Swift' 카테고리의 다른 글

[Swift] 문자열 처리  (0) 2022.01.22
[Swift] NSCache와 NSDictionary의 차이점  (0) 2022.01.22
[Swift] UserDefaults와 Codable, NSCoding  (2) 2022.01.01
[Swift] Dynamic Dispatch 줄이기  (0) 2021.12.16
[Swift] Codable Protocol  (2) 2021.09.02