BLOG main image
분류 전체보기 (48)
iPhone (21)
Cocoa (17)
Mac (0)
기타 (0)
코코아 헤드퍼스트 (10)
http://site.ru - http://site...
2013 - Roangeperzere
http://site.ru - http://site...
2013 - Roangeperzere
http://site.ru - http://site...
2013 - Roangeperzere
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
http://www.cialisltd.com/#redl..
2013 - cialisclolusyexetty
수준의 작업이 가능하면서도 무..
2012 - Jimmy Choo
click the up coming document
click the up coming document
visit these guys
visit these guys
see this website
see this website
Malaysia Escort
Malaysia Escort
cheap viagra
cheap viagra
151,224 Visitors up to today!
Today 17 hit, Yesterday 42 hit
daisy rss
tistory 티스토리 가입하기!
2011.01.28 16:46
마지막 프로젝트는 바로 객체 마을의 지하철 노선도 프로그램을 만드는 것이다.
가장 난이도가 높긴하지만 자신감을 가지고 도전해보자.

먼저 모델 중 가장 작은 단위인 지하철역 클래스부터 만들자.

// Station.h

@interface Station : NSObject {
@private
NSString *_name;
}

- (id)initWithName:(NSString *)name;
- (NSString *)name;
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

@end

여기서 핵심은 isEqual:과 hash 메소드를 재정의한 것이다. 지하철역 이름이 같으면 동일한 역으로 가정할 것이다. 배열이나 딕셔너리에서 각 원소의 비교는 isEqual:을 통해 이뤄지지만, 원칙적으로 두 객체가 동일한 경우 반드시 해시값도 동일해야 하기 때문에 hash 메소드도 오버라이드한다. 또한 지하철역 이름은 대소문자를 구분하지 않기로 했다. 이를 위해 비교대상을 모두 소문자로 바꾼후에 비교를 한다.

// Station.m

- (BOOL)isEqual:(id)object {
if ([object isMemberOfClass:[Station class]]) {
Station *otherStation = (Station *)object;
NSString *otherStationName = [[otherStation name] lowercaseString];
if ([[_name lowercaseString] isEqualToString:otherStationName]) {
return YES;
}
}
return NO;
}

- (NSUInteger)hash {
return [[_name lowercaseString] hash];
}

이제 두 역 사이를 잇는 연결 클래스를 구현하자. 연결 클래스는 간단하다 라인 이름과 함께 역을 두개 인자로 넘기면 된다.

// Connection.h

@interface Connection : NSObject {
@private
Station *_station1;
Station *_station2;
NSString *_lineName;
}

- (id)initWithLineName:(NSString *)lineName 
 station1:(Station *)station1 
 station2:(Station *)station2;
- (Station *)station1;
- (Station *)station2;
- (NSString *)lineName;

@end

이제 이 역과 연결을 사용할 지하철 클래스를 구현해야 한다. 특히 지하철 클래스에 역이나 연결을 추가할때는 그 내부에서 사용하는 역 클래스와 연결 클래스가 외부로 노출되지 않게 문자열을 인자로 받는다.

// Subway.h

@interface Subway : NSObject {
@private
NSMutableArray *_stations;
NSMutableArray *_connections;
}

- (void)addStationWithName:(NSString *)stationName;
- (BOOL)hasStation:(NSString *)stationName;
- (void)addConnectionWithName:(NSString *)lineName 
stationName1:(NSString *)stationName1 
stationName2:(NSString *)stationName2;

@end

// Subway.m

- (void)addStationWithName:(NSString *)stationName {
if (![self hasStation:stationName]) {
Station *station = [[Station alloc] initWithName:stationName];
[_stations addObject:station];
[station release];
}
}

- (BOOL)hasStation:(NSString *)stationName {
Station *station = [[[Station alloc] initWithName:stationName] autorelease];
return [_stations containsObject:station];
}

- (void)addConnectionWithName:(NSString *)lineName 
stationName1:(NSString *)stationName1 
stationName2:(NSString *)stationName2 {
if ([self hasStation:stationName1] && [self hasStation:stationName2]) {
Station *station1 = [[Station alloc] initWithName:stationName1];
Station *station2 = [[Station alloc] initWithName:stationName2];
Connection *connection1 = [[Connection alloc] initWithLineName:lineName 
 station1:station1 
 station2:station2];
Connection *connection2 = [[Connection alloc] initWithLineName:lineName 
 station1:station2 
 station2:station1];
[_connections addObject:connection1];
[_connections addObject:connection2];
[connection1 release];
[connection2 release];
[station1 release];
[station2 release];
} else {
NSException *e = [NSException exceptionWithName:@"RuntimeException" 
reason:@"Invalid connection!" 
  userInfo:nil];
@throw e;
}
}

이제 실제로 데이터를 로드하는 SubwayLoader 클래스를 만들자. 텍스트 파일을 라인 단위로 읽어들이고 지하철 부분과 지하철 노선 부분을 분리하기 위한 작업이 필요하다. 여기서는 라인 단위로 계속 읽어들이면서 파싱하기 보다는 아예 모든 데이터를 읽어들이고 각 라인별 문자열을 원소로 갖는 하나의 배열을 만들어놓고 시작했다.

// SubwayLoader.m

- (Subway *)loadFromFileAtPath:(NSString *)path {
NSError *error = nil;
NSString *data = [NSString stringWithContentsOfFile:path 
  encoding:NSUTF8StringEncoding 
 error:&error];
NSMutableArray *lines = [[data componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] mutableCopy];

NSRange range;
range.location = 0;
range.length = [lines indexOfObject:@""];
NSArray *stations = [lines subarrayWithRange:range];
[self loadStations:stations];
[lines removeObjectsInRange:range];
while ([lines count]) {
[lines removeObjectAtIndex:0]; // @""
range.location = 0;
int indexOfEmptyString = [lines indexOfObject:@""];
if (indexOfEmptyString > -1) {
range.length = indexOfEmptyString;
} else {
range.length = [lines count];
}
NSMutableArray *lineAndStations = [[lines subarrayWithRange:range] mutableCopy];
if ([lineAndStations count]) {
NSString *lineName = [lineAndStations objectAtIndex:0];
[lineAndStations removeObjectAtIndex:0];
[self loadLineWithName:lineName stations:lineAndStations];
}
[lines removeObjectsInRange:range];
[lineAndStations release];
}
[lines release];
return _subway;
}

지하철을 추가하는 메소드와 라인을 추가하는 메소드는 간단하다.

// SubwayLoader.m

- (void)loadStations:(NSArray *)stations {
for (NSString *stationName in stations) {
[_subway addStationWithName:stationName];
}
}

- (void)loadLineWithName:(NSString *)lineName stations:(NSArray *)stations {
for (NSString *stationName in stations) {
int index = [stations indexOfObject:stationName];
if (index < [stations count] - 1) {
NSString *nextStationName = [stations objectAtIndex:index + 1];
[_subway addConnectionWithName:lineName 
 stationName1:stationName 
 stationName2:nextStationName];
}
}
}

이제 데이터를 로드하는 부분도 끝났으니 테스트를 할 차례다. 테스트에서는 실제로 역이나 연결이 존재하는지를 검사해봐야 하는데 Subway 클래스에 hasStation: 메소드는 이미 구현했으니 연결 유무를 확인하는 메소드를 구현하자.

// Subway.m

- (BOOL)hasConnectionWithName:(NSString *)lineName 
stationName1:(NSString *)stationName1 
stationName2:(NSString *)stationName2 {
Station *station1 = [[[Station alloc] initWithName:stationName1] autorelease];
Station *station2 = [[[Station alloc] initWithName:stationName2] autorelease];
for (Connection *connection in _connections) {
NSString *name = [[connection lineName] lowercaseString];
if ([[lineName lowercaseString] isEqualToString:name]) {
if ([[connection station1] isEqual:station1] &&
[[connection station2] isEqual:station2]) {
return YES;
}
}
}
return NO;
}

이제 안심하고 테스트 클래스를 작성하자.

// LoadTester.h

@interface LoadTester : NSObject {

}

+ (void)run;

@end

// LoadTester.m

+ (void)run {
@try {
SubwayLoader *loader = [[SubwayLoader alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"ObjectvilleSubway" 
ofType:@"txt"];
Subway *objectville = [loader loadFromFileAtPath:path];
NSLog(@"Testing stations...");
if ([objectville hasStation:@"DRY Drive"] &&
[objectville hasStation:@"Weather-O-Rama, inc."] &&
[objectville hasStation:@"Boards 'R' Us"]) {
NSLog(@"...station test passed successfully.");
} else {
NSLog(@"...station test FAILED.");
exit(-1);
}
NSLog(@"Testing connections...");
if ([objectville hasConnectionWithName:@"Meyer Line" 
 stationName1:@"DRY Drive" 
 stationName2:@"Head First Theater"] &&
[objectville hasConnectionWithName:@"Wirfs-Brock Line" 
 stationName1:@"Weather-O-Rama, inc." 
 stationName2:@"XHTML Expressway"] &&
[objectville hasConnectionWithName:@"Rumbaugh Line" 
 stationName1:@"Head First Theater" 
 stationName2:@"Infinite Circle"]) {
NSLog(@"...connections test passed successfully.");
} else {
NSLog(@"...connections test FAILED.");
exit(-1);
}
}
@catch (NSException * e) {
NSLog(@"%@: %@", [e name], [e reason]);
}
}

이제 메인 함수에서 테스트를 실행시키자. 한 가지 주의할 점은 프로젝트 내의 텍스트 파일을 반드시 빌드 파일과 같은 위치에 복사해 놓는 것이다.  텍스트 파일의 경로를 번들 경로로 간단히 알아내기 위해 이렇게 했다.

// main.m

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[LoadTester run];
    
[pool drain];
return 0;
}

Testing stations...
...station test passed successfully.
Testing connections...
...connections test passed successfully.

좋다. 데이터는 정상적으로 다 갖춰진 것같다. 이제 길찾기를 구현할 차례다. 그리고 길찾기가 완료되면 화면에 출력할 수 있어야 한다. 얼마 안남았다. 조금만 힘내자.

큰 그림을 그려볼때 길찾기에 대한 인터페이스는 간단하다. 출발역 이름과 도착역 이름을 인자로 받아서 경로를 반환하면 된다. 이때 경로는 연결의 집합, 즉 역과 역을 연결한 선이 쭉 나열된 것으로 볼 수 있다.

// Subway.h

- (NSArray *)directionsBetweenStation:(NSString *)stationName1 
  andStation:(NSString *)stationName2;

이를 구현하기위해서는 약간의 그래프 알고리즘을 써야한다. 여기서는 역과 역사이에 가중치(시간)은 고려하지 않을 것이므로 최단 경로는 곧 최소한으로 역을 거치는 경로를 의미한다.

먼저 각 커넥션을 추가할때 각 역별로 인접 역을 알 수 있는 네트워크를 형성하자.

// Subway.m

- (id)init {
if (self = [super init]) {
_stations = [[NSMutableArray alloc] init];
_connections = [[NSMutableArray alloc] init];
_network = [[NSMutableDictionary alloc] init];
}
return self;
}

- (void)addToNetworkWithStation1:(Station *)station1 
station2:(Station *)station2 {
if ([[_network allKeys] containsObject:station1]) {
NSMutableArray *connectingStations = [_network objectForKey:station1];
if (![connectingStations containsObject:station2]) {
[connectingStations addObject:station2];
}
} else {
NSMutableArray *connectingStations = [NSMutableArray array];
[connectingStations addObject:station2];
[_network setObject:connectingStations forKey:station1];
}
}

- (void)addConnectionWithName:(NSString *)lineName 
stationName1:(NSString *)stationName1 
stationName2:(NSString *)stationName2 {
if ([self hasStation:stationName1] && [self hasStation:stationName2]) {
Station *station1 = [[Station alloc] initWithName:stationName1];
Station *station2 = [[Station alloc] initWithName:stationName2];
Connection *connection1 = [[Connection alloc] initWithLineName:lineName 
 station1:station1 
 station2:station2];
Connection *connection2 = [[Connection alloc] initWithLineName:lineName 
 station1:station2 
 station2:station1];
[_connections addObject:connection1];
[_connections addObject:connection2];
[connection1 release];
[connection2 release];
[self addToNetworkWithStation1:station1 
 station2:station2];
[self addToNetworkWithStation1:station2 
 station2:station1];
[station1 release];
[station2 release];
} else {
NSException *e = [NSException exceptionWithName:@"RuntimeException" 
reason:@"Invalid connection!" 
  userInfo:nil];
@throw e;
}
}

이 네트워크를 기반으로 다익스트라(Dijkstra) 알고리즘을 써서 최단 경로를 구한다.

- (Connection *)connectionFromStation:(Station *)start toStation:(Station *)end {
for (Connection *connection in _connections) {
Station *one = [connection station1];
Station *two = [connection station2];
if ([start isEqual:one] && [end isEqual:two]) {
return connection;
}
}
return nil;
}

- (NSArray *)directionsBetweenStation:(NSString *)stationName1 
  andStation:(NSString *)stationName2 {
if (![self hasStation:stationName1] || ![self hasStation:stationName2]) {
NSString *msg = @"Stations entered do not exist on this subway.";
NSException *e = [NSException exceptionWithName:@"RuntimeException" 
reason:msg 
  userInfo:nil];
@throw e;
}
Station *start = [[Station alloc] initWithName:stationName1];
Station *end = [[Station alloc] initWithName:stationName2];
NSMutableArray *route = [NSMutableArray array];
NSMutableArray *reachableStations = [NSMutableArray array];
NSMutableDictionary *previousStations = [NSMutableDictionary dictionary];
NSMutableArray *neighbors = [_network objectForKey:start];
for (Station *station in neighbors) {
if ([station isEqual:end]) {
[route addObject:[self connectionFromStation:start toStation:end]];
return route;
} else {
[reachableStations addObject:station];
[previousStations setObject:start forKey:station];
}
}
NSMutableArray *nextStations = [NSMutableArray arrayWithArray:neighbors];
Station *currentStation = start;
BOOL needToBreak = NO;
for (int i = 1; i < [_stations count]; i++) {
NSMutableArray *tmpNextStations = [NSMutableArray array];
for (Station *station in nextStations) {
[reachableStations addObject:station];
currentStation = station;
NSMutableArray *currentNeighbors = [_network objectForKey:currentStation];
for (Station *neighbor in currentNeighbors) {
if ([neighbor isEqual:end]) {
[reachableStations addObject:neighbor];
[previousStations setObject:currentStation forKey:neighbor];
needToBreak = YES;
break;
} else if (![reachableStations containsObject:neighbor]) {
[reachableStations addObject:neighbor];
[tmpNextStations addObject:neighbor];
[previousStations setObject:currentStation forKey:neighbor];
}
}
if (needToBreak) {
break;
}
}
if (needToBreak) {
break;
}
nextStations = tmpNextStations;
}
[start release];
[end release];
// 경로를 찾았음
BOOL keepLooping = YES;
Station *keyStation = end;
Station *station = nil;
while (keepLooping) {
station = [previousStations objectForKey:keyStation];
[route insertObject:[self connectionFromStation:station toStation:keyStation] atIndex:0];
if ([start isEqual:station]) {
keepLooping = NO;
}
keyStation = station;
}
return route;
}

이제 이 경로를 인자로 받아 화면에 출력하는 클래스를 만들자.

// SubwayPrinter.m

@implementation SubwayPrinter

+ (void)printDirections:(NSArray *)route {
Connection *connection = [route objectAtIndex:0];
NSString *currentLine = [connection lineName];
NSString *previousLine = currentLine;
NSLog(@"Start out at %@.", [[connection station1] name]);
NSLog(@"Get on the %@ heading towards %@.", currentLine, [[connection station2] name]);
for (int i = 1; i < [route count]; i++) {
connection = (Connection *)[route objectAtIndex:i];
currentLine = [connection lineName];
if ([currentLine isEqualToString:previousLine]) {
NSLog(@"\tContinue past %@...", [[connection station1] name]);
} else {
NSLog(@"When you get to %@, get off the %@.", [[connection station1] name], previousLine);
NSLog(@"Switch over to the %@, heading towards %@.", currentLine, [[connection station2] name]);
previousLine = currentLine;
}
}
NSLog(@"Get off at %@ and enjoy yourself!", [[connection station2] name]);
}

@end

자, 모든 구현은 끝났다. 테스트해보자. 편의상 출발역과 도착역을 인자로 받지 않고 가장 위에 선언해두었다.

// main.m

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// [LoadTester run];
NSString *start = @"HeadFirstLabs";
NSString *end = @"Choc-O-Holic, Inc";
@try {
SubwayLoader *loader = [[SubwayLoader alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"ObjectvilleSubway" 
ofType:@"txt"];
Subway *objectville = [loader loadFromFileAtPath:path];
if (![objectville hasStation:start]) {
NSLog(@"%@ is not a station in Objectville.", start);
exit(-1);
} else if (![objectville hasStation:end]) {
NSLog(@"%@ is not a station in Objectville.", end);
exit(-1);
}
NSArray *route = [objectville directionsBetweenStation:start andStation:end];
[SubwayPrinter printDirections:route];
[loader release];
}
@catch (NSException * e) {
NSLog(@"%@: %@", [e name], [e reason]);
}
[pool drain];
return 0;
}

이제 실행해보면,

Start out at HeadFirstLabs.
Get on the Booch Line heading towards LSP Lane.
When you get to LSP Lane, get off the Booch Line.
Switch over to the Wirfs-Brock Line, heading towards XHTML Expressway.
When you get to XHTML Expressway, get off the Wirfs-Brock Line.
Switch over to the Rumbaugh Line, heading towards Choc-O-Holic, Inc.
Get off at Choc-O-Holic, Inc and enjoy yourself!

감동적인 결과다! 우리는 모든 것을 해냈다.
이제는 여러분 스스로 지하철을 타고 객체마을 이곳저곳을 누벼보자!

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
| 2011.07.06 16:19 | PERMALINK | EDIT/DEL | REPLY
비밀댓글입니다
Redleaf | 2011.07.08 12:16 신고 | PERMALINK | EDIT/DEL
안녕하세요^^ 전 맥으로만 프로그래밍을 합니다 ㅎㅎ 맥북 구입처라고 하심은 애플스토어나 프리스비 이런데를 말씀하시는거 같은데요. 거기는 하드웨어를 파는 곳이지 아이폰 앱 개발과는 전혀 관계가 없기 때문에 직원들이 제대로 알고 있을 가능성이 적죠^^; 아이폰 개발자 프로그램은 연간 99달러입니다. 앱을 계속 개발하시려면 매년마다 99달러를 내셔야 합니다.
In-Ear Headphones | 2012.11.03 11:09 신고 | PERMALINK | EDIT/DEL | REPLY
표시해줄것이다 Pave Way For Success With The Monster Beats headphones

With the world of mathematics reaching leaps and bounds, an ordinary calculator is no longer sufficient. Be it students, scientists, engineers or mathematicians, they just cannot do without graphic calculators. This is where high-end calculator like the
Tod's | 2012.12.19 10:13 신고 | PERMALINK | EDIT/DEL | REPLY
저도 is an attainable, luxury lifestyle brand defined by classic American sportswear with an eclectic sensibility. It embodies the personal style and spirit of its co-founder and creative director rewtryreyuy
Jimmy Choo | 2012.12.24 11:24 신고 | PERMALINK | EDIT/DEL | REPLY
수준의 작업이 가능하면서도 무척 배우기 쉽다. 또한 "문서"라는 작업 대상에 적합한 각종 API들이 제공되기 때문에 훨씬 더 쉽 dfsadeeqrtrt
Name
Password
Homepage
Secret

티스토리 툴바