Extension

IOS/공통 2017. 8. 31. 15:31

Introduction
: Extension은 앱과 다르다. Extension의 바이너리는 Extension포함 또는 배포하는 앱과는 별개로 실행된다. 


An App Extension's Life Cycle
: extension은 App이 아니기 때문에, App의 Life cycle과 다르다. 대부분의 경우 사용자가 사용중인 앱에서 Extension을 사용하려고 할때 Extension이 Launch 된다.
즉, App 에서 실행될 Extension에 request를 보내면 Extension이 Launch, Extension code가 실행되고 해당동작이 끝나면 시스템이 extension을 kill한다.


How an App Extension Communicates
: Host app(extension을 사용하는 앱)이 Extension에게 Request를 날리면, Extension이 해당 처리를 하고 Host app에게 response를 날리는 형식이다. 

<Containing app - Extension을 소유하고 있는 앱> 

containing app과 host app은 전혀 communication 하지 않는다. 심지어 대부분의 경우 extension이 실행 될때 containing app이 실행되지 않는다.

다만, Today widget과 같은 몇몇 extension은 NSExtensionContext의 openURL:completionHandler: 메서드를 통해 containing app을 열 수 있다. 그리고 containing app은 필요한 경우 app extension과 shared resource가 저장된 공통의 private한 저장소를 공유할 수 있다. 아래 그림을 보자.



Some APIs Are Unavailable to App Extensions
: 몇몇 API들은 extension에서 사용할 수 없다.

- sharedApplication 객체를 아예 사용 못함
- API의 헤더파일에 "NS_EXTENSION_UNAVAILABLE" 또는 비슷한 의미의 매크로가 붙은 것은 사용 불가. (ex- in iOS 8에서 HealthKit Framework, EventKit UI Framework)
- camera, microphone 을 접근할 수 없다. (다만, iMessage app은 camera, microphone을 사용할 수 있다. 그 대신 Info.plist에 NSCameraUsageDescription, NSMicrophoneUsageDescription을 세팅해야 한다.)
- 장시간 걸리는 background task는 수행할 수 없다. (limit 정책은 플랫폼에 따라 다양하다. 나중에 언급하도록 하겠다. 그리고 extension은 NSURLSession을 통해 upload, download 작업을 할 수 있다.)
- AirDrop을 통해서 data를 받을 수 없다. (다만, UIActivityViewController class를 통해서 AirDrop으로 data를 보내는 것은 가능하다.)
 UIBackgroundModes를 extension의 plist에 포함하면 reject 당한다.


Using an Embedded Framework to Share Code
: containing app 과 extension이 코드를 공유하기를 원한다면 embed framework에 해당 코드를 넣고 embed framework를 양쪽 타겟에 포함시키면 된다.
단, embed framework에는 extension에서 사용할 수 없는 API들을 포함 시키면 안된다. 
그리고 extension target의 "Require Only App-Extension-Safe API"를 YES로 세팅해야 한다. 
또한, build phase 탭의 Embed Frameworks의 Destination을 "Frameworks"로 지정해야 한다. "SharedFramework"로 지정할 경우 앱 심사에서 reject 당한다. 


Sharing Data with Your Containing App
: app extension과 containing app은 기본적으로 서로의 container에 접근하지 못한다. 그러나 서로의 데이터를 공유하기 위한 방법이 있다.

- xCode 또는 Developer portal에서 app groups을 enable 시킨다.
- appGroup을 등록하고, containing app이 appGroup을 사용하도록 지정한다.
- 위 두단계를 거치면, containing app과 extension은 NSUserDefaults를 사용하여 data를 공유할 수 있다. UserDefaults를 init할때 shared group의 identifier로 init 해야 한다. 

NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.example.domain.MyShareExtension"];

[mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"];

* extension에서 NSURLSession을 사용하여 background upload, download를 하려면 shared container를 setup 해야한다. 그래야만 extension과 containing app 모두가 전송되는 data에 접근할 수 있다. (https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW2)

*Version Note
in IOS 8.2 and later, you can alternatively employ the UIDocument class to coordinate shared data access.

in IOS 9 and later, you can employ the NSFileCoordinator class directly for shared data access, but if you do this you must remove your NSFilePresenter objects when your app extension transitions into the background.


Accessing a Webpage
: Share extension, action extension에서 javaScript 파일을 통해서 web contents 들을 extension 으로 받아올 수 있다. 예를 들면, Share extension을 통해서 web content를 자신의 앱에 공유하는 기능을 추가할 수 있다는 것이다.(https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1)


Declaring Supported Data Types for a Share or Action Extension
: Share 또는 Action extension에서 host app이 extension에 data를 넘겨주는데, extension 에서 넘겨받기를 원하는 data type을 지정할 수 있다. extension의 Info.plist의 NSExtensionActivationRule key로 지정한다. 또한 해당 타입 data의 max, min 갯수도 지정할 수 있다. 

  1. <key>NSExtensionAttributes</key>
  2. <dict>
  3. <key>NSExtensionActivationRule</key>
  4. <dict>
  5. <key>NSExtensionActivationSupportsImageWithMaxCount</key>
  6. <integer>10</integer>
  7. <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
  8. <integer>1</integer>
  9. <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
  10. <integer>1</integer>
  11. </dict>
  12. </dict>
  13. 특정 data type을 지원하지 않으려면 0값을 세팅하거나 해당 데이터 타입의 key, value
  14. 를 삭제하면 된다.

* Share 또는 Action Extension에서 webpage에 접근하기를 원한다면 NSExtensionActivationSupportsWebPageWithMaxCount key의 값을 지정하면 된다.

위의 NSExtensionActivationRule dictionary에 값을 지정하는 것만으로도 충분히 타입지원을 할 수 있지만, 좀 더 세밀하게 컨트롤하고 싶을 경우 "predicate statement"(NSPredicate) 문법을 통해 지원할 수 있다.(https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1)



Deploying a Containing App to Older Versions of iOS
- https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1(맨 아래 참고)


* 각 extension type에 대한 설명
- https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Action.html#//apple_ref/doc/uid/TP40014214-CH13-SW1

'IOS > 공통' 카테고리의 다른 글

Bundle Programming Guide  (0) 2017.06.11
Event Handling Guide - Responders  (0) 2017.05.03
Event Handling Guide - Gesture  (0) 2017.04.07
Objective-C, Swift 기초  (0) 2017.03.26
Cocoa Pods  (0) 2017.02.19
Posted by 홍성곤
,

Application Bundles
: Application Bundle은 앱이 실행되기 위한 모든 데이터들을 담고 있는 bundle이라고 보면 된다. 상세 구조는 개발하는 Platform에 따라 달라질 수 있지만 사용하는 방법은 똑같다.

What Files Go Into an Application Bundle?
1) Info.Plist(필수) 
-  앱의 설정 파일이라고 보면 된다.
2) Executable(필수)
- 앱은 반드시 이 파일을 포함해야 한다. 이 파일은 앱의 main entry point와 app target으로 링크된 code가 포함되어 있다.
3) Resource files
- 이 파일은 executable file 바깥에 있다. 대표적으로 image, icon, sound, nib file, string file, configuration file, data file등이 포함된다. 대부분의 resource file들은 사용언어, 지역에 따라 localized될 수 있다. 
- Resource는 localized가 필요한것과 그렇지 않은것으로 나뉠수 있는데, localized가 필요한것을 app 번들 root에 각자 subdirectory들이 존재하고 그 subdirectory안에 똑같은 파일들과 똑같은 이름을 가진 파일들이 각각 들어가 있다. app이 localized resource를 찾을때는 language preference를 참조해서 해당 폴더를 결정한다.
4) Other support files
- custom data resource를 Application bundle에 포함 시킬 수 있다. 하지만 custom framework나 plug-in은 포함하지 못한다.

Anatomy of an iOS Application Bundle
The iOS Application bundle Structure
: 전형적인 iOS Application Bundle은 App이 사용하는 Executable과 resource들을 bundle top directory안에 포함한다.

*Bundle structure of an iOS application

- subdirectory에 있는 파일들은 localized가 필요한 파일들이다. 또한 개발자들이 필요에 의해서 추가적인 subdirectory들도 만들수 있다. 

*Contents of a typical iOS application bundle 
1) MyApp(필수)
- executable file이다. application 코드가 포함되어 있다. 파일 이름은 application name에 ".app" extension을 제외한 문자열이다.
2) Application icons(필수/권고)
- MyAppIcon.png, MySearchIcon.png, MySettingIcon.png
- 모든 icon이 필수인것은 아니지만 모두 포함하기를 권고하고 있다. SearchIcon은 앱 검색시 나오는 아이콘이고 MySettingIcon은 설정화면에서 노출되는 아이콘 이미지 이다.
3) Info.plist(필수)
- 앱 정보, 설정파일
- bundle ID, version number, display name.
4) Launch images(권고)
- 앱이 처음 켜질때 보여지는 하나 또는 그 이상의 이미지들이다.
5) MainWindow.nib(권고)
- 앱의 main nib file이다. 대표적으로, 이 파일은 app의 main window object, application delegate object를 포함한다.
- 요즘도 널리 사용중인지는 모르겠다.(본적이 없음..)
6) Setting.bundle
- plug-in의 특별한 타입이라고 보면 된다. 설정 앱에 추가하고 싶은 컨텐츠를 설정하고 싶을 때 사용한다. 
- property list, other resource file들을 포함한다.
7) Custom resource files
- nib file, image, sound files, configuration files, string files, any other custom data files.
- Non-localized resource들은 top level directory에 위치하고, localized resource들은 language-specific subdirectory 안에 위치한다.

* iOS app bundle은 "Resources"라는 이름의 custom folder를 포함하지 못한다.



Framework Bundles
: Framework는 dynamic shared library와 resource file을 포함하는 directory구조다.
Framework는 dynamic shared library가 가지는 장점을 그대로 가진다. 즉, 여러 application에서 같은 dynamic shared library를 사용하면 메모리에는 하나의 library만 올라간다. 모든 application에서 같은 library라 할지라도 각각의 library와 resource file을 중복으로 가지고 있거나 사용하는 비효율을 막기 위함이다. 
동작 방식은 build 타임에는 shared library를 링킹만 시켜주고 application이 실제 로드 될때 dynamic loader가 링크된 shared library를 참조해서 이미 로딩되어 있으면 기존것을 사용하고 로딩되어 있지 않으면 shared library를 로드해서 사용하는 방식이다.

* 오직 code와 read only resource들만 공유한다. writable resource들은 각자 application이 copy해서 사용한다.

Anatomy of a Framework Bundle
: Framework는 dynamic shared library를 포함하기 때문에 다른 앱이 같은 framework를 쓰지만 framework의 version이 서로 다를 경우가 있다. 이를 위해서 Framework의 구조는 multiple version을 지원하는 versioned bundle구조로 되어 있다. 
즉, multiple version의 code와 resource들을 포함할 수 있다. 필요에 따라서 하위 버전을 사용하는 앱이 최신버전의 framework를 사용하도록 업데이트 되는것도 허용된다.

아래는 framework의 구조이고, symbolic link가 어떤식으로 되어있는지 보여준다.






Localized Resources in Bundles
: localized subdirectory의 이름은 'language_region.lproj'의 규칙을 따라야 한다.
language 부분은 두글자를 써서 구분한다.(ISO 639 convention을 따름.) 
region 부분 역시 두글자를 써서 구분한다.(ISO 3166 convention을 따름.) region을 명시하는 것은 optional 이지만 지역별로 다르게 대응해줘야 할 경우 유용하게 사용된다.
예를들어 'en.lproj'로 영어 국가는 모두 대응할 수 있다. 하지만 'en_GB.lproj', 'en_AU.lproj', 'en_US.lproj'를 통해 영국, 호주, 미국을 따로 대응할 수 있다.

*NSBundle, CFNundleRef는 이전버전과의 호환성을 위해 human-readable directory names응 지원한다. English.lproj, japanese.lproj 등등.. 
하지만 ISO convention을 따르는 것을 추천한다.

loader는 정해진 순서대로 해당 폴더를 참조하여 resource를 가져온다. 처음에는 언어+지역에 해당하는 폴더에서 resource를 찾고 그곳에 없으면 언어에 해당하는 폴더에서 찾고 그곳마저 없으면 non-localized resource들 중에 찾는다. 즉, 모든 localized subdirectory안에 resource 파일을 폴더마다 중복으로 가지고 있을 필요가 없다.




이와같이 언어_지역 subdirectory는 localized resource를 full set으로 가지고 있을 필요가 없다. 없는 resource는 언어 subdirectory에도 찾을 것이기 때문이다. 하지만 언어 subdirectory는 localized resource를 full set으로 가지고 있어야 한다.

*Resource 폴더는 무시해라.(Mac app에만 존재하는 구조이다. iOS는 App bundle root라고 생각하면 된다.)






'IOS > 공통' 카테고리의 다른 글

Extension  (0) 2017.08.31
Event Handling Guide - Responders  (0) 2017.05.03
Event Handling Guide - Gesture  (0) 2017.04.07
Objective-C, Swift 기초  (0) 2017.03.26
Cocoa Pods  (0) 2017.02.19
Posted by 홍성곤
,

Understanding Responders and the Responder Chain
: UIKit은 event가 들어오면 적절한 responder object로 전달한다. responder object는 UIResponder 또는 이를 상속한 subclass들이다. UIView, UIViewController, UIApplication이 이에 해당한다. 
제일 처음 event를 전달받는 object를 first-responder라 부른다. first-responder가 event를 처리하지 못하면 event는 responder chain에 의해서 next-responder로 넘어간다. 해당 flow를 그림을 통해 살펴보자.

위 그림에서 사용자가 UITextField를 클릭했다고 가정 시, first-responder인 UITextField가 event를 처리할 것이다. 만약 UITextField가 event를 처리하지 못하면 superview에게 event가 넘어간다. 이렇게 superview를 계속 따라가다보면 root view가 나오는데 root view 마저 event를 처리하지 못하면 UIViewController가 event를 처리하게 된다. 만약 UIViewController도 event를 처리하지 못하면 자신을 present하고 있는 viewController가 있으면 그쪽으로 event를 넘기고 아닐경우 UIWindow가 event를 받고, 역시 처리하지 못할 경우에는 UIApplication이 받아서 event를 UIApplicationDelegate로 넘긴다.

위 로직을 간단히 정리해 보면 다음과 같다.
first-responder -> super view -> UIViewController -> presenting ViewController -> UIWindow -> UIApplication(UIApplicationDelegate) 


* 만약, view에 gesture recognizer가 설정되어 있으면 touch event가 무시될 수 있다. 예를 들어 view에  tap gesture recognizer가 설정되어 있고, UIGestureRecognizer의 delaysTouchesBegan이 YES로 설정되어 있으면 해당 view를 tap해도 tap gesture action만 불리고 touchesBegan:withEvent: 메서드를 불리지 않는다. 
이러한 제어들은 UIGestureRecognizer의 delaysTouchesBegan, delaysTouchesEnded, cancelsTouchesInView를 통해 할 수 있다.

Determining Which Responder Contained a Touch Event
: UIKit은 어느 view에서 touch가 일어났는지 판별하기 위해서 UIView의 hitTest:withEvent: method를 사용한다. hitTest:withEvent: 메소드는 touch가 일어난 point를 포함하는 subview들 중 가장 먼 subview를 return한다. 바로 그 view가 first-responder가 되는것이다.


Altering The Responder Chain
: nextResponder 프로퍼티를 override해서 자신이 원하는 object를 nextResponder로 지정할 수 있다.
UIView는 이미 nextResponder를 override해서 사용하고 있다. 만약 UIView가 viewcontroller의 Root View라면, nextResponder를 viewcontroller로 지정하고 root view가 아니면 super view로 지정한다. 
UIViewController도 마찬가지로 자신의 root view가 UIWindow의 root view라면 UIWindow로 next responder를 지정하고 그게 아니면 presenting ViewController로 지정한다.

Directing Input to a Specific Responder
: UIResponder를 상속받은 object들은 event를 받으면 firstResponder가 될 수 있다. 다만, UITextFiled, UITextView처럼 event를 받는 즉시 자동으로 firstResponder가 되는 경우가 있고, UIButton, UIView처럼 명시적으로 becomeFirstResponder 메서드를 호출해야 firstResponder가 되는 경우가 있다. 
firstResponder가 되면 UIKit은 해당 object에 등록해논 input view를 display한다. UITextField, UITextView의 input view는 system keyboard view이다. 

Programmatically Changing the First Responder
: 명시적으로 firstResponder로 지정하기 위해 becomeFirstResponder 메서드를 호출하면 UIKit은 해당 object가 firstResponder가 될 수 있는지 몇가지 체크를 한다.

1. 현재의 firstResponder object에게 canResignFirstResponder 메시지를 보내 firstResponder를 resign할 수 있는지 확인한다.
보통 대부분의 object들은 YES를 리턴하지만, 필요에 따라 NO를 리턴하게 하는 경우가 있다. 대표적으로 textField, textView가 invalid한 data를 포함했을 경우이다.
2. firstResponder로 지정하고자 하는 object에게 canBecomeFirstResponder 메시지를 보내 firstResponder가 될 수 있는지 확인한다.(default값이 NO다.)

Assigning an Input View to a Responder
: object가 firstResponder가 될 때 inputView를 띄우기 위해서는 해당 object에 inputView 또는 inputViewController 프로퍼티를 세팅해줘야 한다. inputView, inputViewController를 올바르게 사용하기 위해서 알아둬야할 몇가지가 있다.

1. inputView의 UI를 screen width에 맞춰서 구현해야 한다.
- UIKit은 screen width와 내가 지정한 높이로 input view의 size를 결정하기 때문이다.
2. delegate를 사용해서 inputView를 통해 들어온 data를 firstResponder에게 전해줘라.
3. keyboard notifications를 사용해라.
- system keyboard view뿐만 아니라 firstResponder의 inputView는 모두 keyboard notification들을 사용할 수 있다.
(UIKeyboardWillChangeFrameNotification, UIKeyboardWillShowNotification, UIKeyboardDidChangeFrameNotification, UIKeyboardDidShowNotification)

responder는 inputView이외에도 accessory view를 가질 수 있다. inputAccessoryView, inputAccessoryViewController 프로퍼티를 이용해서 지정한다. accessory view는 input view를 보조하는 역할을 한다. 예를들어 system keyboard view는 accessory view를 이용해서 자동완성 기능을 제공한다. 

'IOS > 공통' 카테고리의 다른 글

Extension  (0) 2017.08.31
Bundle Programming Guide  (0) 2017.06.11
Event Handling Guide - Gesture  (0) 2017.04.07
Objective-C, Swift 기초  (0) 2017.03.26
Cocoa Pods  (0) 2017.02.19
Posted by 홍성곤
,

Gestures
Tap Gesture

tap gesture action 메서드가 호출되지 않을 때.
- userInteractionEnabled가 YES인지 확인
- numberOfTapsRequired 프로퍼티가 제대로 설정되었는지 확인.
- numberOfTouchesRequired 프로퍼티가 제대로 설정되었는지 확인

Long Press Gesture
long press gesture action 메서드가 호출되자 않을 때.
- userInteractionEnabled가 YES인지 확인.
- mininumPressDuration 프로퍼티에 설정된 시간보다 길게 press 하고 있었는지 체크.
- numberOfTapsRequired 프로퍼티가 제대로 설정되었는지 확인.
- numberOfTouchesRequired 프로퍼티가 제대로 설정되었는지 확인.

Pan Gesture
- UIPanGestureRecognizer 이외에 UIScreenEdgePanGestureRecognizer가 있다. UIScreenEdgePanGestureRecognizer는 스크린 Edge에서 panGesture가 발생할 경우 핸들링 하기 위한 것이다.

pan gesture action 메서드가 호출되지 않을 때.
- userInteractionEnabled가 YES인지 확인.
- mininumNumberOfTouches와 maximumNumberOfTouches사이 갯수의 손가락으로 터치해야 한다.
- UIScreenEdgePanGestureRecognizer의 경우 edges 프로퍼티가 설정되어 있어야 하고, 해당 edges에서 event가 시작되야 한다.

Swipe Gesture
swipe gesture action 메서드가 호출되지 않을 때.
- userInteractionEnabled가 YES인지 확인.
- touch한 손가락 갯수가 numberOfTouchesRequired와 같아야 한다. 
- direction 프로퍼티와 swipe한 방향이 맞아야 한다. 

*Pinch Gesture, Rotation Gesture는 생략.


Custom Gesture Recognizer
: Custom Gesture Recognizer를 구현하기 위해서는 UIGetstureRecognizerSubclass.h 파일을 import하고 touchesBegan:withEvent, touchesMoved:withEvent:, touchesEnded:withEvent, touchesCancelled:withEvent: 등의 메서드를 오버라이드 해서 custom recognizer를 구현해 줘야 한다. 
올바르게 구현하려면 discrete gesture recognizer, continuous gesture recognizer가 각각 가질수 있는 state등 여러가지 사전 지식이 필요하다. 자세한 구현방법은 설명하지 않겠다.

참조
https://developer.apple.com/library/content/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/ImplementingaCustomGestureRecognizer.html#//apple_ref/doc/uid/TP40009541-CH8-SW1


Coordinating Multiple Gesture Recognizers
: 동시에 두개 이상의 Gesture Recognizer를 다룰때, 별 다른 처리를 하지 않으면 충돌하기 마련이다. 예를 들어, 하나의 뷰에 Pen gesture Recognizer와 Swipe gesture Recognizer를 동시에 설정하면 항상 Pen gesture만 인식이 될 것이다. Pen gesture는 continuous gesture이기 때문에 Swipe gesture 보다 우선순위가 높다. 이런 경우에 우선순위를 어떻게 처리해야할 지 알아보겠다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
    if gestureRecognizer = self.penGesture && otherGestureRecognizer == self.swipeGesture
    {
        return true
    }
    
    return false
}
위 메서드는 UIGestureRecognizer의 delegate인데 panGesture에 해당 delegate를 설정해 주고 swipeGesture의 state가 fail이 아닌 경우에만 panGesture를 인식하도록 했다.

또 다른 delegate가 있는데 주체만 바뀌었을 뿐 동작 방식은 같다. 

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
    if gestureRecognizer = self. swipeGesture && otherGestureRecognizer == self.panGesture
    {
        return true
    }
    
    return false    
}

또한, 동시에 2개이상의 gesture recognition을 허용할 수 있다.
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: delegate를 설정하여 처리하면 된다. 이 delegate에서 YES를 return 하면 Simultaneous Recognition을 허용하겠다는 것이다.


Attaching Gesture Recognizers to UIKit Controls
: 일반적으로 내가 설정한 gesture recognizer는 UIKit Control들이 자체적으로 가지고 있는 event handling 동작에 영향을 미치지 않는다. 예를 들어 view에 버튼을 올리고 view에  tapGesture를 등록해도 버튼 event에 대한 동작은 손상되지 않는다는 것이다. 다만 버튼 evnet에 대한 동작에 우선하는 gesture recognizer를 등록하고 싶다면 button 자체에 gesture recognizer를 등록해야 한다. 다만, 추천하지는 않는다. 


출처
- https://developer.apple.com/library/content/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/index.html#//apple_ref/doc/uid/TP40009541-CH3-SW1

'IOS > 공통' 카테고리의 다른 글

Bundle Programming Guide  (0) 2017.06.11
Event Handling Guide - Responders  (0) 2017.05.03
Objective-C, Swift 기초  (0) 2017.03.26
Cocoa Pods  (0) 2017.02.19
About Bundle  (0) 2017.02.14
Posted by 홍성곤
,

Introduction
: class type에만 적용되는 개념. class instance가 dealloc 되기 직전에 호출된다.


How Deinitialization Works
: Swift는 ARC를 사용하여 메모리 관리를 하기 때문에 일반적인 경우에는 수동으로 메모리 관리를 할 필요가 없다. 하지만 자신의 resource를 사용하는 경우 수동으로 clean up하는 동작이 필요할 때가 있다. 예를들어, 파일을 열고 쓰는 class를 사용할 경우 class가 dealloc 되는 시점에 해당 파일을 닫는 작업이 실행되어야 한다.
: 직접 deinitializer를 호출하는 것은 허용되지 않는다. super class의 deinitializer는 sub class에서 자동 상속 되고, sub class의 deinitializer 마지막에 super class의 deinitializer가 불린다. 


Deinitializers in Action
: 아래 코드를 보며 deinitializer가 어떤 역할을 하는지 살펴 보도록 하자.

class Bank {
    static var coinsInBank = 10_000
    
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        
        coinsInBank -= numberOfCoinsToVend
        
        return numberOfCoinsToVend
    }

    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

class Player {
    var coinsInPurse: Int

    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }

    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}

var playerOne: Player? = Player(coins: 100)

print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
//Prints "A new player has joined the game with 100 coins"

Print("There are now \(Bank.coinsInBank) coins left in the bank")
//Prints "There are now 9900 coins left in the bank"

playerOne!.win(coins: 2_000)

print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
//Prints "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
//Prints "The bank now only has 7900 coins left"

playerOne = nil

print("PlayerOne has left the game")
//Prints "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
//Prints "The bank now has 10000 coins"

'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Initialization(2)  (0) 2017.04.01
[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
Posted by 홍성곤
,

Failable Initializers
: Class, Structure, Enumeration을 init할 때 initialization 과정이 실패할 가능성이 있는 경우 Failable Initializer를 정의할 수 있다. 'init' keyword 뒤에 '?'을 붙여서 정의한다.

* 똑같은 파라미터 타입, 갯수, 이름으로 failable initializer, nonfailable initializer를 동시에 정의할 수는 없다.

failable initializer는 optional instance를 return한다. init이 실패했을 경우는 nil을 return 한다.

* 엄밀히 말하면 initializer는 값을 return하지 않는다. init과정이 성공했다고 해서 'return self' 를 하지 않듯이, initializer에 'return nil'이 의미하는것은 실제 nil을 return 한다는 것이 아니라 init에 실패했다라고 알려주는 역할에 가깝다고 보면된다.

먼저, Swift에서 제공하는 failable Initializer를 사용해 보자. numeric conversion을 하는데 있어 type conversion에 실패를 할 경우 nil을 return 하는 init(exactly:) initializer를 살펴볼 것이다.

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value")
}
// wholeNumber를 Int type으로 conversion 하는데 아무 문제가 없으므로 Int 인스턴스를 return 한다.

let valueChanged = Int(exactly: pi)

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// pi는 Int로 Conversion 할 수 없기 때문에, nil을 return 한다.

이제 직업 failable Initializer를 사용해 보겠다.

struct Animal {
    let species: String
    
    init?(species: String) {
        if species.isEmpty { return nil }
        
        self.species = species
    }
}
// init에 넘어온 species 파라미터가 empty string이면 init에 실패한다. 

let someCreature = Animal(species: "Giraffe")
// someCreature 는 Animal 타입이 아니라, Animal? 타입이다.

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"

let anonymousCreature = Animal(species: "")
// anonymousCreature 는 Animal 타입이 아니라, Animal? 타입이다.

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"

Failable Initializers for Enumerations
: Enumeration의 Failable Initializer는 파라미터로 넘어온 값에 매칭되는 enum 값이 없을때 init을 Fail 시킨다.

enum TemperatureUnit {
    case kelvin, celsius, fahrenheit
    
    init? (symbol: Character) {
        switch symbol {
        
            case "K":
                self = .kelvin
            case "C"
                self = .celsius
            case "F"
                self = .fahrenheit
            default:
                return nil

        }        
    }
}

let fahrenheitUnit = TemperatureUnit(symbol: "F")

if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}

//Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(symbol: "X")

if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Failable Initializers for Enumerations with Raw Values
: raw Value를 갖는 Enumeration은 자동으로 failable initializer, init?(rawValue)을 갖는다.
rawValue와 맞는 case가 없으면 initialization이 실패한다. 

enum TemperatureUnit: Character {
    case kelvin = "K", celsius = "C", fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")

if (fahrenheitUnit ! = nil) {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")

if (unknownUnit == nil) {
    print ("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Propagation of Initialization Failure
: class, structure, enumeration의 failure initializer는 다른 failure initializer로 delegation이 가능하다. subclass 또한 super class의 failure initializer로 delegation이 가능하다.

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil } 
        
        self.name = name
    }
}

class CarItem: Product {
    let quantity: Int
    
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        
        self.quantity = quantity
        
        super.init(name: name)
    }
}

if let twoSocks = CarItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
//Prints "Item: sock, quantity: 2"

if let zeroShirts = CarItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
// quantity가 1보다 작기 때문에 quantity 체크하는 initializer에서 init 과정이 실패하고, 더이상의 init 작업은 진행되지 않는다.

if let oneUnnamed = CarItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize on unnamed product"

Overriding a Failable Initializer
: sub class에서 failable initializer도 다른 initializer 처럼 override가 가능하다. 또한 super class의 failable initializer를 sub class에서 non-failable initializer로 override가 가능하다. 단, 이 경우에는 super class의 failable initializer로 init 작업을 delegate-up 하기 위해서는 force-unwrap 과정이 필요하다.

*super class의 failable initializer를 non-failable initializer로 override 하는 경우는 보통 자식클래스에서는 init 과정이 실패할 가능성이 없을경우 이런식으로 구현하는데 왠만하면 이 방식을 사용하지 않는것이 좋다. 명시적으로 force unwrap 하는 것은 이유를 불문하고 위험한 행동이다.

class Document {
    var name: String?
    
    init() {}

    init?(name: String) {
        if name.isEmpty { return nil }
        
        self.name = name
    }
}

위 Document 클래스는 name property에 empty string값이 들어가는것을 허용하지 않는다.(nil은 가능)

class AutomaticallyNameDocument: Document {
    override init() {
        super.init()

        self.name = "[Untitled]"
    }
    
    override init(name: String) {
        super.init()
        
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

위 AutomaticallyNameDocument 클래스에서는 initialization이 실패할 가능성을 차단했다. 

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

위 UntitledDocument 클래스는 name에 nil이 할당된 채로 instance가 초기화 되는것을 차단했다.


Required Initializers

: 모든 subclass에게 특정 initializer의 구현을 강제화할 목적으로 사용하는 것이 required initializer이다. super class의 required initializer는 무조건 구현해야 한다.

class SomeClass {
    required init() {
        
    }
}

subclass 에서도 'required' 키워드를 붙여줘야 한다. 그래야 subclass의 subclass에서도 required initializer의 구현을 강제화할 수 있기 때문이다. required initializer를 override 할 때에는 'override' 키워드는 붙이지 않는다.

class SomeSubclass: SomeClass {
    required init() {

    }
}

*단, 자동으로 super class의 initializer들을 상속받게 되는 경우에는 명시적으로 super class의 required initializer를 구현하지 않아도 된다.(예를들어, sub class의 stored property 들이 default 값을 가지고 있거나 stored property가 없어서 custom initializer가 필요없는 경우에 실제로 custom initializer를 제공하지 않은 경우.)


Setting a Default Property Value with a Closure or Function 
: stored property에 default 값을 할당하기 위한 방법 중 하나로 closure 또는 global function을 사용할 수 있다. 해당 closure와 global function은 instance가 생성될 때 실행되어 property에 값을 할당한다. 당연히 이 closure, global function 안에서는 다른 property들에 대한 접근, self 및 다른 instance 메서드들에 대한 접근도 허용하지 않는다.

struct Chessboard {
    let boardColors: [Bool] = {
        var temporaryBoard = [Bool]()
        var isBlack = false
        
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }

            isBlack = !isBlack
        }
        
        return temporaryBoard
    }()

    func squareIsBlackAt(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

let board = Chessboard()

print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"

'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Deinitialization  (0) 2017.04.01
[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Objective-C와 달리 Swift의 initializer는 값을 return 하지 않는다. 또한 Swift에는 initializer와 pair되는 deinitializer가 있다. deinitializer에서 instance가 dealloc 되기전에 해야되는 작업을 해야한다. 


Setting Initial Values for Stored Properties
: Class와 Structure는 stored property들을 자신의 instance가 만들어 질때까지 적절한 값으로 세팅되어 있어야 한다.
즉, initializer 안에서 property의 초기값을 세팅하거나, property 선언과 동시에 default property 값을 세팅해야 한다.

* 위에 설명된 바와 같이 initializer안에서 property의 값을 초기화 하거나, property 선언과 동시에 default 값을 세팅하면 property observer들이 호출되지 않는다.


Initializers

init () {
    // perform some initialization here
}
// 가장 심플한 형태의 initializer다.

struct Fahrenheit {
    var temperature: Double
    
    init() {
        temperature = 32.0
    }    
}

var f = Fahrenheit()
print("The default temperature is \(f.temperature) Fahrenheit")
//Prints "The default temperature is 32.0 Fahrenheit"


Default Property Values
: property 초기화를 선언과 동시에 할 수도 있다.

struct Fahrenheit {
    var temperature = 32.0
}


Customizing Initialization
Initialization Parameters
: initializer는 파라미터를 가질 수 있다.

struct Celsius {
    var temperatureInCelsius: Double
    
    init (fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }

    init (fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

이처럼 원하는대로 initializer를 만들 수 있으며 각 initializer는 파라미터들을 가질 수 있다.

Parameter Names and Argument Labels
: initializer는 function과 같이 parameter 이름과 argument label을 가질 수 있다. parameter 이름은 initializer 내에서 paramenter를 참조할 때 쓰이고, argument label은 initializer를 호출할 때 쓰인다. 
그러나 initializer는 함수와 달리 자체적으로 이름을 가질 수 없다. 그래서 initializer끼리의 구별은 parameter의 이름과 타입으로 initializer를 구별한다. 그래서 Swift는 자동으로 Initializer의 parameter 이름을 argument label로 지정해준다. 

struct Color {
    let red, green, blue: Double
    
    init (red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }

    init (white: Double) {
        red = white
        green = white
        blue = white
    }

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

initializer를 호출할 때는 꼭 argument label을 명시해줘야 한다. 그렇지 않으면, compile error가 발생한다.

let veryGreen = Color(0.0, 1.0, 0.0)
// compile error 발생~

Initializer Parameters Without Argument Labels
: initializer의 argument label을 생략할 수 있는 방법이 있다. argument label이 들어갈 자리에 '_'를 명시하면 된다.

struct Celsius {
    var temperatureInCelsius: Double

    init (fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }

    init (fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }

    init (_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}

let bodyTemperature = celsius(37.0)

// bodyTemperature.temperatureInCelsius is 37.0

Optional Property Types
: 만약 당신이 만든 custom type이 optional type의 stored property를 갖고 있다면, 이런 의도일 것이다. "이 property는 initialization 동안에는 값을 가질수 없는 property 야" 또는 "이 property는 값을 가지고 있다가도 나중에는 값을 가질 수 없는 상태도 존재할거야" 
optional property는 nil 값으로 초기화 된다. 이것은 아직 초기화 되지 않은 상태라는것을 명시적으로 표현하기 위함이다.

class SurveyQuestion {
    var text: String
    var response: String?
    
    init (text: String) {
        self.text = text
    }
    
    func ask() {
        print(text)
    }
}

let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")

cheeseQuestion.ask()
//Prints "Do you like cheese?"

cheeseQuestion.response = "Yes, I do like cheese."

cheeseQuestion.response는 question이 초기화 되기전까지는 값을 가질 수 없는 상태이다. 위의 예제는 억지로 만들어낸 상황이라고 생각할 수 있지만, 코딩을 하다보면 위와 같은 상황은 언제든지 겪을 수 있다.

Assigning Constant Properties During Initialization
: initialization 과정중에 constant property에도 값을 할당하는 것이 가능하다. 만약 클래스의 경우라면 마찬가지로 initialization 과정중에 constant property에 값 할당이 가능하지만, subclass에서는 불가능하다.
위 SurveyQuestion의 text property를 let으로 변경하는것이 더 바람직해보인다. 한번 해보자.

class SurveyQuestion {
    let text:String
    var response: String?
    
    init (text: String) {
        self.text = text
    }

    func ask() {
        print(text)
    }
}

let beetsQuestion = SurveyQuestion(text: "How about beets?")

beetsQuestion.ask()
//Prints "How about beets?"

beetsQuestion.response = "I also like beets. (But not with cheese.)"


Default Initializers
: Class나 Structure는 Default initializer를 가질 수 있다. 단, 조건이 있다. 모든 property들이 default 값을 가지고 있어야 한다. 그리고 다른 initializer를 제공하지 않아야 한다. 

class ShoppingListItem  {
    var name: String?
    var quantity = 1
    var purchased = false
}

var item = ShoppingListItem()

위 클래스의 name property는 optional이기 때문에 default nil값을 가진다. 그러므로 모든 property들이 default값을 가지게 되므로 default initializer를 통해 instance를 만들 수 있다.

Memberwise Initializers for Structure Types
: Structure는 다른 custom initializer를 제공하지 않으면 Memberwise Initializer로 instance를 생성할 수 있다. 모든 property를 초기화 하는 initializer이고 property 이름을 argument label로 사용한다. 

struct Size {
    var width = 0.0, height = 0.0
}

let twoByTwo = Size(width: 2.0, height: 2.0)

* 만약 당신이 만든 custom value type이 default initializer, memberwise initializer, custom initializer를 동시에 갖기를 원한다면, custom initializer를 extension에 선언하면 된다.



Initializer Delegation for Value Types
: initializer는 다른 initializer를 호출할 수 있다. 이것은 instance 초기화의 한 부분으로 동작하며, 여러개의 initializer가 존재할 때, 중복 코드 생성을 피하기 위함이다. 
delegation 동작은 value type(structure, enumeration)과 class type간에 차이가 존재한다. 그도 그럴것이 value type은 상속이 불가능하기 때문에 initializer delegation 동작이 비교적 심플하다. initializer delegation이 자기 자신 내에서만 이루어 지기 때문이다.
Class는 상속이 가능하기 때문에 Class는 슈퍼클래스의 property들까지 initialization 동안에 적절한 값으로 초기화 해줘야 하는 책임을 가지고 있다. 

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()
    
    init () {}
    // default initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.

    init (origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    // memberwise initializer랑 동작이 똑같지만 밑에 custom initializer와 같이써야 하기 때문에 custom initializer인것 처럼 보이기 위한 fake이다.
    
    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 basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

let originRect = Rect(origin: Point(x: 2.0, y: 2.0)), size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

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)


Class Inheritance and Initialization 
: 슈퍼클래스의 property들을 포함해서 클래스의 모든 property들은 initialization time에 적절한 값으로 초기화 되어야 한다. 
스위프트는 클래스 타입에 대한 두 종류의 initializer를 제공한다. designated initializer와 convenience initializer가 그것이다.

Designated Initializers and Convenience Initializers
: Designated Initializer는 현재 클래스의 모든 property들을 초기화 하고, 슈퍼 클래스의 property들을 초기화 하기 위해서 적절한 슈퍼클래스의 initializer를 호출해 준다.
대부분의 클래스는 하나의 Designated Initializer를 가지고, 왠만하면 2개를 넘지 않는다. 그리고 모든 클래스는 반드시 적어도 하나의 designated Initializer를 가지고 있어야 한다. 
: Convenience Initializer는 같은 클래스의 Designated Initializer를 호출해야 하는 책임을 가지고 있다. Convenience Initializer는 꼭 생성해야할 필요는 없다. 필요할때 생성해서 사용하면 된다. 

Syntax for Designated and Convenience Initializers
: Designated Initializer는 일반적인 initializer 선언방법과 동일하게 하면 된다.

init (parameters) {
    //statements
}

convenience initializer는 'convenience' 키워드가 앞에 붙는다.

convenience init (parameters) {
    //statements
}

Initializer Delegation for Class Types
: 스위프트는 initializer delegation에 대해 3가지 rule을 정의했다.

Rule 1
- designated initializer는 반드시 슈퍼클래스의 designated initializer를 호출해야 한다.
Rule 2
- convenience initializer를 반드시 같은 클래스의 다른 initializer를 호출해야 한다.
Rule 3
- convenience initializer는 결국 designated initializer를 호출하도록 구성되어야 한다.(즉,  convenience initializer끼리 메세지 체인이 일어나도 결국엔 마지막 convenience initializer는 designated initializer를 호출해야 한다.)


Two-Phase Initialization
: 스위프트는 Class initialization을 하는데 두 단계 과정을 거치도록 강제한다.
첫번째는, 모든 stored property들이 적절한 값으로 초기화 되어야 한다.
두번째는, 각 클래스들이 stored property들을 customize할 수 있는 기회를 갖는다. 
위 두단계는 순차적으로 이루어 져야하며, 두 번째 단계를 생략해도 된다. 

* 위 두 단계의 초기화 과정은 Objective-C의 초기화 과정과 비슷하지만, 가장 큰 차이는 첫번째 단계인 stored property를 초기화 하는 과정에 있다. Objective-C는 default로 모든 stored property들을 0 또는 nil로 초기화 시키지만, 스위프트는 개발자가 원하는 값으로 초기화 시킬 수 있다.

Safety check
: 스위프트의 compiler는 위 두 단계의 initialization 과정이 올바르게 이루어졌는지 체크하기 위해 4가지 Safety check를 한다.

Safety check1
- designated initializer가 슈퍼클래스의 designated initializer를 호출하기 전에 자신 클래스의 모든 stored property들이 적절한 값으로 초기화 되었는지 체크한다.
Safety check2
- designated initializer가 상속받은 property들의 값을 customize 하기전에 슈퍼클래스의 designated initializer를 호출해서 상속받은 property들을 초기화 하는지 체크한다.
만약 슈퍼클래스의 designated initializer가 불리기 전에 상속받은 property들을 customizing한다면, 슈퍼클래스의 designated initializer에서 해당값들이 overwritten될 수 있다.
Safety check3
- convenience initializer는 property(슈퍼 클래스의 property 포함)들에게 값을 지정하기 전에 다른 initializer를 미리 호출해야 한다. 
만약 그렇지 않으면,  초기화한 값이 다른 initializer에 의해 overwritten될 수 있다. 즉 convenience initializer는 property를 초기화 하는것이 아니라 property를 customizing하는 곳이다. 그러기 위해서는 미리 property들의 값을 초기화하는 작업이 선행되어야 한다.
Safety check4
- Phase1(모든 property들이 적절한 값으로 초기화 되는것)이 끝나기 전에 self를 참조할 수 없고, property를 참조하고 있는 instance method들을 호출할 수 없다.
그 이유는, 모든 property들이 적절한 값으로 초기화 되기 전까지 클래스의 instance는 준비된 상태가 아닌 불완전한 상태라고 판단하기 때문이다. 

Two-Phase Initialization In Detail
: 위 Safety check를 토대로 Two-Phase Initialization과정을 좀 더 상세히 들여다 보도록 하겠다.

Phase1
- designated 또는 convenience initializer가 호출된다.
- 클래스 인스턴스에 대한 메모리가 할당된다. 그렇지만 아직 초기화가 완료된 상태는 아니다.
- designated initializer를 통해 자신 클래스의 property에 대한 초기화를 진행한다.
- 슈퍼클래스의 designated initializer를 호출한다. 슈퍼클래스의 designated initializer 역시 자신의 property에 대한 초기화를 진행하고 다시 슈퍼클래스의 designated initializer를 호출해서 root 클래스까지 이 과정이 반복된다.
- root 클래스까지 initialization 과정이 완료되면 모든 property들의 초기화가 완료되었다는 것이고, 이는 클래스 인스턴스에 할당된 모든 메모리가 적절한 값으로 초기화 되었다는 것이다. 즉, Phase 1이 완료된다.

Phase2
- root 클래스에서 bottom 클래스까지 역으로 initialization 과정이 delegate down되면서 각 designated initializer는 필요하다면 property들에 원하는 값을 대입 시키거나 self 또는 다른 instance method 호출을 통해서 instance customizing 한다.
- 마지막으로 convenience initializer에서 instance를 customizing할 수 있는 기회는 갖는다.

Initializer Inheritance and Overriding
: 스위프트는 Objective-C와 다르게 슈퍼클래스의 initializer를 default로 상속받지 않는다. 그 이유는 스위프트는 클래스가 property의 default 초기값을 제공하지 않기 때문이다. 그래서 instance를 생성할 때 자동상속된 슈퍼클래스의 initializer를 호출한다면 자신 클래스의 property들을 초기화가 되지 않을 위험이 있다.
만약 슈퍼클래스의 designated initializer를 override 하기를 원한다면, 앞에 @override 키워드를 붙여줘야 한다. default initializer를 override할때도 마찬가지다.
@override 키워드를 붙이면 스위프트 컴파일러는 슈퍼클래스의 initializer와 매칭되는지 체크를 한다. 
슈퍼클래스의 convenience initializer와 매칭되는 initializer를 구현할때에는 앞에 @override 키워드를 붙이지 않는다.

Automatic Initializer Inheritance
: 자식클래스는 슈퍼클래스의 initializer를 자동으로 상속받지 않지만, 특별한 경우에 자동으로 상속받는다. 

Rule 1
- 자식클래스가 새로운 designated initializer를 제공하지 않았다면, 슈퍼클래스의 모든 designated initializer를 상속받는다. 

Rule 2
- 만약 자식클래스가 슈퍼클래스의 모든 designated initializer의 implementation을 제공 한다면(자동으로 상속받았거나, 실제로 override해서 custom implementation을 제공한 경우 둘다 해당된다. 또한 슈퍼클래스의 designated initializer를 자식클래스에서 convenience initializer로 구현한 경우도 해당된다. 단, 슈퍼클래스의 여러개의 designated initializer 중 자식클래스에서 일부만 override한 경우는 해당되지 않는다.) 슈퍼클래스의 모든 convenience initializer도 상속받는다.

위 두가지 rule은 자식클래스가 custom convenience initializer를 가지고 있어도 해당된다.

Designated and Convenience Initializers in Action
: 실제 Designated, Convenience Initializer들이 어떻게 동작하는지 알아보도록 하자.

class Food {
    var name: String  
    
    init (name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}


class RecipeIngredient: Food {
    var quantity: Int
    
    init (name: String, quantity: Int) {
        self.quantity = quantity
        
        super.init(name: name)
    }
    
    override convenience init (name: String) {
        self.init(name: name, quantity: 1)
    }
}



RecipeIngredient 클래스는 자신의 stored property를 초기화 시키는 designated initializer 가지고 있고, 부모클래스의 designated initializer를 override 해서 구현하고 있기 때문에 부모클래스의 convenience initializer를 자동으로 상속 받았다. 

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? "v" : "x"
        
        return output
    }
}



ShoppingListItem은 유일한 stored property인 purchased에 선언과 동시에 초기값을 제공하고 있기 때문에 이것을 초기화 해주는 designated initializer가 필요하지 않다. 
그리고 부모클래스의 어떤 designated initializer도 override하고 있지 않고, 자신의 custom designated initializer도 구현하지 않고 있기 때문에 부모클래스의 모든 designated initializer를 자동으로 상속 받는다. 또한 부모클래스의 모든 designated initializer를 구현하고 있기 때문에(자동 상속으로..) 부모 클래스의 모든 convenience initializer도 자동 상속받는다. 

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]

breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true

for item in breakfastList {
    print(item.description)
}

// 1 x Orange juice v
// 1 x Bacon x
// 6 x Eggs x  



'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Deinitialization  (0) 2017.04.01
[Swift 4.0] Initialization(2)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
Posted by 홍성곤
,

Introduction
: Class는 다른 Class의 method, property 그리고 다른 특성들을 상속받을 수 있다. 이때, 상속을 하는 Class는 Subclass라 부르고 상속 당하는 클래스를 Superclass라고 부른다.

Subclass는 Superclass의 method, property, subscript에 접근할 수 있으며 overriding 해서 동작을 customizing 할 수 있다. 
또한 Subclass에서 Superclass의 property observer를 만들 수 있다. 해당 property가 stored로 선언되었는지 computed로 선언되었는지 상관없이 observer를 만들 수 있다.


Defining a Base Class
: 어떤 Class도 상속받지 않은 Class를 Baseclass 라고 한다.

class Vehicle {

    var currentSpeed = 0.0

    var description: String {

        return "traveling at \(currentSpeed) miles per hour"

    }

    func makeNoise() {

        // do nothing - an arbitrary vehicle doesn't necessarily make a noise

    }

}


let someVehicle = Vehicle()


print("Vehicle: \(someVehicle.description)")

// Vehicle: traveling at 0.0 miles per hour


Subclassing
: Subclassing은 기존 Class를 상속해서 새로운 Characteristics를 더하는 것이다.

class SomeSubclass: SomeSuperclass {

    // subclass definition goes here

}

":" 뒤에 Superclass를 명시한다.

class Bicycle: Vehicle {

    var hasBasket = false

}


let bicycle = Bicycle()


bicycle.hasBasket = true

bicycle.currentSpeed = 15.0


print("Bicycle: \(bicycle.description)")

// Bicycle: traveling at 15.0 miles per hour


class Tandem: Bicycle {

    var currentNumberOfPassengers = 0

}


let tandem = Tandem()

tandem.hasBasket = true

tandem.currentNumberOfPassengers = 2

tandem.currentSpeed = 22.0

print("Tandem: \(tandem.description)")

// Tandem: traveling at 22.0 miles per hour


Overriding
: overriding은 inherit과 다르게 상속받은 instance method, type method, instance property, type property, subscript를 재정의(덮어쓰기) 하는 것이다. 
앞에 "override" 키워드를 붙여 표시한다.

Accessing Superclass Methods, Properties, and Subscripts
: Superclass의 method, property, subscript를 overriding된 각 구현부에서 접근할 수 있다.

- overriding된 Subclass의 method 구현부에서 "super" 키워드를 통해 Superclass의 override한 method를 접근할 수 있다.
- overriding된 Subclass의 property getter, setter 구현부에서 "super" 키워드를 통해 Superclass의 override한 property를 접근할 수 있다.
- overriding된 Subclass의 subscript 구현부에서 "super[someIndex]" 와 같은 문법으로 Superclass의 subscript에 접근할 수 있다.

Overriding Methods

class Train: Vehicle {

    override func makeNoise() {

        print("Choo Choo")

    }

}

let train = Train()

train.makeNoise()

// Prints "Choo Choo"

위와같이 "override" 키워드를 붙여서 해당 method를 override 했으며, 해당 instance는 override된 method를 호출한다.

Overriding Properties
: property를 overriding 하는 방법은 subclass에서 해당 property의 getter, setter를 재정의 하거나 property observer를 추가해서 custom한 동작을 구현하는 것이다.

Overriding Property Getters and Setters
property가 stored 인지 computed 인지 상관없이 Getter, Setter를 통해 overriding 할 수 있다.
Subclass 에서는 Superclass의 properties가 stored 인지, computed 인지 알지 못한다. 단지 property 이름과 type 정보만 알 뿐이다. 
override 할 때 해당 property의 이름과 type은 변경할 수 없다.
하지만  getter, setter를 제공해서 read-only property 에서 read-write property로 바꿀 수 있다.
그러나 read-write property를 read-only property로 바꿀 수 없다.

*property를 override 하려고 setter를 정의한 경우에는 getter도 함께 정의하여야 한다. getter의 동작은 override 하고 싶지 않은 경우에는 구현부에 super.someProperty를 단순히 return 하면 된다.

class Car: Vehicle {

    var gear = 1

    override var description: String {

        return super.description + " in gear \(gear)"

    }

}

let car = Car()

car.currentSpeed = 25.0

car.gear = 3

print("Car: \(car.description)")

// Car: traveling at 25.0 miles per hour in gear 3

Overriding Property Observers
property가 stored 인지 computed 인지 상관없이 Observer를 통해 overriding 할 수 있다.
다만, read-only property인 경우 overriding 할 수 없다. willset, didset method를 구현할 수 없기 때문이다.  또한 하나의 property에 setter와 property observer를 동시에 구현할 수 없다. property observer의 기능을 setter 안에서 모두 구현이 가능하기 때문이다.

class AutomaticCar: Car {

    override var currentSpeed: Double {

        didSet {

            gear = Int(currentSpeed / 10.0) + 1

        }

    }

}


let automatic = AutomaticCar()

automatic.currentSpeed = 35.0

print("AutomaticCar: \(automatic.description)")

// AutomaticCar: traveling at 35.0 miles per hour in gear 4


Preventing Overrides
: "final" 키워드를 method, property, subscript 앞에 붙여 overriding 되는것을 막을 수 있다. 
"final" 키워드는 extension 안에서의 method, property, subscript 에도 붙일 수 있다.
그리고 Class 앞에 "final" 키워드를 붙여서 해당 Class는 subclassing을 불가능하게 만들 수 있다. 


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Initialization(2)  (0) 2017.04.01
[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Subscripts

IOS/Swift 2017. 4. 1. 23:17

Introduction
: subscript는 collection, list, sequence의 element에 접근하려고 할때 조금 더 편리한 방법을 제공하는 수단이다.
예를 들어 Array의 element를 접근할 때는 someArray[index], Dictionary의 element 접근할 때는 someDictionary[key]와 같이 subscript를 이용해서 편리하게 접근할 수 있다.

subscript는 Class, structure, enumeration에서 정의 가능하다.


Subscript Syntax
: "subscript" 키워드와 함께 정의한다. 그리고 하나이상의 input parameter와 return type을 정의해야 한다. 

subscript(index: Int) -> Int {

    get {

        // return an appropriate subscript value here

    }

    set(newValue) {

        // perform a suitable setting action here

    }

}

return value의 type은 newValue의 type과 동일하다. Computed property 처럼 newValue parameter를 생략할 수 있는데, 이 경우 "newValue"라는 이름이 자동할당 된다.

read-only 형식의 subscript도 정의할 수 있는데, 예시는 아래와 같다.

subscript(index: Int) -> Int {

    // return an appropriate subscript value here
}

set 메서드가 존재하지 않는다.
read-only subscript를 정의한 TimesTable 이라는 구조체를 만들어 보도록 하겠다.

struct TimesTable {

    let multiplier: Int

    subscript(index: Int) -> Int {

        return multiplier * index

    }

}

let threeTimesTable = TimesTable(multiplier: 3)

print("six times three is \(threeTimesTable[6])")

// Prints "six times three is 18"


Subscript Options
: Subscript는 input parameter 갯수의 제한은 없습니다. 이러한 parameter 들은 어떠한 type도 될 수 있고, 가변 파라미터 정의도 가능하다. 
그러나, in-out parameter, default parameter value는 사용이 불가능 하다.

Class, Structure는 multiple subscript를 구현할 수 있다. 이를 subscript overloading 이라 한다.

subscript는 보통 하나의 parameter를 받는게 일반적이지만, 필요에 따라 여러개의 parameter를 받도록 구현할 수 있다. 아래의 Matrix struct를 예로 들어보겠다.

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

        }

    }

}


var matrix = Matrix(rows: 2, columns: 2)


matrix[0, 1] = 1.5

matrix[1, 0] = 3.2



let someValue = matrix[2, 2]

// this triggers an assert, because [2, 2] is outside of the matrix bounds

위 matrix structure는 2차열 배열과 아주 유사한 구조를 가지고 있고, 각각의 element에 접근하기 위해서 2개의 input parameter를 받는 subscript를 구현하고 있다.






'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Initialization(1)  (0) 2017.04.01
[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Methods  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
Posted by 홍성곤
,

[Swift 4.0] Methods

IOS/Swift 2017. 4. 1. 23:17

Introduction
: Class, Structure, Enumeration 모두 instance method, type method(class method)를 정의할 수 있다.


Instance Methods
: instance의 functionality를 담당한다. 

class Counter {

    var count = 0

    func increment() {

        count += 1

    }

    func increment(by amount: Int) {

        count += amount

    }

    func reset() {

        count = 0

    }

}


dot(.)을 통하여 instance method를 호출할 수 있다.

let counter = Counter()

// the initial counter value is 0

counter.increment()

// the counter's value is now 1

counter.increment(by: 5)

// the counter's value is now 6

counter.reset()

// the counter's value is now 0


The self Property
: 모든 instance는 "self"라는 property를 가지고 있다. "self"는 instance 자신이다. instance method 안에서 사용가능하며, 다른 instance method 호출이나 property에 접근할 때 사용한다. 

func increment() {

    self.count += 1

}


property에 접근할 때 property 앞에 무조건 "self"를 붙여줄 필요는 없지만, parameter 이름 또는 지역 변수 이름이 property 이름과 같은 경우 "self"를 property 앞에 붙여줘야 한다.

struct Point {

    var x = 0.0, y = 0.0

    func isToTheRightOf(x: Double) -> Bool {

        return self.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")

}

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


Modifying Value Types from Within Instance Methods
: Structure와 Enumeration은 Value type 이다. 그래서 기본적으로는 자신의 instance method 안에서 property 값을 변경할 수 없다. 변경이 가능하도록 하기 위해서는 property를 변경하는 instance method 앞에 "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))")

// Prints "The point is now at (3.0, 4.0)"


위 mutating method moveBy는 해당 instance를 let으로 선언하면 호출할 수 없다.

let fixedPoint = Point(x: 3.0, y: 3.0)

fixedPoint.moveBy(x: 2.0, y: 3.0)

// this will report an error


Assigning to self Within a Mutating Method
: mutating method는 instance 자신도 바꿀 수 있다.


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)

    }

}


structure 뿐만 아니라 Enumeration의 mutating method도 마찬가지다.

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 is now equal to .high

ovenLight.next()

// ovenLight is now equal to .off


Type Methods 
: Type Method는 다른 언어의 Class Method 라고 보면 된다.
선언 방법은 메서드 앞에 "static" 키워드를 붙이거나 "class" 키워드를 붙이면 된다. static method는 자식 클래스에서 override 하지 못하고, class method는 자식 클래스에서 override가 가능하다.

이 둘을 통틀어서 Type Method 라고 하는데, Type Method 안에서 참조하는 self는 instance가 아니라 Type 그 자체를 의미한다. 

class SomeClass {

    class func someTypeMethod() {

        // type method implementation goes here

    }

}

SomeClass.someTypeMethod()


* instance Method나 type Method안에서 다른 method를 호출할 때 또는 다른 property를 접근할 때 "self" 키워드를 앞에 안붙여도 된다. 
* Type Method와 Instance Method 이름을 같게해서 선언할 수 있다.
* Instance Method 안에서 Type Method를 호출할 때 또는 Type property를 접근할 때  타입이름을 앞에 붙어야 한다. 


'IOS > Swift' 카테고리의 다른 글

[Swift 4.0] Inheritance  (0) 2017.04.01
[Swift 4.0] Subscripts  (0) 2017.04.01
[Swift 4.0] Properties  (0) 2017.04.01
[Swift 4.0] Classes and Structures  (0) 2017.04.01
[Swift 4.0] Enumerations  (0) 2017.04.01
Posted by 홍성곤
,