Swift 기본 문법 ⓕ (Structure & Class, Method & Subscript, Extension)

krystal·2022년 10월 10일
0

Swift 기초

목록 보기
6/11
post-thumbnail

The Swift Language Guide (번역본)

Structure & Class

클래스과 구조체 (Classes and Structures)

클래스와 구조체는 프로그램의 코드를 조직화 하기 위해 일반적으로 사용.
(OOP(객체지향 프로그래밍)를 위한 필요 요소이기도 함)

Swift는 다른 언어와 다르게 interface 파일과 implementation 파일을 분리해서 만들지 않아도 됨
하나의 파일에 구조체나 클래스를 정의하면, Swift가 자동으로 알아서 해당 클래스와 구조체를 사용할 수 있는 인터페이스를 생성해준다.

클래스와 구조체의 비교

(c언어에서도 클래스와 구조체의 비교하는 내용을 배웠던 기억이 난다.)
Swift에서 클래스와 구조체는 많은 공통점이 있다.

📌 공통점

  1. 값을 저장하기 위한 프로퍼티 정의
  2. 기능을 제공하기 위한 메소드 정의
  3. subscript 문법을 이용해 특정 값을 접근할 수 있는 subscript 정의
  4. 초기 상태를 설정할 수 있는 initializer 정의
  5. 기본 구현에서 기능 확장
  6. 특정한 종류의 표준 기능을 제공하기 위한 프로토콜 순응(conform)

📌 클래스(Only)

  1. 상속 (Inheritance) : 클래스의 여러 속성을 다른 클래스에 물려 줌
  2. 타입 캐스팅 (Type casting) : 런타임에 클래스 인스턴스의 타입을 확인
  3. 소멸자 (Deinitializers) : 할당된 자원을 해제(free up) 시킴
  4. 참조 카운트 (Reference counting) : 클래스 인스턴스에 하나 이상의 참조가 가능

선언 문법 (Definition Syntax)

클래스와 구조체 둘다 비슷한 선언 문법을 갖고 있다.
클래스는 class 키워드를 구조체는 struct 키워드를 이름 앞에 적어서 선언할 수 있다.

class SomeClass {
    // 클래스 내용
}
struct SomeStructure {
    // 구조체 내용
}

📍 클래스, 구조체 : UpperCamelCase(SomeClass, SomeStructure 등)으로 선언
📍 프로퍼티, 메소드 : lowerCamelCase(frameRate, incrementCount 등)으로 선언합니다.

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()  // 위 Resolution 구조체를 값으로 사용
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

구조체 Resolution의 프로퍼티 width 와 height는 초기 값으로 0을 할당 했기 때문에
Swift의 타입추론에 의해 자동으로 Int형을 갖게 된다.

클래스와 구조체 인스턴스 (Class and Structure Instances)

클래스와 구조체 이름 뒤에 빈 괄호를 적으면 각각의 인스턴스를 생성할 수 있다. (기존 다른 언어와 동일)

let someResolution = Resolution()    // 구조체 인스턴스 생성
let someVideoMode = VideoMode()    // 클래스 인스턴스 생성

프로퍼티 접근 (Accessing Properties)

점(dot) 문법을 통해 클래스/구조체 인스턴스의 프로퍼티에 접근 & 할당할 수 있다.

print("The width of someResolution is \(someResolution.width)")

// "The width of someResolution is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")

// "The width of someVideoMode is now 1280"

구조체형의 멤버 초기화 (Memberwise Initializers for Structure Types)

모든 구조체는 초기화시 프로퍼티를 선언할 수 있는 초기자를 자동으로 생성해 제공.
아래와 같은 맴버의 초기화는 구조체 안에 width와 height 프로퍼티만 정의했다면 자동으로 사용 가능하다는 의미

let vga = Resolution(width: 640, height: 480)

구조체와 열거형은 값 타입 (Structures and Enumerations Are Value Types)

값 타입이라는 뜻은, 이것이 함수에서 상수나 변수에 전달될 때 그 값이 복사되서 전달 된다는 의미.
Swift에서 모든 구조체와 열거형 타입은 값 타입.

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

구조체의 인스턴스 hd를 선언 후 hd를 cinema라는 변수에 할당.
이 cinema와 hd는 같은가? => X

할당하는 순간 복사(copy)되기 때문에 cinema와 hd는 같지 않고 완전히 다른 인스턴스.


열거형에서의 동작도 마찬가지 입니다.

enum CompassPoint {
    case north, south, east, west
}

var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection = .east

if rememberedDirection == .west {
    print("The remembered direction is still .west")
}
// "The remembered direction is still .west" 출력
  1. 현재 방향 currentDirection에 west를 할당
  2. 기억한 방향 rememberedDirection에 현재 방향 currentDirection을 저장
  3. 현재 방향 currentDirection을 east로 변경합니다.
  4. 기억하고 있는 인스턴스 rememberedDirection => 원본을 복사할 때의 값 west를 갖고 있음

즉, 다른 인스턴스의 변화는 그 인스턴스에만 영향을 끼치고 그것과 가른 인스턴스에는 아무런 영향도 없다.



클래스는 참조 타입 (Classes Are Reference Types)

값 타입과 달리 참조 타입은 변수나 상수에 값을 할당을 하거나 함수에 인자로 전달할 때
그 값이 복사되지 않고 참조 된다.

참조된다는 의미는 그 값을 갖고 있는 메모리를 바라보고 있다는 뜻

let tenEighty = VideoMode()

tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

//tenEighty라는 VideoMode 클래스 인스턴스를 생성하고 각 프로퍼티에 값을 할당

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// "The frameRate property of tenEighty is now 30.0" 출력



식별 연산자 (Identity Operators)

클래스는 참조 타입이기 때문에 여러 상수와 변수에서 같은 인스턴스를 참조할 수 있다.
상수와 변수가 같은 인스턴스를 참조하고 있는지 비교하기 위해 식별 연산자를 사용한다.

=== : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 (True)
!== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 (True)

if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// "tenEighty and alsoTenEighty refer to the same VideoMode instance."

식별 연산자(===)는 비교 연산자(==)와 같지 않다.
식별 연산자는 참조를 비교하는 것이고, 비교 연산자는 값을 비교.



클래스와 구조체의 선택 (Choosing Between Classes and Structures)

📌 일반적으로 다음의 조건 중 1개 이상을 만족하면 구조체를 사용

  1. 구조체의 주 목적이 관계된 간단한 값을 캡슐화(encapsulate) 하기 위한 것인 경우
  2. 구조체의 인스턴스가 참조되기 보다 복사되기를 기대하는 경우
  3. 구조체에 의해 저장된 어떠한 프로퍼티가 참조되기 보다 복사되기를 기대하는 경우
  4. 구조체가 프로퍼티나 메소드 등을 상속할 필요가 없는 경우

Swift에서는 String, Array, Dictionary 같은 기본 데이터 타입이 구조체로 구현 되어있다.
값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 값이 복사 된다는 뜻이다.

반면 Foundation의 NSString, NSArray, NSDictionary는 클래스로 구현 되어있다.
그래서 데이터들은 항상 할당 되거나 전달될 때 복사 되지 않고 참조가 사용된다.


Method & Subscript

Method

특정 타입의 클래스, 구조체, 열거형과 관련된 함수를 메소드라 한다.

1. 인스턴스 메소드 : 특정 타입의 인스턴스에서 실행할 수 있는 메소드
2. 타입 메소드 : 특정 형과 관련된 메소드

타입 메소드는 Objective-C에서 클래스 메소드와 유사

Swift에서 메소드가 C나 Objective-C 메소드 간의 가장 큰 차이는
바로 Objective-C에서는 클래스 타입에서만 메소드를 선언할 수 있다는 것 입니다.

반면 Swift에서는 클래스 타입 뿐만아니라 구조체, 열거형에서도 메소드를 선언해 사용할 수 있습니다.

ⓐ 인스턴스 메소드

특정 클래스, 구조체, 열거형의 인스턴스에 속한 메소드이다.
이 메소드를 통해 인스턴스 내의 값을 제어하거나 변경할 수 있다.

인스턴스 메소드는 이름 그대로 그 인스턴스가 속한 특정 타입의 인스턴스에서만 실행 가능.

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}


let counter = Counter()
// 초기 count 값은 0
counter.increment()
// count 값이 1로 변경
counter.increment(by: 5)
// count 값은 6
counter.reset()
// count 값은 0

모든 프로퍼티는 암시적으로 인스턴스 자체를 의미하는 self라는 프로퍼티를 갖는다.
인스턴스 메소드 안에서 self프로퍼티를 이용해 인스턴스 자체를 참조하는데 사용할 수 있다.

func increment() {
    self.count += 1
}

self키워드를 이용해 파라미터와 프로퍼티의 모호함을 명확하게 할 수 있다.

struct Point {

    var x = 0.0, y = 0.0
    
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x  
        // self.x를 이용해 프로퍼티 x와 인자 x를 구분
    }
}

let somePoint = Point(x: 4.0, y: 5.0)

if somePoint.isToTheRightOf(x: 1.0) {
    print("This point is to the right of the line where x == 1.0")
}

// "This point is to the right of the line where x == 1.0" 출력



ⓐ - 1 인스턴스 메소드 내에서 값 타입 변경 (Modifying Value Types from Within Instance Methods)

메소드에 mutating붙여 주면 가능.
mutating이라는 키워드가 붙은 메소드에서 메소드의 계산이 끝난 후,
원본 구조체에 그 결과를 덮어 써서 그 값을 변경.

struct Point {

    var x = 0.0, y = 0.0
    
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)

print("The point is now at (\(somePoint.x), \(somePoint.y))")
// "The point is now at (3.0, 4.0)" 출력


let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// let으로 선언해서 값 변경 시도시 에러 발생!



ⓐ - 2 Mutating 메소드 내에서 self 할당 (Assigning to self Within a Mutating Method)

Mutating메소드에서 self프로퍼티를 이용해 완전 새로운 인스턴스를 생성할 수 있다.

struct Point {
    var x = 0.0, y = 0.0
    
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
    
        self = Point(x: x + deltaX, y: y + deltaY)
        
    }
}



enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight 값은 .high
ovenLight.next()
// ovenLight 값은 .off



ⓑ 타입 메소드 (Type Methods)

인스턴스 메소드는 특정 타입의 인스턴스에서 호출되고, 타입 메소드는 특정 타입 자체에서 호출해 사용.
타입 메소드의 선언은 메소드 키워드 func앞에 static혹은 class키워드를 추가

📌 static메소드와 class메소드의 차이점

  1. static 메소드 : 서브클래스에서 오버라이드 할 수 없는 타입 메소드
  2. class 메소드 : 서브클래스에서 오버라이드 할 수 있는 타입 메소드 라는 것입니다.

타입 메소드도 인스턴스 메소드와 같이 점(.) 문법으로 호출할 수 있다
타입 메소드 안에서도 self키워드를 사용할 수 있다.
☞ 하지만 타입 메소드에서의 self는 인스턴스가 아니라 타입 자신을 의미
타입 메소드 안에서 다른 타입 메소드를 사용하는 것이 가능


Subscript

클래스, 구조체 그리고 열거형에서 스크립트를 정의해 사용할 수 있다.
서브스크립트란 콜렉션, 리스트, 시퀀스 등 집합의 특정 멤버 엘리먼트에 간단하게 접근할 수 있는 문법

선언 문법은 인스턴스 메소드와 계산된 프로퍼티를 선언하는 것과 비슷
서브스크립트는 읽고-쓰기(read-write) 혹은 읽기 전용(read only)만 가능하며
정의는 계산된 프로퍼티 방식과 같이 setter, getter 방식을 따른다.

subscript(index: Int) -> Int {
    get {
        // 적절한 반환 값
    }
    set(newValue) {
        // 적절한 set 액션
    }
}

<읽기 전용 서브스크립트 예>

struct TimesTable {

    let multiplier: Int
    
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}

let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// "six times three is 18" 출력

<서브 스크립트 사용>

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

numberOfLegs값은 타입 추론에 의해 [String: Int]형을 갖는다.
numberOfLegs["bird"] = 2는 사전형 변수 numberOfLegs에 key로 bird를 그 값은 2를 넣으라는 서브스크립트 문법이다.

<서브스크립트 옵션 (Subscript Options)>

  1. 서브스크립트는 입력 인자의 숫자에 제한이 없고, 입력 인자의 타입과 반환 타입의 제한도 없다.
  2. in-out 인자(in-out parameter)나 기본 인자 값(default parameter value)을 제공할 수 없다.
  3. 서브스크립트는 오버로딩도 허용 => 그래서 인자형, 반환형에 따라 원하는 수 만큼의 서브스크립트를 선언 가능
struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}



Extension

익스텐션을 이용해 클래스, 구조체, 열거형 혹은 프로토콜 타입에 기능을 추가할 수 있다
익스텐션은 extension 키워드를 사용해 선언

📌 Swift에서 익스텐션을 이용해 사용할 수 있는 것

  1. 계산된 인스턴스 프로퍼티와 계산된 타입 프로퍼티의 추가
  2. 인스턴스 메소드와 타입 메소드의 추가
  3. 새로운 이니셜라이저 제공
  4. 서브스크립트 정의
  5. 중첩 타입의 선언과 사용
  6. 특정 프로토콜을 따르는 타입 만들기
    (+) 익스텐션은 타입에 새 기능을 추가할 수 있지만 오버라이드는(override)는 불가
extension SomeType {
    // new functionality to add to SomeType goes here
}

//현재 존재하는 타입에 한개 이상의 프로토콜을 따르도록 확장할 수 있다
extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

익스텐션을 정의하여 존재하는 타입에 새 기능을 추가하면그 기능은 익스텐션을 정의하기 이전에
생성한 인스턴스를 포함한 존재하는 모든 해당 타입의 인스턴스에서 사용 가능하다.

연산 프로퍼티 추가

extension Double {
    var km: Double { return self 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// Prints "One inch is 0.0254 meters"

let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// Prints "Three feet is 0.914399970739201 meters"


let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"

이니셜라이저 추가

익스텐션을 이용해 존재하는 타입에 새로운 이니셜라이저를 추가가능
struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
}


extension Rect {
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}


let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

메서드 추가

익스텐션을 이용해 존재하는 타입에 인스턴스 메소드나 타입 메소드를 추가할 수 있다

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

repetitions(task:) 메소드는 () -> Void 타입의 하나의 인자를 받고 파라미터와 반환 값이 없는 함수

3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!
profile
https://source-coding.tistory.com/

0개의 댓글