매일매일 개발하는 소스코드를 올리고 있습니다. 원래는 프로젝트가 끝나고 올리려고 했으나, 그러다 보니 까먹는 경우가 있어서 이렇게 매일매일 올리는 소스코드 입니다. 제가 쓴 소스코드의 문제 혹은 개선점이 있으면 언제든지 댓글 달아 주세요
아이폰 어플은 Document 폴더에서만 작업을 할 수 있다는 장점아닌 단점을 가지고 있는데 그래서 대부분의 작업이 Document 에서 이루어지기 마련이러더라구요. 그래서 Document 폴더 path를 가져오는 함수를 따로 만들어 두고 해당 폴더 혹은 파일을 Document Path와 연결해 주는 코드입니다. Release는 알아서~
- 실행코드와 이미지, 사운드, 문자열, NIB 파일과 같은 리소스의 모음 - 각 리소스의 다른 버전을 동시에 저장, 사용자의 언어나 지역 설정에 따라
다른 버전의 리소스를 사용 가능하게 함. - 자바의 JAR(Java ARchive), C# 프로그래밍의 리소스
패턴이 만들어진 동기
- 필요한 리소스가 내부 저장 공간에 여러버전과 여러 파일로 구성되어있는 경우에도,
한 곳에 모을 수 있도록 한다. - 실행코드와 리소스를 동적으로 로드 할 수 있도록 유연한 플러그인 기법을 구현한다.
패턴으로 문제 해결
- 번들 = 디렉토리 : 코드, 리소스등을 파일시스템 디렉토리를 통해서 만든것 - 계층구조로 구성되어 있다.
구성 * Contents - 모든 번들 리소스 담고 있다. * Info.plist : 번들에 대한 정보, Unique 하게 식별 가능한 번들 식별자 문자열을 저장하고 있음. * Mac OS : 애플리케이션 실행 파일 * Resources : nib 파일, 그래픽, 문자열, 리소스 파일, 지역별버전(.lproj) 폴더
- 사용자에게 하나의 파일처럼 보이게 하는 효과 있음. (패키지) - 그러나 언제든지 파인더를 통해서 내부 파일을 볼수 있다.
* 번들의 장점 - 번들은 패키지이자, 표준 파일 뷰어로 볼수 있기 때문에 편집이 가능하다. - 이동 복사, 삭제가 가능 - 순진한 사용자는 내부 파일을 볼 일이 없어서 변경의 가능성이 줄어든다. - 다국어 , 지역화 지원. 원치않는 지역화 리소스 쉽게 제거 - 여러버전 저장 및 실행코드의 여러버전을 포함 할 수 있음. - 특정기능에 의존적이 아니라, 서버 또는 다양한 파일시스템에 저장할 수 있다.
*배포
- CD : 번들을 CD로 복사 - 네트워크 : 문제 야기의 가능성 있음, 번들의 일부만 다운로드 받을 가능성 - DMG : 디스크이미지로 생성, 이동식 디스크로 마운트 됨
* 항상 번들을 사용해야하는 것은 아니다. (독립형 커맨드라인 프로그램 개발 가능)
- 번들 제작시, 자동으로 XCode가 이미지, 사운드 파일등과 같은 표준 리소스를 자신이 속하는 곳에 위치시킨다.
코코아 사용 예제
- 번들을 NSBundle 클래스로 캡슐화 - 하나의 어플은 적어도 하나의 번들을 가지며, 메인 번들은 [NSBundle mainBundle]을 통해서 접근 가능함. - NSApplication - mainBundle - load nib file.
- 동적으로 실행코드와 리소스를 불러올 수 있다. (375p)
- 현재 언어와 지역 설정에 적합한 버전의 경로 반환 - 지정된 지역화에 해당하는 리소스 버전에 대한 경로 반환 하는 등의 리소스 접근 관련 메소드를 제공함.
- NSBundle은 Foundation 프레임워크에 소속되어 있지만, ApplicationKit 프레임워크에서 카테고리를 통해서 NSBundle을 여러 방면으로 확장한다.
* 메인 번들 뿐만아니라 실행코드를 담고 있는 번들에 접근하는 것도 가능함. (I don't know)
동적으로 실행코드 로드하기
- 번들은 어플리케이션 구동시 자동 로드된다. - 번들을 동적으로 불러들이려면
+(NSBundle*)bundleWithPath:(NSString*)fullPath : NSBundle 인스턴스 생성
377p 예제 : myPlugin.bundle 을 로드
- 번들 인스턴스 만들고 캡슐화 해도 자동 로드되지 않는다. 해당 코드를 사용할필요가 있을시까지 기다린다. - 강제로 실행코드를 어플리케이션에 링크하게 하려면, NSBundle의 load 메소드 또는 principalClass 메소드 사용 (I don't know)
패턴 사용 결과
- 실행코드와 관련 리소스를 한곳에 두어, 어플리케이션 구동시, 리소스 경로를 하드코딩 하지 않아도 된다. - 리소스와 코드를 저장시, 디렉토리 계층 사용의 장단점 : 사용자가 해당 어플리케이션의 번들의 내용물을 보고 편집 할수 있다는 점 : 그러나, 다른 사용자가 어플리케이션을 언제든지 확인, 수정,삭제가 가능하다는 것.
프로토 타입이란? - 기능 구현을 위해 복사해서 사용하는 객체 - 기존 객체를 복사해서 사용하는 것은, 새 인스턴스를 생성하는 것 보다 더 유연하다. - 객체간의 강한 의존성 관계를 피하도록 해준다. '
패턴이 만들어진 동기 - 새로 인스턴스를 생성하는 객체와 해당 객체의 타입간의 의존성을 최소화 - 생성되는 객체의 종류에 대한 정보를 컴파일시 지정하지 않고, 런타임시 조절할 수 있도록 한다.
패턴으로 문제 해결 - 핵심기능 : 복사 될수 있다는 것 - NSCopying, NSCoding 프로토콜 제공
* NSCopying protocol - copyWithZone 메소드 - 메소드를 수신하는 객체와 동일한 상태의 객체를 반환해야한다.
1. Shallow Copy - 복사되는 객체와 동일한 값을 저장한다. - 원본을 가리키는 또 다른 포인터를 만드는 것, 포인터 복사
2. Deep Copy - 원본 객체에 저장된 값의 실제 복사본을 저장
* 프로토 타입 패턴은 딥 카피를 제공하는 객체와 가장 잘 동작,
완벽히 독립적인 사본이 필요한 경우가 있기 때문임. ex) IB Library의 경우, 복사된 객체는 인터페이스 빌더가 종료된 후에도 동작해야함.
그러나, 대부부의 코코아클래스는 NSCopying 프로토콜을 구현해서 Shallow copy를 반환함.
* Deep Copy가 쉽지 않은 이유
: 참조하고있는 객체중 Shallow copy를 반환하도록 구현되어 있다면, 섞이게 된다.
* NSArchiver, NSUnarchiver class 는 쉬운 Deep Copy 방식을 제공 - 복사되는 객체와 그 객체 내에서 참조하는 모든 객체들이 NSCoding Protocol을 따른다면, Deep Copy 반환 - 347p 예제 : 아카이빙 한 후, 언아카이빙하는 것은 딥 카피를 만들기 위한 억지 기법
- IB 라이브러리에서 객체를 복사할때 사용하는 방식. - " 작업중인 .NIB 파일에 객체를 드래그 해서 옮기면, 객체는 기존의 라이브러리 인스턴스르에서 먼저 아카이브 되고나서 언 아카이브하여 추가 편집할 사본을 생성한다. 그리고 .NIB 파일이 저장될 때, 객체들은 다시 아카이브된다. .NIB파일이 로드되면 파일에서 객체가 언아카이브 되어서 IB에 복원된다."
* NSCell - NSCopying프로토콜의 다른 기법인 NSCopyObject() 함수 사용함.
NSCopyObject : 원본이 차지하는 메모리를 그대로 복사해서 Shallow Copy 생성함. 이후, NSCell의 -copyWithZone이 원본에 저장된 속성이 참조한느 객체에 copy 메시지를 보내 복사함. NSCell은 Shallow copy 와 Deep Copy를 섞어 씀.
코코아 사용 예제
- IB 라이브러리 객체는 모두 프로토타입 : 객체를 복사함으로써 IB를 재 컴파일 하지 않아도 확장이 가능함. - NSMatrix 클래스에서는 프로토타입 NSCell 인스턴스를 사용함. - 매트릭스에서 행, 열 추가가 있을시에는 필요한 만큼 프로토타입 셀을 복사한다. - NSMatrix 클래스는 자신이 사용하는 셀에 의존하지 않는다.
349p NSCell의 서브 클래스 MYLabeledBarCell에서 정의한 barValue 역시 NSCopyObject()에 의해서 상속된 변수들과 함께 자동으로 복사된다.
* NSCopyObject() 함수는 포인터를 저장하지 않는 클래스에만 사용해야 한다. * NSCell의 NSCopyObejct() 사용은 NSCell의 객체를 가리키는 인스턴스 변수를 추가하는 서브클래스 생성하기를 어렵게 만든다.
패턴 사용 결과
- 객체를 복사하는 작업이 새로운 인스턴스를 생성하는 것 만큼 시간과 비용소요(절약되는 것이 아님)
- NSMatrix 의 경우, 필요한 셀의 수가 줄어들더라도 프로토타입의 셀 사본을 거의 릴리즈 하지 않는데, 다시 필요할 경우 유지하고 있는 사본을 사용한다. 그러나 메모리가 필요하지 않는 사본을 저장하는데 소비된다는 측며에서 보면 단점이다.
- 프로토타입 패턴 주의사항 : 프로토타입 객체를 지원하는데 필요한 행동을 문서화 하는 것이 필요함.
디렉토리를 생성하는 코드입니다. 일단 도큐먼트 경로를 가져와서, 해당 생성하려는 폴더 이름을 붙여주는 방식입니다. Objective-C 에서 파일 및 디렉토리 처리를 담당하는 부분은 NSFileManager 에서 담당하기 때문에 관련 된 사항이 있으면 그 부분을 보면 될것입니다. Release는 알아서 하시길.
This code is that create directory in document path. First, get document path in app and add folder name. In Objective-C, the part of file & directory processing is responsible to NSFileManager class. so that you find NSFileManager Document.
- 코코아 그래픽 애플리케이션의 핵심적이고 중요한 요소
- 책임사슬 패턴(Chain of Responsibility)
패턴이 만들어진 동기
- 사용자 이벤트의 정확한 전달
- 현재 활성화 되어 있는 인터페이스 객체는 무엇이고, 어떻게 액션 메시지를 전달할 것인가.
패턴으로 문제 해결
- 책임사슬패턴은 메시지의 전송자와 수신자 객체 관계를 최대한 분리시키는 것이 목적
- 수신객체들이 연결 리스트로 구성되어 있고, 각 객체들이 메시지를 무시 혹은 수신하는 순차적인 시스템
용어
NSResponder - 사용자 입력을 다룰수 있는 개체들은 모두 NSResponder의 서브클래스
- 키보드, 마우스, 터치등의 상자 입력에 대응하는 역할
- NSWindow, NSView, NSViewController, NSDrawer 등이 모두 NSResponder의 서브 클래스
* 사용자의 초점이 어디에 맞춰져 있는지 자동적으로 코코아 프레임워크에서 기억.
KeyWindow - 입력을 받는 윈도우를 키 윈도우
Main Window - 현재 초점이 맞춰져 있는 문서 창을 메인 윈도우
* 키윈도우와 메인 윈도우가 하나의 인터페이스 인 경우도 있지만, 서로 다른 인터페이스인 경우 있음.
NSApplication 의 역할
- 애플리케이션 객체는 키 윈도우와 메인 윈도우를 항상 기억하도록 되어 있음.
- keyWindow, mainWindow 를 통해서 해당 개개체에 대한 레퍼런스를 얻을수 있음.
FirstResponder - 현재 초점이 맞춰져 있는 뷰를 지칭함
- 리스폰더 체인의 첫번째 객체
- -firstResponder : 첫번째 리스폰더에 대한 레퍼런스 리스폰더 체인 - 다수의 수신 객체가 엮여져 있는 형태로 액션 메시지의 대해서 각 노드가 ignore 또는 accept 를 하는 구조
- nextResponder : 주어진 수신객체의 다음 수신객체를 반환
- setNextResponder : 다음 객체를 재설정하고 싶을때 사용
- 일반적으로 주어진 뷰의 다음 수신객체는 슈퍼뷰(super view)
- 윈도우 내 콘텐츠 뷰의 다음 수신객체는 윈도우.
- 윈도우의 다음 수신 객체는 nil.
- 리스폰더 체인의 마지막 노드가 윈도우 창이다.
예외) NSWindowContorller에 의해서 제어되고 있는 윈도우 창은 컨트롤러가 최종 수신 객체임
예제) 304p
- 모든 리스폰더 체인은 nil로 끊어지기 때문에, 무한 루프의 위험성이 없다.
- 윈도우가 여러개인 환경에서는 모든 각각의 윈도우가 리스폰더 체인을 가짐.
- 윈도우 내 사용자의 선택이 바뀔때 마다 리스폰더체인이 업데이트 됨.
- acceptResponder : 새로운 수신객체가 될 객체에게 전달
- resignFirstResponder : 현재 리스폰더에게 FirstResponder를 반환하라는 메시지를 전달
- becomeFirstResponder : 새로운 첫번째 리스폰더가 될 객체에게 전달 - 키보드가 마우스 입력의 경우,
NSApplcation 객체가 입력을 NSEvent 객체로 변환한 다음. 리스폰더 객체를 찾는다.
- 리스폰더 체인은 계층구조를 이용해서 FirstResponder를 찾아냄.
확장된 리스폰더 체인
- 키 윈도우와 메인 윈도우의 리스폰더 체인을 아우르고, 또한 공용의 NSApplication 인스턴스와
NSDocumentController 인스턴스를 포함한 확장 리스폰더 체인.
- nil을 target을 갖는 메시지를 담당.
- 메뉴 항목의 경우 다양한 종류의 메시지 구현이 쉽지 않다.
- 키 윈도우와 메인 윈도우의 리스폰더 체인 만으로는 이벤트 핸들링이 불충분한 경우.
- 확장된 리스폰더 체인의 순서
1. 키 윈도우의 리스폰더 체인의 첫번째 리스폰더를 처음 시도.
2. 뷰 계층 구조의 리스폰더 체인을 따라 메시지 전달.
3. 윈도우 객체를 시도
4. 윈도우의 델리게이트를 시도(주로 NSDocument의 인스턴스)
5. 만약, NSWindowController가 있다면 시도.
6. 1-5의 순서를 메인 윈도우에 적용
7. NSApplication 객체와 그의 델리게이트를 시도
8. NSDocumentController 가 있다면 시도.
예제) 306P - 307P
- 키 윈도우 == 메인윈도우의 경우, 확장된 리스폰더 체임의 검색 길이는 짧아 짐.
- 인터페이스 빌더 이용시, 컨트롤을 첫번째 리스폰더에 연결하면, 실제로는 해당 액션에 대한 target를 nil로 지정.
- 어떤 컨트롤의 액션메시지를 확장된 리스폰더 체인에 전달하고 싶다면 action의 target을 nil로 지정하면 됨.
- 뷰 컨트롤러가 뷰 다음에 위치하게 되면, 그 뷰가 활성화될 경우에만 작동하게 됨.
(주어진 윈도우가 하나 이상의 뷰/뷰 컨트롤러 짝을 가지고 있는 경우를 위해서임)
- 리스폰더 체임의 어느위치에 객체를 삽입하느냐에 따라 컨트롤러가 언제 어느 식으로 사용자 입력에 대응하도록 할 것인지를 조정 할 수 있음.
리스폰더 체인 활용하기
- 이미 존재하고 있는 리스폰더 체인을 사용하는 것이 가장 편함.
- 자동검열 기능, 텍스트의 선택여부에 따른 액션 지정 등은 컨텍스트에 민감한 행동 기능을 리스폰더 체인을 통해서 단순화 시킬수가 있다.
코코아 사용예제
- 코코아에서 리스폰더 체인은 핵심임.
- 사용자 입력은 NSApplication 객체가 책임을 지고 적합한 리스폰더 체인에게 전달됨.
- 키 윈도우와 메인 윈도우의 리스폰더 체인을 아우르고, 또한 공용의 NSApplication 인스턴스와
NSDocumentController 인스턴스를 포함한 확장 리스폰더 체인.
- nil을 target을 갖는 메시지를 담당.
- 애플리케이션의 상태에 따라 목표물이 동적으로 좌우되는 액션을 구현해야 한다면, target을 nil로 지정.
- 복사하기/붙여넣기, 눈금자 조절기능, undo/redo 등의 기능이 확장 리스폰더 체인의 대상
- 컨텍스트 인식 메뉴들도 리스폰더 체인을 많이 활용
패턴 사용 결과
- 다양한 사용자 입력을 처리 할수 있는 유연성을 제공함.
- 다양한 액션 기능을 최소한의 코드로 구현 가능함.
- 리스폰더 체인을 활용해서 애플리케이션에 특화된 컨텍스트 인식기능의 구현 가능.
uiimagepickercontroller 를 사용하시면 (modal 로 띄우겠죠?) 대부분 memory warning 이 발생합니다.
memory warning 이 생기면 여러가지 떠 있는 viewcontroller 들 중에 보이지 않는 controller 들의 viewDidUnload 를 호출합니다.
그리고 카메라를 다 사용하고 나면 (dismiss 시키게되면) .. 원래 controller 의 viewDidLoad 를 호출합니다.
그래서 viewDidLoad 가 호출되는 것처럼 보일 수 있습니다.
UIImagePickerController 를 띄우는 Controller 에서 viewDidUnload 에서 적절한 처리를 해 주시면 됩니다.
이렇다고 하네요, 결과적으로는 카메라 호출로 인해서 생기는 "메모리 경고"로 인한 ViewDidLoad의 문제 였습니다. 확인해 본 결과 실제로 카메라 혹은 포토라이브러리 호출로 인한 메모리 경고시에는 해당 뷰가 ViewUnload가 호출되고 dismissModal 후 ViewLoad가 다시 호출되는 것을 확인하였습니다. 때문에 한번만 호출된다는 개념을 가지고 접근해서 코드를 ViewDidLoad 에 작성해서는 안될것 같습니다. 그리고 이런 호출이 있는 경우를 잘 체크해서 코드를 작성해야 할것 같습니다.
HTML 파싱에 관한 부분은 한우찾기 1.0 및 2.0 에 걸쳐서 가장 핵심적인 부분이라고 할수 있습니다. 총 2가지 방법을 사용해서 웹 사이트에 있는 HTML 데이터를 파싱해서 가져오고 있습니다.
1. Java Script + UIWebView 를 이용하는 방식
2. xpath 를 이용하는 방식
이 두가지는 장단점이 있기 때문에 적절하게 사용해야 할것 같습니다. 일단 이번 포스팅에서는 첫번째, 즉 Java Script 와 UIWebView 를 이용해서 개발하는 부분을 보도록 하겠습니다.
자바스크립트로 파싱을 한다?
HTML 로 파싱을 하기 위해서 어떻게 해야할까? 라는 고민을 처음 하게 되었는데, 첫번째로 방법이 바로 UIWebView 에 해당 HTML 을 불러와서 UIWebView의 stringByEvaluatingJavaScriptFromString 함수를 이용해서 처리하는 것 이었습니다.
파라미터로 자바스크립트를 받고 리턴 값으로 해당 결과 혹은 실패일 경우에는 nil 값을 전달해 주는 것입니다. 즉, 이 함수를 통해서 html 에 있는 특정 값을 가져오기 위해서는 자바 스크립트가 필요합니다. 자바 스크립트를 조금 공부하셔야 하는게 있긴 한데 일단 제가 썼던 소스를 기반으로 보여드리습니다.
실제 HTML 페이지의 일 부분입니다. 제가 가져오고 싶은 데이터가 있는 부분은 2007-11-24 라고 가정했을때 저는 다음과 같은 자바스크립트를 구성했습니다.
var d = document.getElementById('lblBirthDate');
d = d.innerHTML;d;
간단히 말하자면 'id가 lblBirthDate 라고 있는 곳의 innerHTML 을 가져와라'. 그럼 이 자바스크립트는 전체 HTML 문서의 id 중에서 lblBirthData 부분을 찾고, 그 태그 안에 있는 2007-11-24를 반환하게 되는 것입니다. 실제 코드를 보겠습니다. (사실 저는 자바스크립트를 처음 써 보았습니다.)
NSString*js = @" var d = document.getElementById('lblAnimalNo');d= d.innerText; ";
보시는 것 처럼 매우 간단합니다. webView 란, UIWebView의 인스턴스이고 거기에서 위에서 언급한 함수의 파라미터에 자바스크립트를 넣어주고 그럼, webView가 해당 자바스크립트를 실행 시켜서 데이터를 가져오는 것이다. 즉, 웹 브라우저가 자바스크립트를 실행 시켜서 데이터를 가져온다고 볼 수 있다.
다 좋은데? 문제는 뭐?
일단 이 방식을 사용할때의 가장 큰 걸림돌이 여러가지가 있습니다.
첫번째, 과연 자바스크립트를 쓸 수가 있는가?
저 같은 경우에는 자바스크립트가 처음이라 조금 고생했지만, 아시는 분이라면 getElementById 라는 것을 통해서 전체 html 문서 내 지정한 id를 찾는것을 기반으로 한다는것을 알 수 있을것 입니다. 그런데 문제는 간혹 html 페이지 자체가 전혀 id 를 쓰지 않은채 제작된 html 페이지도 있다는 것입니다. 그럴 경우에는 이 방법을 사용 할 수 없겠지요.
두번째, 이게 최선 입니까?
한우 찾기 2.0 을 실행해 보면 처음 12자리를 입력하고 들어가는 부분에서는 로딩뷰가 뜨는것을 확인 할 수가 있습니다. 사실상 로딩뷰는 위의 UIWebView가 파싱 하려는 사이트를 로딩시작할때 시작하고 끝나면 끝나게 되어 있습니다. 그렇기 때문에 사실상 찾으려는 텍스트 데이터 임에도 불구하고 html 페이지 자체에 이미지가 있는 경우에는 UIWebView에서 해당 이미지를 로딩해야 하기 때문에 속도적인 부분에서 오래 걸릴수가 있습니다. 1분, 2분은 아니더라도 단 몇초라도 사용자 입장에서는 길게 느낄수가 있으니까요.
정리해볼까요?
자바스크립트를 사용할 경우에는 반드시, HTML 페이지 소스에서 id가 있는지 확인하세요.
한번쯤 프로토타입을 만들어서 시간을 측정해 보시고, 적용하세요.
다음 포스팅에서는 한우 찾기 2.0 에서 사용한 XPath를 이용한 방법에 대해서 알아 보도록 하겠습니다.