Programming/Mac & iOS

[Swift] Swift Study 3주차 요약 (ARC, Extension, Generics, Operators ... etc)

MB Brad KWON 2014. 8. 25. 11:00
Deinitializer
리소스를 가지고 작업을 하다가 이를 정리하기 위한 작업을 정의 하려면 Deinitialization을 이용한다. 'deinit'은 객체가 deallocation이 되기 전에 호출하기 때문에 객체의 모든 property에 접근이 가능하다.

ex)
deinit {
    // perform the deinitialization
}

Automatic Reference Counting (memory management)
Automatic Reference Counting(이하 'ARC')는 각 객체를 가리키는 refenece의 개수를 세는 것을 의미한다. 해당 객체를 가리키는 refenece의 개수가 '0'이 되면 객체를 해제한다. ARC는 이와 같은 방식으로 Mac/iOS 내에서의 memory 관리 기법으로 활용된다. 일반적으로 ARC를 사용할 때는 애플이 항상 강조하듯이 'just works'라고 생각하고 쓰는 것이 건강에 좋다. 하지만 'Strong refence cycle'이라는 상황이 발생하여 memory의 누수를 일으킬 수 있다. 그럼 Strong reference cycle이 발생하는 상황과 피할 수 있는 방법에 대해 알아보자.

Strong reference cycle between class instances
객체끼리 서로 참조하는 상황에서 Strong reference cycle(이하 'SRC')이 발생하기 쉽다. 아래의 그림을 보자.


위의 그림과 같이 각 객체가 서로 참조중인 상황이라고 보면 각 객체는 '2'라는 참조 개수를 갖는다. 각 객체를 다 사용한 뒤, 각 reference를 해제해보자. 


ex)

var john = nil

var number73 = nil



사용을 다 사용한 뒤, 각 객체를 nil로 해제했다. 하지만 각 객체는 서로 참조중인 참조 개수 '1'이 유지가 된다. 실제로 객체에 접근하기 위해 필요한 reference들은 없지만 메로리상에서 객체들은 해제가 안 된 상태이다. 위의 상황을 SRC라고 부르며 지금은 각 객체간에 발생했다. 그럼 이를 피하기 위한 방법을 알아보자.

Weak reference
weak reference는 기존에도 제공하던 방식으로 reference를 추가하더라도 reference count를 증가시키지 않음으로써 SRC를 피하는 방법이다. swift에서도 이와 같은 방식을 이용할 수 있다. weak reference를 사용하면 아래의 그림과 같이 SRC를 피할 수 있다. weak reference는 아래와 같이 사용 가능하다. 참고로 'no value'를 허용할 수 없기 때문에 constant에 사용할 수 없다.

ex)
weak var tenant: Person?


하지만 swift에서는 optional/non-optional 개념이 추가되었다. weak reference는 'no value'를 허용하기 때문에 optional에 대해서 사용할 수 있으며 non-optional에 대해서는 다음 설명할 unowned reference를 사용해야한다.

Unowned References
unowned reference는 weak reference와 같이 referenece count를 증가시키지 않지만 weak reference와 다르게 'no value'를 허용하지 않는다. 그래서 non-optional에 대해 사용된다. unowned reference를 사용하면 아래의 그림과 같이 SRC를 피할 수 있다. unowned reference는 아래와 같이 사용한다.

ex)
unowned let customer: Customer


위에서 배운 weak과 unowned를 사용하는 시점을 아래와 같이 표로 정리했다. 참고하면 좋다.

 

Optional

non-Optional

Optional

strong reference &

weak reference 

 strong reference & 

unowned reference

non-Optional

strong reference &

unowned reference 

 unowned reference & 

implicitly unwrapped optional property


implicitly unwrapped optional property는 optional이 아님을 명시적으로 선언부에 '!'를 표시함으로써 알려준 property를 말한다. 기억이 안 난다면 1주차 요약으로 돌아가서 다시 한번 확인한다.

Strong reference cycle for closures
closure에서도 위와 같은 SRC가 발생할 수 있다. 아래의 클래스를 보자.

ex)
class HTMLElement {
    
    let name: String
    let text: String?
    
    @lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        println("\(name) is being deinitialized")
    }
    
}

위에서 보면 HTMLElement의 asHTML이라는 closure가 호출되면 closure 내부에서 self.name과 self.text를 참조하면 capturing을 한다. capturing 때문에 SRC가 발생한다. 사용이 끝난 HTMLElement 객체를 nil로 만들면 아래의 그림 같이 된다.


이를 해결하기 위해서 weak과 unowned를 앞서 배웠다. closure에서는 'capture list'로 해결한다. 'capture list'는 closure 내부에서 capture할 때 weak로 혹은 unowned 참조할지를 결정한다. syntax는 아래와 같다.

ex)
var someClosure: () -> String = {
    [weak or unowned self] in
    // closure body goes here
}

그럼, weak를 사용할지 unowned를 사용할지는 어떻게 확인할까? apple에서 말하는 것을 따르자면 객체와 closure가 서로를 항상 참조를 하면서 동시에 해제되는 관계일 경우엔 unowned를 쓰라고 한다. 반대로 객체와 closure가 서로 참조되는 시점이 다르고 해제 되는 시점도 달라 서로의 참조값이 'nil'이 될 소지가 있다면 weak을 쓰라고 한다. 

위의 경우엔 closure는 HTMLElement 클래스 안에 정의되어있어 self를 참조하기 때문에 동시에 정의되어 생성되고 서로를 참조하다가 동시에 해제되기 때문에 unowned를 사용하여 아래의 그림과 같이 SRC를 피한다.


Optional chaining
optional chaning은 간단하게 말해서 property, method, subscript 등을 호출할 때 nil을 가진 optional인지를 확인하는 것이다. 이는 forced unwrapping(!)으로 접근하는 방법의 대안으로 사용이 가능하다. 차이점이 있다면 forced unwrapping은 값이 nil일 경우,  runtime error를 발생시키며 optional chaining은 nil일 경우, 실패한 부분을 수행하지 않는다. optional chaining을 사용한 코드를 보자면 다음과 같다.

ex)
if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."


중요한 점은 optional chaining을 사용한뒤에 등장하는 객체들은 모두 optional처럼 다뤄진다는 사실이다. 위의 예제에서도 'numberOfRooms'가 optional인지 non-optional인지 상관 없이 optional처럼 optional binding을 사용하여 다뤄지고 있다. 뒤에 오는 것이 optional처럼 다뤄지기 때문에 아래와 같은 코드도 나올 수 있다.

ex)
if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
        println("John's uppercase building identifier is \(upper).")
}
// prints "John's uppercase building identifier is THE LARCHES."

Type casting
type casting은 크게 3개의 키워드를 기억하자. 'is', 'as?', 'as'이다. is는 해당 클래스의 서브 클래스인지를 체크하는 키워드이다. 해당 객체가 서브 클래스가 맞다면 true를 리턴하고 아니라면 false를 리턴한다. as?는 optional value를 리턴한다. 
type casting의 성공 여부가 불확실한 상황에서 type casting이 실패할 경우, nil을 리턴한다. 반면에 as는 type casting이 항상 성공할 경우에 사용한다. 만약에 type casting이 실패할 경우, 런타임 에러를 발생시킨다.

AnyObject & Any
AnyObject는 어떠한 class type이든 하나의 type의 객체를 표현 가능하다. objective-C의 id와 같은 존재이다. Any는 function type 외의 각기 다른 모든 type의 객체를 표현 가능하다.

ex)
let someObjects: AnyObject[] = [
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
    Movie(name: "Moon", director: "Duncan Jones"),
    Movie(name: "Alien", director: "Ridley Scott")
]

for movie in someObjects as Movie[] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott

/************************************************************/

var things = Any[]()
 
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
‘for thing in things {
    switch thing {
    case 0 as Int:
        println("zero as an Int")
    case 0 as Double:
        println("zero as a Double")
    case let someInt as Int:
        println("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        println("a positive double value of \(someDouble)")
    case is Double:
        println("some other double value that I don't want to print")
    case let someString as String:
        println("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        println("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        println("a movie called '\(movie.name)', dir. \(movie.director)")
    default:
        println("something else")
    }
}
 
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman

 Nested type
class와 구조체 안에의 기능성을 강화하기 위해 내부적으로 enum을 정의하는 것이다.

ex)
struct BlackjackCard {
    
    // nested Suit enumeration
    enum Suit: Character {
        case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"
    }

     // nested Rank enumeration
     enum Rank: Int {
          case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King, Ace
        struct Values {
            let first: Int, second: Int?
        }

        var values: Values {
        switch self {
        case .Ace:
            return Values(first: 1, second: 11)
        case .Jack, .Queen, .King:
            return Values(first: 10, second: nil)
        default:
            return Values(first: self.toRaw(), second: nil)
            }
        }
     }
    
    // BlackjackCard properties and methods
    let rank: Rank, suit: Suit
    var description: String {
    var output = "suit is \(suit.toRaw()),"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

Extension
objective-C의 category와 기능적으로 비슷하지만 category와 달리 이름을 갖지 않는다. extension은 클래스뿐만 아니라 구조체와 enum 또한 확징이 가능하다.

Extension으로 할 수 있는것
-computed property와 computed static property를 추가 (일반 property는 X)
-instance method와 type method를 정의만
-새로운 initializer 추가 (class extension에서는 convenience initializer만 구현 가능)
-subscript 정의
-새로운 nested type 사용 및 정의

ex)
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
println("One inch is \(oneInch) meters")
// prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
println("Three feet is \(threeFeet) meters")
// prints "Three feet is 0.914399970739201 meters"

Protocol
protocol은 요구사항을 반영한 청사진이다. protocol은 property, met hod, operator, subscript 등을 선언할 수 있다.

ex)
protocol SomeProtocol {
     // protocol definition goes here
}

struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
     // class definition goes here
}

protocol에 선언한 property는 접근자를 정의하여야 한다. 물론 static/class 등의 type property도 해당된다. 접근자가 gettable만 필요할 때에는 {get}을 쓰고 gettable과 settable이 모두 필요할 땐, {get set}을 쓴다. instace/type method 정의는 body부분이 없이 선언한다. variadic params를 사용할 수 있으나 default param은 사용할 수 없다. 구조체와 enum에서 사용하는 mutating method의 경우 'mutating'키워드를 적으면 선언이 가능하다.

다음의 경우, type으로 protocol을 사용 가능
    1. 함수, 메소드, initializer의 param, return으로 사용
    2. 상수, 변수, property
    3. collection의 item type으로 사용

delegation : objective-C와 동일

protocol을 받아서 extension으로 구현 가능하다. 이를 이용하면 기존의 type을 건드리지 않고 extension을 통해 protocol을 사용할 수 있다. protocol을 collection의 type으로 사용가능하며 protocol을 상속받아 또 다른 protocol을 구현할 수도 있다.

ex)
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

여러 프로토콜을 하나로 통합해서 사용이 가능하다. syntax는 아래와 같다.

ex)
     protocol Named {
          var name: String { get }
     }

     protocol Aged {
          var age: Int { get }
     }

     struct Person: Named, Aged {
         var name: String
         var age: Int
     }

     func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
          println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
     }

     let birthdayPerson = Person(name: "Malcolm", age: 21)
     wishHappyBirthday(birthdayPerson)
     // prints "Happy birthday Malcolm - you're 21!"

Eextesion에도 optioanl을달 수 잇다. optional을 달면 이에 따른 protocol 구현이 필수적인 부분을 피할 수 있다. property, method 등의 앞에 '@optional'이라는 키워드를 사용하면 된다. 단, protocol이 '@objc'로 선언되어야 한다.

ex)
@objc protocol CounterDataSource {
    @optional func incrementForCount(count: Int) -> Int
    @optional var fixedIncrement: Int { get }
}

Generics
generic은 함수가 어느 타입이든  동작할 수 있도록 유연하고 확장성을 확보해 준다. 일반적으로 함수는 아래와 같이 param 혹은 return의 타입에 따라 따로 정의되어 있다.

ex)
func swapTwoStrings(inout a: String, inout b: String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
func swapTwoDoubles(inout a: Double, inout b: Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

generic은 위와 같이 같은 로직을 타입에 따라 다시 정의하는 수고를 예방하고 함수의 재사용성을 높인다. 위의 함수의의 경우 generic을 사용하면 아래와 같이 만들 수 있다. 아마 몇몇 분들은 C++의 template을 떠올릴 것이다.

ex)
func swapTwoValues<T>(inout a: T, inout b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

struct Stack<T> {
    var items = T[]()
    
    mutating func push(item: T) {
         items.append(item)
    }

    mutating func pop() -> T {
         return items.removeLast()
    }
}

위에 보면 generic을 이용한 stack의 예제도 볼 수 있다. stack의 예제에서 보면 generaic은 함수의 type뿐만 아니라 property의 타입으로도 사용이 가능하다. 그리고 generic은 특정 클래스의 서브 클래스만 받을 수 있도록 아래와 같이 제한도 가능하다.

ex)
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

Associated type
'typealias' 키워드 : 추후,  다시 볼 것

Advanced Operators
Bitwise Operators (~, &, |, <<, >>)

Overflow Operators (&+, &-, &*, &/, &%)
사칙연산의 연사자 앞에 '&'를 붙이면 연산 도중에 일어날 수 있는 overflow, underflow를 예방할 수 있으며 연산과정에서 생기는 '불능'과 같은 런타임 에러를 예방할 수 있다.

Operator funcitons (Operator overloading)
C++의 operator overloading와 비슷하다. 이미 정의되어 있는 기본 연산자를 사용자의 임의로 정의하여 사용할 수 있는 기능이다. syntax는 아래와 같다.

ex)
<operator funciton keyword> func <operator> (<params>) {
     <statements>
}

위에 하이라이팅된 operator function keyword에는 각 연산자의 종류에 따라 다른 키워드가 들어간다. 연산자가 각 피연사자보다 앞에 나오는 전위 연산자의 경우 '@prefix'가 들어간다. 연산자가 피연산자보다 늦게 나오는 후위 연산자는 '@postfix'가, 중위 연산자는 '@infix'가 들어간다. 

 

 수식

 키워드

 전위 연산자

+ 1 2 

@prefix 

 중위 연산자

 1 + 2

 @infix

 후위 연산자

 1 2 +

 @postfix



Language reference
언어의 lexical rule에 관한 내용으로 apple 서적 참조


참고 : Apple Inc. ‘The Swift Programming Language.’