iOS8에서 새롭게 제공하는 기능 중에 하나이다. 기존 안드로이드에서 이미 제한 기능이다. iOS8에서 새롭게 추가되면서 더욱 편리한 기능을 제공할 것으로 생각된다. 현재(2014.09.18) iOS8이 정식 릴리즈된 시점에서 필자가 사용하는 앱 중에 에버노트와 드롭박스는 발 빠르게 이 기능을 대처하고 있다. 이제 iOS8 위젯 기능을 사용하기 위한 과정을 알아보자.


    먼저, 나의 앱 프로젝트에 App Extension(이하 'AE')을 추가한다. AE는 호스트 앱이 구현하고 호스트 앱에 AE를 추가 구현하는 형태로 개발한다. AE는 다른 앱들에서 사용할 수 있다. 호스트 앱에는 AE에서 사용하는 설정이나 정보를 처리하는 기능들을 얻도록 한다. 현재 Xcode 6에서는 아래와 같이 iOS에서 사용 가능한 6가지 AE들을 제공하고 있다.



    기존의 프로젝트에 새로운 타겟을 추가한다. AE 탭을 누르면 새롭게 추가할 수 있는 AE 목록이 보인다. 여기서는 Today Extension을 선택한다. 새로운 타겟을 생성하면서 Today Extension을 위한 템플릿이 생성된다. 'NCWidgetProviding'이 구현된 템플릿이 있다. 'NCWidgetProviding'은 optional method를 2가지 재공한다. 먼저 'widgetPerformUpdateWithCompletionHandler(completionHandler:)'이라는 method이다. 이는 위젯이 표시되는 순간, 사용되는 정보의 수정을 체크하여 갱신되도록 요청하는데 쓰인다. 물론 여기서 정보가 수정되었는지를 체크하는 부분은 개발자가 이 method에 직접 구현해 넣으면 된다. 구현을 하고난 후, 'comletionHandler(NCUpdateResult)'를 호출하면 된다. 'NCUpdateResult'는 3가지의 case를 제공하는 enum이다. '.NewData / .NoData / .Failed'의 3가지를 제공한다. 앞에서부터 하나씩 설명하면 '.NewData'는 새로운 정보의 수정이 발생하여 위젯을 갱신하고자 할 때 사용된다. '.NoData'는 갱신이 불필요할 경우이 사용되고 'Failed'는 정보의 수정을 확인하는 과정에서 실패할 경우 호출된다. 이외에 'NCWidgetProviding'에서 제공하는 'widgetMarginInsetsForProposedMarginInsets() '를 통하여 위젯의 여백을 조정할 수 있다.


    AE에서 표시할 정보를 가져오기 위해서 호스트앱과 Extension간의 데이터를 공유하는 저장소가 필요하다. 저장소는 NSUserDefault를 사용한다. NSUserDefault로 앱간의 데이터 공유를 위해서 각 타겟에 동일한 AppGroup을 추가한다. AppGroup은 각 타겟의 'Capabilities'에서 추가 가능하다. 추가된 AppGroup을 'NSUserDefaults(suiteName:) '의 suiteName으로 사용함으로써 동일한 NSUserDefault를 공유할 수 있다. 아래의 예제를 참고하는 것이 설명만 보는 것보다 이해가 빠를 것이라고 생각한다.


P. S. Today view는 네트워크 및 UI 입출력을 허용하는 반면, 파일 입출력을 지원하지 않는다. 그리고 과도한 네트워크 사용은 피하는 것이 좋다. 다른 process보다 Today view의 우선순위가 낮아 반응성을 저해할 수 있기 때문이다.


AppExtensionEx.zip


Map

map은 각 element를 순회하면서 element를 수정할 때 사용하기 좋은 함수이다. 예를 들어 우리는 각 String에 "box"라는 String을 덧붙이기 위하여 for-in을 떠올리기 쉽다.


ex) 

for string in strArray {

string += "box"

}


대신에 map 함수를 사용하면 한줄로 간단하게 작성할 수 있다.


ex)

var resultArray = strArray.map({$0+"box"})


참고로 map의 param 함수 타입은 '(T) -> U'이다. map 함수는 Array의 각 element를 순회하면서 개발자가 정의한 동작을 수행하게 된다.


Filter

filter는 말 그대로 특정 조건을 통과한 element들을 모아서 Array로 반환하는 함수 이다. filter의 param 함수 타입은 '(T) -> Bool'이다. 작성하면 아래와 같다.


ex)

var filteredArray = moneyArray.filter({$0<1_000})


Reduce

reduce는 element들을 하나의 value로 통합하는 함수이다. reduce의 함수 원형은 'func reduce<U>(initial: U, combine: (U, T) -> U) -> U'이다. 이를 이용하여 작성하면 아래와 같다.


ex)

var sum = moneyArray.reduce(0, combine: +)


moneyArray의 element는 Integer로 각 element의 합을 구하는 것이다. 혹은 아래와 같이 String을 reduce할 경우, element들을 하나의 String으로 통합할 수도 있다.


ex)

var aString = resultArray.reduce("", combine: {$0+$1 + "!! "})


Cloud Kit


CKContainer : 앱의 public/private 컨텐츠를 캡슐화한 객체이다.
CKDataBase : 앱 컨테이너에 담긴 public/private 데이터들에게 접근하기 위한 통로이다.
CKRecord : record의 컨텐츠를 관리하기 위한 인터페이스

    Record는 key-value pair로 key는 레코드에서 field 값에 해당한다. 각 field의 값은 string, number, date뿐만 아니라 파일, 데이터 블록 그리고 다른 Record와 관계를 맺기 위해 다른 Record의 reference도 넣을 수 있다.

    CloudKit은 container들을 이용하여 데이터를 구성한다. 각 container별로 entitlement가 필요하고 런타임에서 CKContainer 객체를 이용하여 특정 container에 대해 task를 수행한다.

    각 container는 public/private database로 나뉘어 있다. 각 database는 CKDatabase로 표현된다. private database에 쓰여진 데이터는 데이터를 쓸 때의 사용자의 iCloud 계정에 저장되며 현재 사용자의 한해서만 볼 수 있다. public database에 저장된 데이터는 앱의 iCloud 저장소에 저장되며 앱의 모든 사용자가 볼 수 있다.




CloudKit 앱이 구동하는 동안 iCloud 계정이 활성화 안 된 상태라도 public database는 항상 읽을 수 있다. public database에 데이터를 저장하거나 private database에 접근하기 위해서는 iCloud 계정을 기기상에서 활성화 시켜야한다. public databae에서 데이터를 읽는 외의 작업을 한다면 iCloud 계정이 활성화 되었는지 확인한다.



    CKRecord 클래스를 이용하여 key-value pair로 데이터를 저장한다. value에는 string, number, date등의 단순한 값 뿐만 아니라 location, 다른 record의 reference, 파일 등도 넣을 수 있다.
    record에 명시적으로 데이터를 써야한다. NSOperation 객체와 CKContainer, CKDatabase의 메소드를 사용하여 데이터를 쓰거나 fetch할 수 있다. Fetch operation은 fetch하고자 하는 record의 ID를 미리 알고 있어야한다.
    record의 ID를 모를 경우엔 predicate를 사용하여 record를 검색할 수 있다. predicate에 사용하는 query는 CKQuery객체를 사용한다. query를 실행 시키기위해서 CKQueryOperation을 사용한다.
    CKSubscription으로 서버에 한번만 subscription을 저장하면 해당하는 부분의 데이터가 변경될 때마다 서버에서 push notification을 통해 알려준다.

    CloudKit은 개발 환경과 상용 환경을 구분하여 제공한다. 개발 환경은 유연한 환경을 개발팀의 멤버들에게만 제공한다. 개발 환경에서 record에 새로운 field를 추가하여 저장할 때, 서버는 자동으로 새로운 스키마를 업데이트한다. 이 특징을 이용하여 개발 기간동안 스키마를 변경하는데 드는 시간을 아낄 수 있다. 단, 새로운 field를 추가하고 나서 field의 type을 변경하는 것은 직접 CloudKit Dashboard를 활용하여 filed를 지우고 새로 field를 추가하여야 한다.
    앱을 배포하기에 앞서 스키마와 데이터들을 CloudKit Dashboard를 사용하여 상용 환경에 migrate해야한다. 상용에서 구동될 때는 서버에서 스키마가 소프트웨어적으로 변하는 것을 막는다. CloudKit Dashboard를 통하여 여전히 바꿀 수는 있지만 상용 환경에서 record에 field를 추가하려하면 에러가 발생한다.
    개발하는 동안 Xcode가 자동으로 개발 환경으로 앱을 빌드한다. 앱을 배포하기 전에 배포 과정에서 개발환경을 쓸 것인지 상용 환경을 쓸것인지를 선택한다. 여기서 꼭 상용 환경을 선택해야한다. 개발환경으로 선택된 앱은 App Store에 의해 reject된다.


**CloudKit 동작에 관한 총정리**

  1. CKContainer 객체를 이용하여 접근하고자 하는 데이터가 있는 iCloud container를 얻는다.
  2. CKDatabase 객체로 해당 record가 존재하는 public/private database를 얻는다.
  3. fetch하기 원하는 데이터를 식별한다. record ID를 알 경우, CKFetchRecordOperation을 사용한다. record ID를 모를 경우, CKQuery를 만든 후, CKQueryOperation을 사용한다.
  4. 결과 값을 다루기 위해 operation의 completion handler에 블럭을 정의한다.
  5. operation 객체를 CKDatabase 객체의 addOperation: 메소드로 큐에 넣어 실행시킨다. operation 객체는 completion block을 통하여 비동기적으로 결과값을 처리할 것이다.


CloudKit Operations


Fetch & Modify records

  1. CKFetchRecordChangesOperation
  2. CKFetchRecordOperation
  3. CKModifyRecordOperation
  4. CKQueryOperation

Zones
  1. CKFetchRecordZonesOperation
  2. CKModifyRecordZonesOperation

Users
  1. CKDiscoverAllContactsOperation
  2. CKDiscoverUserInfosOperation

Notification
  1. CKFetchNotificationChangesOperation
  2. CKMarkNotificationReadOperation

Subscription
  1. CKFetchSubsriptionOperation
  2. CKModifySubscriptionOperation
  3. CKModifyBadgeOperation


see How to use CloudKit


sample code of CloudKit

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.’


Function parameters
함수의 param들은 기본적으로 다 상수다. 그러므로 수정이 불가능하다. 함수 내에서 param을 수정하기 위해선 inout param으로 선언하면 되는데, 함수 선언부에서 param앞에 '&'를 표시해주면 된다.

Function type
function type을 다른 function의  param의 타입으로 사용할 수 있다. 함수의 타입이 들어가는 부분에  '(Int, Int) -> Int'와 같이 일반 함수 선언 부에서 함수명의 뒷부분을 적어주면 된다. function type을 사용해서 함수를 param으로 사용할 수 있다.

ex)
func addTwoInts(a: Int, b: Int) -> Int {
    return a + b
}

func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) {
    println("Result: \(mathFunction(a, b))")
}

printMathResult(addTwoInts, 3, 5)
// prints "Result: 8’

또, function type을 return  type으로 또한 사용이 가능하다. 함수 선언부에 return을 적어 주는 부분에 function type을 적으면 된다.

ex)
func stepForward(input: Int) -> Int {
    return input + 1
}
func stepBackward(input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    return backwards ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function

Closures
closure는 코드 상에서 나뉘어 있고 사용되는 기능을 스스로 내포한 블록이다. C와 objective-C의 block과 다른 언어의 lambda와 비슷하다. 다만, block과 closure의 다른 점은 block는 단순한 instruction의 모음 단위이다. closure는 실제로 block을 value로 소유한 객체라고 생각하면 된다. 간단한 예로 value를 block에서 쓰려면 block에서 사용한다는 표시하기 위해 '__block' 키워드를 사용하지만 closure는 실체를 가진 reference이므로 정의할 때 당시의 주변 context의 value 자체에 대한 refence count를 증가시켜 capturing이 가능한 것이다.

다른 개발자의 표현을 빌리자면 'closure는 함수 포인터와 비슷하지만 함수 포인터는 함수 내부의 변수만 접근이 가능한 반면 closure는 정의할 당시의 주변의 context의 변수 들에 접근이 가능하다'라고 서술되어 있다.

전역 함수와 중첩 함수도 closure의 유형으로 아래와 같다.

    • 전역 함수는 함수명을 가지지만 value를 capture하지 않는다.
    • 중첩 함수는 함수명을 가지면서 자신의 함수 중첩 내의 value를 capture할 수 있다.
    • closure는 이름을 가지지 않으며 주변의 context의 value를 capture할 수 있다.


closure의 capturing value에 관해서는 뒤에서 자세히 설명할 것이다. 먼저, closure의 syntax는 아래와 같다.

ex)
{ (parameters) -> return type in
    statements
}

'in' 키워드는 closure에서 param과 return의 선언이 끝나고 body가 시작됨을 알려준다. closure의 body는 너무 짧아 한 줄로 끝날수도 있으므로 'in'이라는 키워드를 사용하여 명시적으로 표시한다.

Operator function
closure의 축약형이다. 애플의 swift 서적의 예로 설명하면 아래와 같다. 먼저, 아래와 같은 closure가 존재한다.

ex)
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 })

위에서와 같이 실행 단위를 block으로 묶어서 사용된다. 단, 위의 경우는 전역함수나 중첩 함수와 다르게 anonymous 형태로 사용되었다. 위의 closure를 단순화 시켜 최종적으로 operator function의 형태로 만들것이다. 우선, Swift에서 제공하는 sort 함수는 두번 째 param의 (String, String) -> Bool 임을 예측한다. 그러므로 function type은 생략한다.

ex)
reversed = sort(names, { (s1, s2) in return s1 > s2 })

'in' 키워드 뒤에 statement가 1줄이기 때문에 return statement가 올 것이라는 것을 예측할 수 있기 때문에 'return' 키워드를 생략한다.

ex)
reversed = sort(names, { (s1, s2) in s1 > s2 })

swift는 위와 같은 inline closure에 default param name을 제공한다. 그러므로 param name을 $0, $1, $2 ... and so on으로 대체 가능하다. 그리고 default param name을 사용할 경우 param의 선언부도 생략한다.

ex)
reversed = sort(names, { $0 > $1 })

여기서 추가적으로 swift에서 제공하는 String에서는 '>' operator에 대해서 추가 구현이 되어 있으므로 아래와 같이 축약이 가능하다.

ex)
reversed = sort(names, >)

실제로 operator에 관한 내용은 더 뒤에서 설명이 나오지만 짐작하건데 C++에서의 operator overloading과 비슷할 것으로 생각된다.

func : map -> call closure once each value in array
swift의 array 객체는 map이라는 method를 가진다. map 메소드는 array의 각 value마다 사용자가 정의한 closure를 호출하는 역할을 한다. 사용법은 아래와 같다.

ex)
let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]


capturing values in closure
closure는 자신이 정의된 곳의 context의 constant와 value들을 capture할 수 있다. 심지어 context의 scope를 벗어났을 때도 capture가 가능하다. 일반적인 중첩 함수로 makeIncrementor(forIncrement:)를 예로 들어보자. 지역 변수 'runningTotal'은 makeIncrementor가 가지는 scope 안에서만 유효하다.

ex)
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0

    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }

    return incrementor
}

중첩 함수는 closure의 한 형태이다. 아래와 같이 makeIncrementor(forIncrement:)를 상수에 집어넣어 사용해보자. 비록 makeIncrementor(forIncrement:)가 실행되어 scope안에서만 'runningTotal'의 value가 유효하지만 closure이기 때문에 scope를 벗어나서도 'runningTotal'의 value는 유효하다. 이를 value를 capture했다고 한다.

ex)
let incrementByTen = makeIncrementor(forIncrement: 10)

incrementByTen()
// returns a value of 10

incrementByTen()
// returns a value of 20

incrementByTen()
// returns a value of 30

Enumeration
swift의 enum은 C와 obejctive-C와 달리 default로 integer값을 사용하지 않는다. integer 대신에 각 member를 구별할 수 있는 value를 사용한다.  아래의 syntax로 사용한다.

ex)
enum CompassPoint {
    case North
    case South
    case East
    case West
}

enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

enum : Associated values
associated value는 연관되어 있는 값들을 서로 다른 타입(양식)으로표현할 때 사용한다. 사용자가 정의한 타입으로 enumeration을 할 수 있다. 아래와 같이 UPCA와 QRCode로 사용자가 직접 enumeration 안에서 타입을 정의할 수 있고 분기문에서 사용 가능하다. 사용된 enum의 associated value는 constant로 사용한다. 

ex)
enum Barcode {
    case UPCA(Int, Int, Int)
    case QRCode(String)
}

var productBarcode = .QRCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case let .UPCA(numberSystem, identifier, check):
    println("UPC-A with value of \(numberSystem), \(identifier), \(check).")

case let .QRCode(productCode):
    println("QR code with value of \(productCode).")
}
// prints "QR code with value of ABCDEFGHIJKLMNOP."

enum : Raw values
associated value는 서로 다른 타입의 값을 표할 때 사용된다. 하지만 raw value는 모두 같은 타입의 enum member들을 사용할 때 사용한다. raw value를 사용하려면 enum을 정의할 때, 구체적인 타입을 아래와 같이 정의해주면 된다.

ex)
enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

또한 기존의 C와 objective-C처럼 integer로 이루어진 enum 선언도 아래와 같이 할 수 있다. 'Mercury'를 '1'로 정의하고 뒤에는 자동으로 '2, 3...'으로 정의된다.

ex)
enum Planet: Int {
        case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}


Structures and Enum is value type
Classes and Closures is reference type

syntax)
struct SomeStructure {
    // structure definition goes here
}

class SomeClass {
    // class definition goes here
}

NSArray & Array
NSDictionary & Dictionary
NSArray & NSDictionary는 클래스로 구현되어 있다. 하지만 Array & Dictionary는 구조체로 이루어져 있어서 value type을 가진다. 여기서 특이점은 Dictionary는 value type의 특징 대로 무조건 copy된다. 하지만 Array의 경우는 length의 변화가 없을 경우, copy를 하더라도 공유된 sequence를 가지게 된다. length가 변할 때 실제로 물리적 copy를 수행하게 된다. 만약에 Array를 sequence를 공유하디 않고 애초에 물리적 copy를 하고 싶을 때는 아래와 같이 unshare() 혹은 copy()를 사용하면 된다.

ex)
var unsharedNames = names.unshare()
var copiedNames = names.copy()

unshare()는 호출 당시에는 물리적 copy를 수행하지 않지만 Array의 length 이외의 변화가 생길 때에도 즉시 물리적 copy를 수행한다. copy()는 호출과 동시에 물리적 copy를 수행하게 된다.


You can use (===, !==)
swift에서는 삼등연산자를 사용가능하다. 객체를 상대로 사용할 경우, 객체가 같은 클래스로 생성되었는지 아닌지를 구분할 수 있다. Array에 사용할 경우 Array 내부의 value들이 같은지 아닌지를 확인 할 수 있다.

Lazy property
lazy property는 실제로 property에 접근하여 read를 하기 전까지 불필요한 property의 초기화를 하지 않는다. 사용할 때는 아래와 같이 '@lazy'를 붙여주면 된다. 참고로 conatant property는 lazy property로 사용이 불가능하다.

ex)
class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a non-trivial amount of time to initialize.
    */
    var fileName = "data.txt"
    // the DataImporter class would provide data importing functionality here
}
 
class DataManager {
    @lazy var importer = DataImporter()
    var data = String[]()
    // the DataManager class would provide data management functionality here
}
 
let manager = DataManager()
manager.data += "Some data"
manager.data += "Some more data"
// the DataImporter instance for the importer property has not yet been created

println(manager.importer.fileName)
// the DataImporter instance for the importer property has now been created
// prints "data.txt"

위의 예시를 통해서 볼 경우, 제일 하단의 'println(manager.impoter.fileName)'이 호출되어 read가 일어나기 전까지 lazy property인 manager.importer의 초기화는 이루어지지 않는다.

Computed property
computed property는 실제 물리적인 값을 가지진 않고 오로지 getter와 setter를 가진 property를 말한다. 아래의 예시를 보면 이해가 바르다.

ex)
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set(newCenter) {
        origin.x = newCenter.x - (size.width / 2)
        origin.y = newCenter.y - (size.height / 2)
    }
    }
}

위와 같이 property center는 실제로 물리적인 value를 가지고 있지 않지만 getter와 setter를 통한 접근이 가능하다. 이를 computed property라고 한다. setter를 정의하지 않을 경우, read only property로 사용되며 아래와 같이 getter라고 정의할 필요없이 간단하게 사용 가능하다.

ex)
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
    return width * height * depth
    }
}

Property observer
objective-C의 KVO와 비슷한 기능을 가진 친구이다. 'willSet()'과 'didSet'을 통하여 property의 value에 set이 발생할 때마다 수행할 로직을 정의할 수 있다. 심지어 같은 값을 set해도 호출된다. overrinding한 property에도 사용할 수 있다. 사용방법은 아래와 같다.

ex)
class StepCounter {
    var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        println("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            println("Added \(totalSteps - oldValue) steps")
        }
    }
    }
}

Type property & method
type property와 type method는 Java에서 static property와 method를 말한다. 실제의 객체가 없어도 사용 가능한 property와 method이다. 사용 법은 아래와 같다. struct와 enum에서 사용될 때는 'static'키워드를 사용하고 class에서 사용될 때는 'class'라는 키워드를 사용해주면 된다.

ex)
struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
    // return an Int value here
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
    // return an Int value here
    }
}
class SomeClass {
    class var computedTypeProperty: Int {
    // return an Int value here
    }
}

Mutating
구조체와 enum은 value type을 가지기 때문에 instance method를 통한(type method 말고) 내부 value의 변경이 불가능하다. 하지만 method 앞에 'mutating'이라는 키워드를 사용할 경우, 내부 value의 변경이 가능한 mutating method를 정의할 수 있다. 또한 내부의 value뿐만 아니라 self를 사용하여 구조체와 enum의 자신의 개체 마저도 변경이 가능하다. 

ex)
‘struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(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
            }
    }
}

Subscripts
subscript는 class, struct, enum에서 정의 가능하다. subscript는 array나 dictionary와 같이 index나 key를 가지고 value를 querying하는 것을 가르킨다. subscript를 이용해서 class, struct, enum에서도 index를 통한 value의 querying이 가능하다. syntax는 computed property와 비슷하다. 2개의 예제 중 아래는 read-only subscript이다.

ex)
subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }
}

subscript(index: Int) -> Int {
        // return an appropriate subscript value here
}

아래와 같이 실제 사용의 예를 보면 class, struct, enum도 array, dictionary와 같이 index를 통한 querying을 사용할 수 있다. 또한 index로 사용할 param을 variable param 혹은 variadic param으로도 사용 가능하나 in-out이나 defalut param으로는 사용 불가능하다.(기억이 안난다면 1주차 요약 참고) input param과 return type에는 제한이 없다.

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

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


Preventing overrides
상속을 할 때, 상속을 허용하지 않을 property 혹은 method는 앞에 '@final'이라는 키워드를 사용하면 된다. Java를 사용해본 개발자라면 표현이 익숙할 것이다.

Initializer (init)
Initializer에서 모든 property를 초기화해야만 컴파일 가능하다. Initailizer내에서의 method 호출은 모든 property(Optional 제외, 엄밀히 말하면 optional은 nil로 초기화됨)를 초기화하고 나서 가능하다.

Designated & convenience initializer
Designated initializer는 모든 property 초기화하고 모든 클래스가 최소 1개 이상을 가진다. 반면, Convenience initializer는 보조 initializer이기 때문에 굳이 필요하지 않으면 만들지 않는 것이 좋다. 사용할 때는 'convenience' 키워드 사용한다. syntax는 아래와 같다.

ex)
Designated initializer
init(parameters) {
    statements
}

Convenience initializer
 convenience init(parameters) {
    statements
}

Initailizer chaining 
Initializer의 작은 규칙 몇 가지가 따른다. 구현에 앞서 이를 숙지하면 아~~주 좋다.
  1. Designated만 상위클래스의 Designated를 호출
  2. Convenience만 같은 클래스 내의 다른 생성자를 호출
  3. Convenience는 최종적으로 Designated를 호출하며 끝날 것


위의 이미지와 같이 designated는 오로지 슈퍼 클래스의 designated만을 호출할 수 있다. convenience만 다른 생성자들을 호출이 가능하다. 객체가 실제로 물리적 초기화가 되는 시점은 designated가 호출되는 시점이다. initial logic이 중첩되는 다양한 customized initializer를 사용할 경우엔 convenience를 이용하자. 굳이 필요하지 않으면 안 쓰는 것이 좋다.



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


Swift에 입문하기에 앞서 Swift의 전반적인 syntax에 먼저 익숙해지자.

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

Type alias
기존의 타입들을 대체하는 키워드를 정의할 수 있다. 팀내에서 정의된 타입을 사용할 때는 편리할 수 있으나 무분별한 type alias의 사용은 code의 readability를 저해할 것 같다.

ex)
typealias AudioSample = UInt16

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0’

Tuple
다수의 value들을 하나의 compound value로 사용할 수 있도록 해준다. 

ex)
let http404Error = (404, "Not Found")
let (statusCode, statusMessage) = http404Error

println("The status code is \(statusCode)")
// prints "The status code is 404"

println("The status message is \(statusMessage)")
// prints "The status message is Not Found"

Optionals (?)
value의 부재가 예상되는 곳에 optional을 사용한다. swift에서는 initializer에서 모든 property를 초기화 해야만 실행이 가능하다. 초기화가 완전히 이루어 지지 않으면 컴파일할 때 에러를 뱉는다. 하지만 초기화가 되지 않은 value가 있거나 method의 return이 정상적으로 이루어지지 않거나 부재일 것이 예상되는 부분에 사용하여 컴파일 혹은 런타임에서 에러를 피할 수 있다. 하지만 value가 부재이기 때문에 런타임에서 에러를 발생할 소지가 생기기에 사용을 할 때 신중해야 한다. value checking을 하여 런타임에서의 에러를 방지한다. 선언할 때는 property를 선언한 다음 끝에 '?'를 표시해준다.

ex)
let possibleNumber = "123"
let convertedNumber = possibleNumber.toInt()
// convertedNumber is inferred to be of type "Int?", or "optional Int"

Forced unwrapping
optional value를 사용하기 위해서 '!'를 붙여준다. '!'는 'value가 존재하기 때문에 사용 가능하다'라는 것을 컴파일러에게 명시적으로 알려주는 방법이다. 부재중인 value의 접근 가능성이 있기 때문에 아래와 같이 'if'문을 사용하여 value checking을 해준다.

ex)
if convertedNumber {
                    println("\(possibleNumber) has an integer value of \(convertedNumber!)")
} else {
                    println("\(possibleNumber) could not be converted to an integer")
}
// prints "123 has an integer value of 123"

Optional binding
optional binding은  'if'외에 사용할 수 있는 optional을 사용한 value의 부재에 의한 에러를 방지하기 위한 방법이다. 아래의 예제와 같이 optional value의 직접적인 엑세스를 하지 않고 임시변수를 만들 때 사용한다.

ex)
if let actualNumber = possibleNumber.toInt() {
    println("\(possibleNumber) has an integer value of \(actualNumber)")

} else {
    println("\(possibleNumber) could not be converted to an integer")
}
// prints "123 has an integer value of 123"

Implicitly unwrapped optionals (!)
소프트웨어의 구조 혹은 로직 상으로 value의 부재가 불가능할 경우, '이 property는 value를 항상 가지고 있다'라는 것을 표시해주는 방법이다. optional은 선언할 때 '?'를 사용하지만 Implicitly unwrapped optional은 선언하고 끝에 '!'를 사용해주면 된다.

ex)
let possibleString: String? = "An optional string."
println(possibleString!)
// requires an exclamation mark to access its value
// prints "An optional string."
 
let assumedString: String! = "An implicitly unwrapped optional string."
println(assumedString)
// no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."

Assertion
debugging에 도움이 될만한 도구이다. value의 부재나 부적절한 value의 체크에 유용하다. 잠재적으로 value에 의한 코드의 정상적 실행이 불가능할 경우 value를 확인하기 위해 사용한다. 아래는 Apple에서 권고하는 Assertion의 사용 시기이다.

    • Integer index에 의한 custom collection의 사용 시, index의 값이 너무 적거나 커서 접근에 이상이 예상 될때

    • 함수의 param이 함수의 정상적 실행이 불가능하게 하는 값일 경우
    • optional value가 nil이어서 부분적 실행이 정상적이지 못할 경우


ex)
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// this causes the assertion to trigger, because age is not >= 0"

Range operator
값의 범위를 축약적으로 나타낼 때, 사용한다. 어려운 개념이 아니기에 아래의 예문을 참고하여 쓰도록 한다.

ex)
Closed range
'for index in 1...5' mean '1 <= index <= 5'

Half-Closed range
'for index in 0..count' mean '0 <= index < count'

Func : countElements(String)
Swift에서 사용하는 String의 길이를 받아오는 함수, NSString의 length property는 unicode-16의 길이를 재는 것이기 때문에 countElements(String)의 값과 상이할 수 있다. NSString의 leng와 같은 값을 받아오기 위해서 String에서는 utf16count를 사용하면 된다.

Func : enumerate(ARRAY) -> (index, value)
enumerate 함수를 이용하여 index, value 형태의 tuple로 collection의 접근이 가능하다.

ex)
for (index, value) in enumerate(shoppingList) {
    println("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas

for-in Dictionary -> (key, value)
dictionary를 for-in으로 순회하게 되면 key, value 형태의 tuple로 접근이 가능하다.

ex)
for (airportCode, airportName) in airports {
    println("\(airportCode): \(airportName)")
}
// TYO: Tokyo
// LHR: London Heathrow

Switch-case
switch의 기본  syntax는 아래와 같다. swift에서는 statement가 없는 case문은 컴파일할 때 에러가 발생한다. 그러므로 아래와 같이 case문에 겹쳐서 statement를 사용하도록 한다.

ex)
switch 'some value to consider' {
case value 1:
    respond to value 1

case value 2, value 3:
    respond to value 2 or 3

default:
    otherwise, do something else
}

swift에선 조건에 부합하면서 처음 만나는 case문을 실행하면 자동으로 switch문을 벗어나기 때문에 명시적으로 'break'를 선언할 필요가 없다. 대신에 break없이 다음 케이스문까지 실행하고 싶으면 'fallthrough'라는 키워드를 사용하면 된다.

ex)
let integerToDescribe = 5

var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}

println(description)
// prints "The number 5 is a prime number, and also an integer."

각 case문에는 tuple, range operator 등의 compound value를 사용 가능하다. 또한 추가적인 조건문의 사용이 필요하면 where를 활용할 수 있다.

ex)
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    println("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    println("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    println("(\(x), \(y)) is just some arbitrary point")
}
// prints "(1, -1) is on the line x == -y"


#이하 내용은 기존의 언어에서도 많이 사용하는 방법이기 때문에 빠르게 읽고 숙지하는 편이 정신건강에 좋다.

Mutiple return value using tuple
tuple을 사용하여 아래와 같이 compund value를 return할 수 있다.

ex)
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
    var vowels = 0, consonants = 0, others = 0
    for character in string {
        switch String(character).lowercaseString {
        case "a", "e", "i", "o", "u":
            ++vowels
        case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
        "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
            ++consonants
        default:
            ++others
        }
    }
    return (vowels, consonants, others)
}

External parameter name
함수를 호출할 때 아래와 같이 external name을 정의할 수 있다.

ex)
/*************************************************************************/
func someFunction(externalParameterName localParameterName: Int) {
    // function body goes here, and can use localParameterName
    // to refer to the argument value for that parameter
}
/*************************************************************************/

func join(string s1: String, toString s2: String, withJoiner joiner: String)
    -> String {
        return s1 + joiner + s2
}

join(string: "hello", toString: "world", withJoiner: ", ")
// returns "hello, world"

internal name과 external name을 동일하게 쓸 경우, 아래와 같이 약식으로 '#'을 사용하면 편리하게 사용할 수 있다.

ex)
func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
for character in string {
if character == characterToFind {
return true
}
}
return false
}

let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
// containsAVee equals true, because "aardvark" contains a "v"

Variadic parameter
개수를 예측할 수 없는 다수의 param을 사용할 경우엔 아래와 같은 방식으로 함수를 사용할 수 있다.

ex)
func arithmeticMean(numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8, 19)
// returns 10.0, which is the arithmetic mean of these three numbers


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


CPU 벤더들의 클럭 경쟁에서 멀티코어 형태의 경쟁으로 패러다임이 바뀐 지금. 서버 뿐 아니라 PC 그리고 모바일 단말에 사용되는 OS는 프로그래머들에게 멀티 코어 프로세싱을 지원해 줘야하는 숙명을 가지게 되는데..


이에 애플은 스노우 레오파드를 발표할 때 두가지 큰 기술을 개발자들에게 선물했다.

하나는 GCD (Grand Central Dispatch), 나머지 하나는 OpenCL 이다.


      GCD는 쉽게 말해 멀티코어 프로세서를 위한 Thread 프로그래밍을 OS에서 자동 관리 / 분배 해 주는 Mac OS에 내재된 C Library이다.  이말은 즉 프로그래머에게 자신이 만든 Thread를 어떻게 멀티코어 프로세서에 분산 시킬 것인가에 대한 고민을 없애 주었다는 말이다.  이 이야기는 나중에 보다 심도 있게 설명하겠다.

 

      OpenCL은 재밌는 개념인데, 남아도는 GPU의 코어들을 일반 컴퓨팅 연산에 사용 할 수 있게 지원하겠다는 말이다. 게다가 남아도는 CPU 코어도 같이 ! (아직 GCD와 OpenCL은 유기적으로 연계 된 것 같지는 않다.  내 개인적 추측으로는 Lion 혹은 그 이후에 유기적으로 연계된 GCD와 OpenCL을 보게되지 않을까 생각된다.)


       사실 GPU는 요즘 200개가 넘는 코어를 가지고 오직 그래픽 프로세싱을 위해서만 사용되는 정말 강력한 프로세서 유닛이다. 하지만 일반 프로그래머들에게는 CPU의 SSE류의 인스트럭션셋을 부동소스점 연산을 위해 꾸역꾸역 쓰던것만 허용되었다. 하지만 이제는 애플이 이러한 한계를 풀려고 작정을 한 것 같다. 이제는 프로그래머들이 컴퓨팅 연산을 위해  규격화된 표준 명령어 셋으로 CPU와 GPU의 멀티 코어를 자유롭게 쓸 수 있게 되었다.

이 자료에서는 위의 두가지 멋진 기술중 GCD에 포커스를 맞춰 기술하겠다.


참고 :  만약 시간의 여유가 된다면 아래의 동영상 자료를 보기 바란다. GCD와 OpenCL의 놀라운 마술을 볼 수 있다.
https://www.youtube.com/watch?v=nhbA9jZyYO4

 

1. GCD 소개

      사실 OS에서 지원하는 멀티코어 프로세싱은 애플에게만 있는것은 아니다.

이미 오래전부터 Parallel Programming을 지원하는 OS는 서버쪽으로 시작하여 윈도우 .NET  Framework 4.0의 TPL(Task Parallel Library) 그리고 Java 역시 JDK 7에 Fork/Join Framework를 포함한 JSR166y 등을 통해 멀티코어 프로세싱 프로그래밍을 지원한다.  그리고 정말 오래된, 애플의 오래전 경쟁자였던 BeOS까지..

새로운 기술은 아니지만 지금 애플에서는 GCD를 강력하게 밀고 있다.

이는 다음과 같은 이유에서다.


  • 기존의 어플리케이션은 자체 스레드를 사용하여 개발되었기 때문에 멀티코어 프로세서에 최적화 되지 못하였다.
  • 개발자는 단 몇줄의 코드 추가로 애플 OS단에서 지원하는 멀티코어 프로세서에 최적화된 어플리케이션을 만들 수 있다.
  • 이렇게 어플리케이션 코드를 변경해 놓으면 앞으로 CPU에 코어가 몇개가 붙던 알아서 OS단에서 멀티코어 프로세싱을 지원해 준다.
  • 그러므로 지금부터 GCD를 배워라.

      이것이 지금 우리가 GCD를 공부해야 하는 이유이다. 실제로 2장에서 GCD의 사용 예제를 보게 될 텐데 정말 기존 코드에 몇줄의 GCD코드 삽입으로 멀티코어 프로세싱이 지원되는것을 확인 할 수 있다.

      사실 애플은 iOS4.0부터 GCD를 기본 프레임웍에 포함 시켰다. 그리고 그 이전에는 GCD의 Object-C wrapper class인 NSOperation 및 NSOperationQueue 라이브러리 셋을 iOS2.0 시절부터 지원하였다. 재밌는건 iOS4.0이 발표되던 시절 애플의 어떤 모바일 단말에도 듀얼 코어가 실리지 않았었다.

GCD의 주요 목적은 딱 두가지 이다.


  1. MAC OS에서 비치볼 혹은 iOS에서 스크린 프리징을 없애는 것 !
  2. OS단에서  하나 이상의 CPU 코어에 Thread를 고루 분산 시켜 주는 것 !

      그럼 애플 OS는 어떻게 이러한 Thread를 여러개의 코어에 고루 분산시켜 주는지 살펴 보자.

      OS에는 하나 이상의 어플리케이션이 구동되며  각 어플케이션은 여러개의 Thread를 하나의 CPU에 처리 요청하게 된다. 코어가 하나인 CPU는 꾸역꾸역 이러한 Thread Task List를 수행하겠지만 아무래도 워커 스래드의 양이 많아 지거나 여러개의 어플리케이션이 동시에 heavy thread들을 task list에 등록한다면 어플리케이션들의 속도는 점점 느려지게 될 것이다.  예전에는 이를 해결하기 위해 CPU 클럭을 높였지만 지금은 CPU 코어 수를 늘린다. 그럼 OS는 어떤 숙제를 가지게 될 까?

      하나의 코어에서 수행되던 Thread들을 여러개의  코어로 고루 분산 시켜서 빠르게 Thread task list를 비워 나가게 지원해야 한다. OS는 유휴 코어를 체크 하고 스케줄링하며 이러한 일들을 윈할히 처리해 나갈 것이다. 그런데 또 다른 문제가 있다.


      어플들이 서로 Thread를 처리하기 위해 CPU를 차지하고 사용하려 한다면 ?

지금까지의 pthread 기법에서는 이러한 문제를 해결하기 무척 힘들다. 그럼 어떻게 프로그래머는 CPU의 유휴 코어를 탐지하고 다른 어플들에게 피해를 주지 않으면서 멀티코어 프로세서를 지원하는 Thread 프로그래밍을 할 수 있을까?


CPU의 유휴 코어를 누가 알겠는가?

지금 어떤 어플케이션이 CPU 코어를 점유 하고 있는지 누가 알 수 있나 ? 바로 OS이다.



프로그래머는 그냥 OS에 Task 코드 블럭만 넘기면 OS가 알아서 어플들의 Task들에 우선권 조정을 하고 Thread를 스마트하게 만들어줘서 유휴 코어에 할당하는 것이다.

게다가 Thread 풀링을 사용하여 Thread자원을 재사용까지 하면서…



      이것이 GCD의 핵심 기술이다. 예전 NSOperationQueue에서 Queue를 사용한 것 처럼 프로그래머는 GCD Queue를 이용하여 Task를 Queue에 전달하면 그걸로 끝이다.

정말로 이걸로 끝이다. 나머지는 애플 OS가 다 알아서 해준다. 어플들간의 Task 조율 부터 Thread 만들기, 유휴  코어에 Thread 배정하기, Thread 풀링으로 재사용 하기 등등.

프로그래머는 지겨운 Thread 프로그래밍을 할 필요가 없어 졌다.

      나중에 GCD코드를 보겠지만 소스 코드에 Thread 관련 코드가 완전히 없어져 버렸다.

그럼 GCD 를 어떻게 사용하는지 살펴 보자.


2. GCD 사용법

      CGD를 사용하려면 우선  Block 코딩에 익숙해 져야 한다. 안드로이드 프로그래밍을 할 때 Block은 자연스러운 코딩 방법이었지만 iOS에서는 4.0부터 Block코딩을 지원하기 시작하였다. 사실 나도 왜 Block코딩을 여지껏 지원안했는지 정말 궁금했었다. 그런데 애플은 이미 자기네들은 내부에서 C Level의 Block 코딩을 하고 있었던 거였다. 젠장.

LLVM 근간의 CLang 컴파일러 공식 지원이 그 첫번째 이고, 이미 NSOperation은 iOS2.0 때부터 지원되었던게 그 두번째 증거일 것이다.

      사실 Object-C를 지원하는 애플이 Block 코딩을 지원하는게 그닥 탐탁치만은 안았겠지만 이제는 C base Library인 GCD를 표면적으로 지원해야하니 어쩔 수 없이 Object-C에도 블럭 기법을 추가하게 된 것이다. 도랑 치고 가재 잡는 형상이 되 버린거 같다.

그럼 블럭 코딩에 대해 먼저 살펴 보자.


Block Coding

      “ 블록은 포인터형 함수가 아닌 오브젝트형 함수다” 라는 뜻만 알면 블럭코딩에 대해 모두 안다고 할 수 있다. 포인터형 함수를 스래드에 매칭한다고 가정해보자. 우선 여러 스래드에서 해당 함수를 호출하면 스래드들은 해당 함수 코드 블럭이 존재하는 포인터를 참조하게 된다. 만약 해당 함수에서 전역변수를 사용한다면? 그럼 thread-safe를 위해 lock나 mutual exclusion 코딩이 추가 되어야 할 것이다. 만약 유연한 함수 코드를 지원하기 위해 아규먼트를 사용한다면?  함수 블럭 코드의 유연성을 지원하기 위해 아규먼트의 수량이나 복잡도가 증가하게되고 이는 특정 컨텍스트 관리를 위한 로드 증가 및 콜백 관련 코딩이 지원되어야 하는 고통이 수반될 것이다. 오브젝트형 함수인 블럭은 해당 코드 블럭들을 Thread에서 호출할 때 각각 메모리 상에 로드해 놓음으로서 위의 문제를 조금 간단하게 우회시켜 버렸다. 애플에서는 다음과 같이 블럭을 설명하고 있다.


  • Start out on stack – fast!
  • Blocks are objects!
  • Can be copied to heap!

또한 다음과 같은 경우에 블럭을 사용하라고 권장하고 있다.


  • Enumerations
  • Callback notifications
  • With GCD, moving work off the main thread
  • With GCD, mutual exclusion, concurrency

블럭의 시작은 ^ 으로 시작된다. 이는 마치 포인터의 *를 사용하는 것과 비슷하다.

간단한 블럭 함수 선언과 실행절에서의 사용법은 다음과 같다.


선언절  (void (^)(void))                         (void (^)(BOOL finished))

실행절  ^{ ……}                                      ^(BOOL finished) { …….. }

사용예제는 다음과 같다.

 

 

      위의 예제를 실은 이유는 코드블럭에 이름을 지어주는 방법에 대해 설명하고 싶어서이다. 그럼 GCD의 사용법에 대해 알아 보자. 아래의 예제는 일반적인 Objective-C 코딩이다.



1 2 3 4 5 6 7 
-(void) processImage:(UIImage *) Image { UIImage *processedImage = [ImageHelper doProcessing:Image]; [galleryView setImage:processedImage]   ; }


      간단하게 이미지를 전달받아 이미지 프로세싱을 한 후 이를 갤러리 뷰에 표시해주는 코드 블럭이다. 이 프로그램 코드의 문제는 무엇일까? 바로 이미지 프로세싱을 하는 동안 화면 프로세스는 죽을 수 있다는 것이다. 즉 시쳇말로 화면이 얼어 버릴 수 있다.

왜 그럴까?

      이유는 워커 스래드를 메인 스레드 상에서 처리하기 때문이다. 프로그램 코드에서 메인 스레드와 워커 스레드를 분리해야 할 것이다. 그럼 위의 예제를 Thread 프로그램으로 다시 구성해 보자.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
UIImage *Image = nil;   ....   [NSThread detachNewThreadSelector:@selector(processImage:) toTarget:self withObject:Image];   -(void) processImage:(UIImage *) Image { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; UIImage *processedImage = [ImageHelper doProcessing:Image]; [self performSelectorOnMainThread:@selector(showImage:) withObject:processedImage waitUntilDone:YES]; [pool release];   }   -(void) showImage:(UIImage *)Image { [galleryView setImage:processedImage]   ; }


      위의 예제는 워커 스레드 정의(이미지 프로세싱) 및 워커 스래드 함수 호출( Thread 호출) 그리고 메인스레드로 작업 넘기기(갤러리뷰에 이미지 보이기) 등의 코드로 구현하였다.

위와 같이 Thread프로그래밍을 하면 적어도 화면이 어는 현상은 없어진다. 하지만 여전히 문제는 있다 과연 여기서 멀티코어 프로세서를 어떻게 지원할 것인가 ? 막막하다. 그러려면 왠지 코드가 복잡하고 코드 양도 많아질거 같다. 하지만 아래의 GCD 적용 코드를 한번 살펴보자.



1 2 3 4 5 6 7 8 9 10 11 12 
- (void) processImage:(UIImage *) Image {   dispatch_async(dqueue, ^{ UIImage *processedImage = [ImageHelper doProcessing:Image]; dispatch_async(dispatch_get_main_queue(), ^{ [galleryView setImage:processedImage]   ; }); }); }


      단 두줄 추가하였다. 이렇게 함으로써 멀티코어 프로세서가 지원되는 Thread 프로그램을 만든 것이다. Thread보다 쉽지 않은가? 적어도 나는 NSThread나 NSOperation보다는 간편하였다. 물론 위의 코드는 단적인 예이다. 하지만 대부분의 GCD 코딩은 위의 예제만 습득하여도 충분히 사용 가능하리 만큼 강력 했다.


GCD Queue에 대하여

GCD의 시작과 끝은 GCD Queue로 시작해서 끝난다라고 얘기해도 과언이 아닌거 같다.

그만큼 GCD Queue가 중요한데 그 이유는 다음과 같다.


  • 프로그래머는 테스크를 정의하고 GCD Queue에 던지면 나머지 Thread 관리는 OS단에서 다 알아서 처리해 버린다.
  • GCD Queue는 블럭의 경량화된 리스트이다.
  • 모든 GCD Task는 GCD queue로 Enqueue 된다.
  • 이후 모든 Task는 GCD Queue에 의해 자동으로 Thread 생성되어 Dequeue 된다.

그럼 GCD  Queue에 대해 좀 더 세밀하게 알아보자.

좀 엉뚱하지만 Thread의 메모리상 크기는 얼마일까? 보통 512KB로 잡힌다. 그럼 1000 개의 Thread를 동시에 실행하려면 얼만큼의 리소스가 소요될까? 대략 0.5GB로 아마 아이폰은 예전에 뻗었을 것이다. 실제 내 경험상 아이폰에서 Thread관련 처리를 할 때 동시에 5개 이상 처리는 무리다.

그런데 CGD Queue의 크기는 얼마일까? 256 byte이다.

이것이 바로 블럭의 경량화된 리스트 이다.

그리고 GCD Queue는 Task 하나당 Thread 하나를 만들지 않는다.

Task들이 FIFO방식으로 GCD Queue에 쌓이면 우선 OS 단에서 유휴 코어를 산정하고 Thread Pool에서 재활용 가능한 Thread를 해당 코어만큼 Task로 매칭하여 실행해 버린다.

개발자는 그저 Task가 100개가 되던 1000개가 되던 상관없이 그냥 GCD Queue에 넣어 버리면 끝인것이다..

그럼 GCD Queue에는 어떤것이 있을 까?



  • Main Queue

dispatch_get_main_queue()로 메인 스래드/메인 루프에 테스크를 매칭시킨다.

  • Private Queue

dispatch_queue_create()로 순서대로 큐의 작업을 실행 시킬 때 사용된다.

  • Global Queue

dispatch_get_global_queue()로 Thread pool 방식으로 동작하며, 모든 작업은

이 큐로 보내진 다음 임의의 스레드에서 비동기 실행된다.


 

이상으로 GCD 에 대한 설명을 마치려고 한다.



출처 : http://dev.kthcorp.com/2011/05/19/grand-central-dispatch-gcd-apple-ios/


 객체지향 프로그래밍에서 객체간 메시지를 주고 받는 상황을 비일비재하게 발생하게 됩니다. 객체간의 연관 관계가 존재한다면 단지 객체의 메소드를 호출하는 것만으로 메시지를 전달이 가능합니다. 하지만 객체간 연관 관계가 존재하지 않는다면 메시지를 존재하기 복잡해집니다. 이를 해결하기위해 제공하는 방법 중에 한 가지를 소개하겠습니다. 바로 Notification입니다.


 메시지를 송신할 객체는 NSNotificationCenter를 통하여 Notification을 송신하기 위한 객체를 받습니다. NSNotificationCenter의 [postNotificationName: object: userInfo:]를 통하여 Notification을 보내면 됩니다. 그러면 메시지를 수신할 객체는 어떻게 수신해야할까요?


 수신할 객체는 Observer를 등록해야 합니다. 먼저 Notification을 수신하기 위해서 송실할 때처럼 NSNotificationCenter를 받습니다. 이후, [addObserver: selector: name: object:]를 이용하여 Observer를 등록합니다. NSNotificationCenter는 NotificationName이 일치하는 객체들에게 Notification을 발생시킵니다. Notification을 수신한 각 객체들은 등록되어 있던 selector를 호출하여 상태의 변화를 update시킵니다.






 iOS에서의 진동 구현은 안드로이드에 비해 간단하다. 반면 iOS 정책상으로 진동의 패턴과 길이는 조절할 수 없다. 조절한다고 해도 Reject의 대상이 되기 때문에 앱 스토어에 올릴 수 없다. 참고 바란다.


 먼저 진동을 구현하기 위해서는 AudioToolbox Framework를 import 해줘야 한다.




 위의 그림처럼 프레임워크를 추가한 다음에 다음의 코드를 헤더파일에 추가한다.


#import <AudioToolbox/AudioToolbox.h>


 위와 같이 하면 import과정이 다 끝난다. 이제 진동을 사용하는 부분에 밑의 코드 한 줄만 추가하면 된다. 진동 구현이 안드로이드에 비해 상당히 간편하다


AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

안드로이드와 iOS의 스레드 사용법은 완전히 다르다.


NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadFunc:) object:nil];

[t1 start];


 위와 같이 스레드를 생성해서 이용하면 된다. threadFunc로 스레드의 역할을 정의해주면 된다. 자바의 Thread의 run()이라고 생각하면 이해가 쉽다. 스레드를 종료시킬 때는 스레드를 강재로 죽이는 것보다는 스레드의 실행단위가 완료될 수 있도록 간접적으로 죽이는 방법이 전반적으로 선호된다. 특히 iOS는 시스템의 안전성을 위해서 간접적으로 죽이는 방법만 허용한다.


[t1 cancel];


 cancel을 사용하여 내부의 flag 값을 바꾸어 종료 시키는 방법만 허용 시킨다. 단, threadFunc 내부에서 밑에와 같은 스레드의 종료를 확인하는 명령어 한 줄을 추가 해준다.


if([[NSThread currentThread] isCancelled]  == YES);


+ Recent posts