トップ «前の日記(2013-09-23) 最新 次の日記(2013-09-25)» 編集

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|

2013-09-24 [iOS]RFCViewer(通信について)

オートマティズムのレスポンスパーサを参考に、独自のカスタマイズを施している。

通信のように外部とのやり取りを行う場合、Cocoa touchでは、実装された時期によって、自分でスレッド化したり、デリゲートだったり、キューやGCDだったりして、単純にAPIに沿うと、機能毎に実装が異なる事になってしまう。レスポンスパーサをそれを共通の形にすると自分は理解している。

ヘッダーファイルを見てみよう。
オートマティズムでは、デリゲートとなるコネクタに対して、デリゲート・メソッド呼び出しで結果を伝えているが、それを呼び出し元が用意したブロックを呼び出すようにカスタマイズをしている。

#import <Foundation/Foundation.h>
 
@class RFCResponseParser;
 
#define kRFCResponseParserNoError       0
#define kRFCResponseParserGenericError  1
 
typedef enum _RFCNetworkState {
    kRFCNetworkStateNotConnected = 0,
    kRFCNetworkStateInProgress,
    kRFCNetworkStateFinished,
    kRFCNetworkStateError,
    kRFCNetworkStateCanceled,
} RFCNetworkSate;
 
typedef void (^RFCResponseParserCompletionHandler)(RFCResponseParser *parser);
 
@protocol RFCResponseParserDelegate 
- (void)parser:(RFCResponseParser*)parser didReceiveResponse:(NSURLResponse*)response;
- (void)parser:(RFCResponseParser *)parser didReceiveData:(NSData *)data;
- (void)parserDidFinishLoading:(RFCResponseParser *)parser;
- (void)parser:(RFCResponseParser *)parser didFailWithError:(NSError*)error;
- (void)parserDidCancel:(RFCResponseParser *)parser;
@end
 
@interface RFCResponseParser : NSObject
 
@property (assign, readonly, nonatomic) RFCNetworkSate          networkState;
@property (assign, nonatomic) NSUInteger                        index;
@property (strong, nonatomic) NSError                           *error;
@property (strong, nonatomic) NSOperationQueue                  *queue;
@property (weak, nonatomic) id       delegate;
@property (copy, nonatomic)RFCResponseParserCompletionHandler   completionHandler;
@property (strong, readonly, nonatomic) NSArray                 *indexArray;
@property (strong, readonly, nonatomic) NSString                *rfc;
 
- (void)parse;
- (void)cancel;
 
@end

URL通信という事で、通常はメインスレッドでデリゲートの非同期の処理という事になるが、先日のCocoa勉強会で教えてもらった、キューを使っている。これで、メインスレッド以外のスレッドで動作する。

#import "Document.h"
#import "RFCResponseParser.h"
 
@interface RFCResponseParser () 
@property (assign, readwrite, nonatomic) RFCNetworkSate networkState;
@property (strong, readwrite, nonatomic) NSArray        *indexArray;
@property (strong, nonatomic) NSURLConnection           *urlConnection;
@property (strong, nonatomic) NSMutableData             *downloadedData;
- (void)_notifyParserDidFinishLoading;
- (void)_notifyParserDidFailWithError:(NSError*)error;
- (NSError *)_errorWithCode:(NSInteger)code localizedDescription:(NSString *)localizedDescription;
- (void)_parseIndexArray;
@end
 
@implementation RFCResponseParser
 
@synthesize networkState = _networkState;
@synthesize index = _index;
@synthesize error = _error;
@synthesize queue = _queue;
@synthesize delegate = _delegate;
@synthesize completionHandler = _completionHandler;
@synthesize indexArray = _indexArray;
@synthesize urlConnection = _urlConnection;
@synthesize downloadedData = _downloadedData;
 
- (id)init
{
    DBGMSG(@"%s", __func__);
    self = [super init];
    if (self) {
        _networkState = kRFCNetworkStateNotConnected;
        _index = 0;
        _error = nil;
        _queue = nil;
        _delegate = nil;
        _completionHandler = NULL;
        _indexArray = nil;
        _urlConnection = nil;
        _downloadedData = nil;
    }
    return self;
}
 
- (void)dealloc
{
    DBGMSG(@"%s", __func__);
    self.networkState = kRFCNetworkStateNotConnected;
    self.index = 0;
    self.error = nil;
    self.queue = nil;
    self.delegate = nil;
    self.completionHandler = NULL;
    self.indexArray = nil;
    self.urlConnection = nil;
    self.downloadedData = nil;
}
 
- (void)parse
{
    DBGMSG(@"%s", __func__);
    NSString    *urlString = nil;
    
    if (self.index == 0) {
        urlString = [Document sharedDocument].indexUrlString;
    }
    else {
        urlString = [[Document sharedDocument] rfcUrlStringWithIndex:self.index];
    }
    
    NSURLRequest    *urlRequest = nil;
    if (urlString) {
        NSURL   *url;
        url = [NSURL URLWithString:urlString];
        if (url) {
            urlRequest = [NSURLRequest requestWithURL:url];
        }
    }
    DBGMSG(@"%s urlString(%@)", __func__, urlString);
    
    if (! urlRequest) {
        self.networkState = kRFCNetworkStateError;
        self.error = [self _errorWithCode:kRFCResponseParserGenericError
                     localizedDescription:@"NSURLRequestの生成に失敗しました。"];
        return;
    }
    
    self.downloadedData = [[NSMutableData alloc] init];
        
    self.urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest
                                                         delegate:self
                                                 startImmediately:NO];
    [self.urlConnection setDelegateQueue:self.queue];
    
    [self willChangeValueForKey:@"networkState"];
    self.networkState = kRFCNetworkStateInProgress;
    [self didChangeValueForKey:@"networkState"];
    
    [self.urlConnection start];
}
 
- (void)cancel
{
    DBGMSG(@"%s", __func__);
    [self.urlConnection cancel];
    
    self.downloadedData = nil;
    
    [self willChangeValueForKey:@"networkState"];
    self.networkState = kRFCNetworkStateCanceled;
    [self didChangeValueForKey:@"networkState"];
    
    if ([self.delegate respondsToSelector:@selector(parserDidCancel:)]) {
        [self.delegate parserDidCancel:self];
    }
    
    self.urlConnection = nil;
}
 
- (NSString *)rfc
{
    DBGMSG(@"%s", __func__);
    NSString    *result = [[NSString alloc] initWithData:self.downloadedData encoding:NSUTF8StringEncoding];
    return result;
}
 
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
    DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
    if ([self.delegate respondsToSelector:@selector(parser:didReceiveResponse:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate parser:self didReceiveResponse:response];
        });
    }
}
 
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
    DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
    [self.downloadedData appendData:data];
    
    if ([self.delegate respondsToSelector:@selector(parser:didReceiveData:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate parser:self didReceiveData:data];
        });
    }
}
 
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
    DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
    
    [self willChangeValueForKey:@"networkState"];
    self.networkState = kRFCNetworkStateFinished;
    [self didChangeValueForKey:@"networkState"];
    
    [self _parseIndexArray];
	
    dispatch_async(dispatch_get_main_queue(), ^{
        [self _notifyParserDidFinishLoading];
    });
	
    self.urlConnection = nil;
}
 
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
    DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
    self.error = error;
    
    [self willChangeValueForKey:@"networkState"];
    self.networkState = kRFCNetworkStateError;
    [self didChangeValueForKey:@"networkState"];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self _notifyParserDidFailWithError:error];
    });
    
    self.urlConnection = nil;
}
 
- (void)_notifyParserDidFinishLoading
{
    if ([self.delegate respondsToSelector:@selector(parserDidFinishLoading:)]) {
        [self.delegate parserDidFinishLoading:self];
    }
}
 
- (void)_notifyParserDidFailWithError:(NSError*)error
{
    if ([self.delegate respondsToSelector:@selector(parser:didFailWithError:)]) {
        [self.delegate parser:self didFailWithError:error];
    }
}
 
- (NSError *)_errorWithCode:(NSInteger)code localizedDescription:(NSString *)localizedDescription
{
    NSDictionary    *userInfo = [NSDictionary dictionaryWithObject:localizedDescription forKey:NSLocalizedDescriptionKey];
    NSError         *error = [NSError errorWithDomain:@"RFCViewer" code:code userInfo:userInfo];
    return error;
}
    :
@end

キューを使って別スレッドで動作させる場合に気をつけないと行けないのは、呼び出し元に戻す際は、メインスレッドでということだ。そこで、メインスレッドで動作する- _notifyParserDidFinishLoadingと- _notifyParserDidFailWithErrorのメソッドを用意して、それをdispatch_async()関数でメインスレッドで呼びようにしている。

別スレッドで動作する事の副作用で、読み込んだRFCのインデックス情報の解析には、それなりに時間があかかると思うが、この処理が別スレッドで実行される為、UIのレスポンスへの影響が小さくなっている。

- (void)_parseIndexArray
{
    DBGMSG( @"%s [Main=%@]", __FUNCTION__, [NSThread isMainThread] ? @"YES" : @"NO ");
    
    NSMutableArray  *indexArray = [[NSMutableArray alloc] init];
    
    NSString	*indexString = nil;
	NSString	*parsedString = nil;
	NSRange		range, subRange;
	NSUInteger	length;
    __block BOOL    isCreatedOn = NO;
    BOOL        isIndex = NO;
    NSMutableString *rfcString = nil;
    
	indexString = [[NSString alloc] initWithData:self.downloadedData
										encoding:NSUTF8StringEncoding];
	length = [indexString length];
	range = NSMakeRange(0, length);
	while (0 < range.length) {
        /* 行単位の取り出し */
		subRange = [indexString lineRangeForRange:NSMakeRange(range.location, 0)];
		parsedString = [indexString substringWithRange:subRange];
		
        /* 改行文字削除 */
		NSCharacterSet	*chSet = nil;
		NSScanner		*scanner = nil;
		chSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
		scanner = [NSScanner scannerWithString:parsedString];
		if (![scanner isAtEnd]) {
			NSString	*line = nil;
			[scanner scanUpToCharactersFromSet:chSet intoString:&line];
			parsedString = line;
		}
        else {
            parsedString = @"";
        }
        //DBGMSG(@"[%@]", parsedString);
        
        /* 更新日付に到着 */
        NSError *error = nil;
        NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\(CREATED ON: (\\d{2}/\\d{2}/\\d{4})\\.\\)"
                                                                               options:NSRegularExpressionCaseInsensitive
                                                                                 error:&error];
        [regex enumerateMatchesInString:parsedString
                                options:0
                                  range:NSMakeRange(0, parsedString.length)
                             usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
                                 if (match.numberOfRanges) {
                                     isCreatedOn = YES;
                                 }
                             }];
        
        /* 目次に到達 */
        if ((isCreatedOn) && ([parsedString isEqualToString:@"RFC INDEX"])) {
            isIndex = YES;
        }
        else if (! isIndex) {
        }
        
        /* 区切り */
        else if ([parsedString isEqualToString:@""]) {
            if (rfcString) {
                /* 表題の取り出し */
                //DBGMSG(@"%@", rfcString);
                NSError *error = nil;
                NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^([0-9]{4}+)\\s(.+?\\.)"
                                                                                       options:NSRegularExpressionCaseInsensitive
                                                                                         error:&error];
                __block NSString    *rfcNumber = nil;
                __block NSString    *title = nil;
                [regex enumerateMatchesInString:rfcString
                                        options:0
                                          range:NSMakeRange(0, rfcString.length)
                                     usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
                                         //NSRange    matchRange = [match range];
                                         NSRange    firstHalfRange = [match rangeAtIndex:1];
                                         NSRange    secondHalfRange = [match rangeAtIndex:2];
                                         rfcNumber = [rfcString substringWithRange:firstHalfRange];
                                         title = [rfcString substringWithRange:secondHalfRange];
                                     }];
                RFC *rfc = [[RFC alloc] init];
                rfc.rfcNumber = rfcNumber;
                rfc.title = title;
                //DBGMSG(@"%@ : %@", rfcNumber, rfc.title);
                [indexArray addObject:rfc];
            }
            rfcString = nil;
        }
        
        /* 先頭 */
        else {
            NSRange match = [parsedString rangeOfString:@"^[0-9]{4}+\\s" options:NSRegularExpressionSearch];
            if (match.location != NSNotFound) {
                //DBGMSG(@"++++先頭");
                rfcString = [[NSMutableString alloc] initWithString:parsedString];
            }
            else if (rfcString) {
                [rfcString appendString:parsedString];
            }
        }		
		range.location = NSMaxRange(subRange);
		range.length -= subRange.length;
	}
    self.indexArray = indexArray;
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/RFCViewer - GitHub

_ 【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

トップ «前の日記(2013-09-23) 最新 次の日記(2013-09-25)» 編集