diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..eaafd24
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+---
+language: objective-c
+
+before_script:
+ - sudo easy_install cpp-coveralls
+
+script:
+ - xctool project DTBonjour.xcodeproj -scheme "DTBonjour (iOS)" build -sdk iphonesimulator6.1 -arch i386 ONLY_ACTIVE_ARCH=NO
+ - xctool project DTBonjour.xcodeproj -scheme "DTBonjour (OS X)" build -arch x86_64 ONLY_ACTIVE_ARCH=NO
+ - appledoc -o /tmp .
+
+after_success:
+ - ./coveralls.rb --extension m --exclude-folder Demo --exclude-folder Test --exclude-folder Externals
diff --git a/AppledocSettings.plist b/AppledocSettings.plist
index 85d2e84..05430d2 100644
--- a/AppledocSettings.plist
+++ b/AppledocSettings.plist
@@ -30,6 +30,10 @@
--no-repeat-first-par
+ --include
+
+ ./Documentation/Change Log-template.markdown
+
--ignore
*.m
@@ -37,16 +41,7 @@
Demo
--index-desc
- Readme.markdown
- --include
-
- ./Documentation/Setup Guide-template.markdown
- ./Documentation/Known Issues-template.markdown
- ./Documentation/DTCoreText_Demo_App.png
- ./Documentation/DTCoreText_Linker_Flags.png
- ./Documentation/DTCoreText_Search_Paths.png
- ./Documentation/DTCoreText_Reference.png
-
+ readme.markdown
--warn-invalid-crossref
diff --git a/Core/Source/DTBonjourDataChunk.m b/Core/Source/DTBonjourDataChunk.m
index 971df76..e706ee4 100644
--- a/Core/Source/DTBonjourDataChunk.m
+++ b/Core/Source/DTBonjourDataChunk.m
@@ -123,7 +123,7 @@ - (BOOL)_encodeObject:(id)object error:(NSError **)error
{
if (error)
{
- NSString *errorMsg = [NSString stringWithFormat:@"Unknown encoding type %d", _encoding];
+ NSString *errorMsg = [NSString stringWithFormat:@"Unknown encoding type %d", (int)_encoding];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey:errorMsg};
*error = [NSError errorWithDomain:DTBonjourDataConnectionErrorDomain code:1 userInfo:userInfo];
}
@@ -251,7 +251,7 @@ - (void)_decodeHeader
*/
_sequenceNumber = [headers[@"Sequence-Number:"] unsignedIntegerValue];
- _contentLength = [headers[@"Content-Length"] longLongValue];
+ _contentLength = [[[NSNumberFormatter new] numberFromString:headers[@"Content-Length"]] unsignedIntegerValue];
_totalBytes = _rangeOfHeader.length + _contentLength;
}
diff --git a/Core/Source/DTBonjourDataConnection.h b/Core/Source/DTBonjourDataConnection.h
index 3903970..07eb3e8 100644
--- a/Core/Source/DTBonjourDataConnection.h
+++ b/Core/Source/DTBonjourDataConnection.h
@@ -12,17 +12,30 @@
/**
Type of encoding to use for sending objects
*/
-typedef enum
+typedef NS_ENUM(NSUInteger, DTBonjourDataConnectionContentType)
{
+ /**
+ Encode sent objects with NSCoding
+ */
DTBonjourDataConnectionContentTypeNSCoding = 0,
+
+ /**
+ Encode sent objects as JSON. Note that not all kinds of Objective-C objects can be represented as JSON.
+ */
DTBonjourDataConnectionContentTypeJSON,
-} DTBonjourDataConnectionContentType;
+};
extern NSString * DTBonjourDataConnectionErrorDomain;
+extern CGFloat DTBonjourDataConnectionDefaultTimeout;
@class DTBonjourDataConnection, DTBonjourDataChunk;
+
+/**
+ Protocol to inform delegate of a DTBonjourDataConnection about what is happening
+ */
@protocol DTBonjourDataConnectionDelegate
+
@optional
// sending
@@ -130,11 +143,19 @@ extern NSString * DTBonjourDataConnectionErrorDomain;
*/
/**
- Opens the connection and establishes the input and output streams.
+ Opens the connection and establishes the input and output streams. Cancels the
+ opening after a timeout of `DTBonjourDataConnectionDefaultTimeout` seconds.
@returns `YES` if the connection could be established.
*/
- (BOOL)open;
+/**
+ Opens the connection and establishes the input and output streams.
+ @param timeout Timeout in seconds after which to cancel the stream opening.
+ @returns `YES` if the connection could be established.
+ */
+- (BOOL)openWithTimeout:(CGFloat)timeout;
+
/**
Closes the connection
*/
diff --git a/Core/Source/DTBonjourDataConnection.m b/Core/Source/DTBonjourDataConnection.m
index 1a0a423..c615ae1 100644
--- a/Core/Source/DTBonjourDataConnection.m
+++ b/Core/Source/DTBonjourDataConnection.m
@@ -12,8 +12,88 @@
#import
+#define kDTBonjourQNetworkAdditionsCheckSEL NSSelectorFromString(@"netService:didAcceptConnectionWithInputStream:outputStream:")
+
+CGFloat DTBonjourDataConnectionDefaultTimeout = 60.0;
NSString * DTBonjourDataConnectionErrorDomain = @"DTBonjourDataConnection";
+@interface NSNetService (QNetworkAdditions)
+
+- (BOOL)qNetworkAdditions_getInputStream:(out NSInputStream **)inputStreamPtr
+ outputStream:(out NSOutputStream **)outputStreamPtr;
+
+@end
+
+@implementation NSNetService (QNetworkAdditions)
+
+- (BOOL)qNetworkAdditions_getInputStream:(out NSInputStream **)inputStreamPtr
+ outputStream:(out NSOutputStream **)outputStreamPtr
+ // The following works around three problems with
+ // -[NSNetService getInputStream:outputStream:]:
+ //
+ // o -- Currently the returns the streams with
+ // +1 retain count, which is counter to Cocoa conventions and results in
+ // leaks when you use it in ARC code.
+ //
+ // o -- If you create two pairs of streams from
+ // one NSNetService and then attempt to open all the streams simultaneously,
+ // some of the streams might fail to open.
+ //
+ // o -- If you create streams using
+ // -[NSNetService getInputStream:outputStream:], start to open them, and
+ // then release the last reference to the original NSNetService, the
+ // streams never finish opening. This problem is exacerbated under ARC
+ // because ARC is better about keeping things out of the autorelease pool.
+{
+ BOOL result;
+ CFReadStreamRef readStream;
+ CFWriteStreamRef writeStream;
+
+ result = NO;
+
+ readStream = NULL;
+ writeStream = NULL;
+
+ if ( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) ) {
+ CFNetServiceRef netService;
+
+ netService = CFNetServiceCreate(
+ NULL,
+ (__bridge CFStringRef) [self domain],
+ (__bridge CFStringRef) [self type],
+ (__bridge CFStringRef) [self name],
+ 0
+ );
+ if (netService != NULL) {
+ CFStreamCreatePairWithSocketToNetService(
+ NULL,
+ netService,
+ ((inputStreamPtr != nil) ? &readStream : NULL),
+ ((outputStreamPtr != nil) ? &writeStream : NULL)
+ );
+ CFRelease(netService);
+ }
+
+ // We have failed if the client requested an input stream and didn't
+ // get one, or requested an output stream and didn't get one. We also
+ // fail if the client requested neither the input nor the output
+ // stream, but we don't get here in that case.
+
+ result = ! ((( inputStreamPtr != NULL) && ( readStream == NULL)) ||
+ ((outputStreamPtr != NULL) && (writeStream == NULL)));
+ }
+ if (inputStreamPtr != NULL) {
+ *inputStreamPtr = CFBridgingRelease(readStream);
+ }
+ if (outputStreamPtr != NULL) {
+ *outputStreamPtr = CFBridgingRelease(writeStream);
+ }
+
+ return result;
+}
+
+@end
+
@interface DTBonjourDataConnection ()
@end
@@ -82,11 +162,27 @@ - (id)initWithService:(NSNetService *)service
if (self)
{
- if (![service getInputStream:&_inputStream outputStream:&_outputStream])
- {
- return nil;
- }
-
+ NSInputStream *in;
+ NSOutputStream *out;
+
+ if (![[service delegate] respondsToSelector:kDTBonjourQNetworkAdditionsCheckSEL])
+ {
+ // Older iOS/OSX versions need a patch for getting input and output
+ // streams see the `QNetworkAdditions` above. (If the delegate does not
+ // implement the `kDTBonjourQNetworkAdditionsCheck` selector, we can
+ // simply use the patched version.
+ if ([service qNetworkAdditions_getInputStream:&in outputStream:&out])
+ return nil;
+ }
+ else
+ {
+ // iOS7/OSX10.9
+ if (![service getInputStream:&in outputStream:&out])
+ return nil;
+ }
+
+ _inputStream = in;
+ _outputStream = out;
_outputQueue = [[NSMutableArray alloc] init];
}
@@ -113,7 +209,7 @@ - (void)dealloc
[self close];
}
-- (BOOL)open
+- (BOOL)openWithTimeout:(CGFloat)timeout
{
[_inputStream setDelegate:self];
[_outputStream setDelegate:self];
@@ -122,9 +218,24 @@ - (BOOL)open
[_inputStream open];
[_outputStream open];
+ __weak id weakSelf = self;
+ double delayInSeconds = timeout;
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
+ // No connection after timeout, closing.
+ if (![weakSelf isOpen]) {
+ [weakSelf close];
+ }
+ });
+
return YES;
}
+- (BOOL)open
+{
+ return [self openWithTimeout:DTBonjourDataConnectionDefaultTimeout];
+}
+
- (void)close
{
if (!_inputStream&&!_outputStream)
@@ -142,50 +253,23 @@ - (void)close
_outputStream = nil;
if ([_delegate respondsToSelector:@selector(connectionDidClose:)])
- {
[_delegate connectionDidClose:self];
- }
}
- (BOOL)isOpen
{
if (!_inputStream)
- {
return NO;
- }
NSStreamStatus inputStatus = [_inputStream streamStatus];
-
- switch (inputStatus)
- {
- case NSStreamStatusNotOpen:
- case NSStreamStatusAtEnd:
- case NSStreamStatusClosed:
- case NSStreamStatusError:
- {
- return NO;
- }
-
- default:
- break;
- }
-
- NSStreamStatus outputStatus = [_outputStream streamStatus];
-
- switch (outputStatus)
- {
- case NSStreamStatusNotOpen:
- case NSStreamStatusAtEnd:
- case NSStreamStatusClosed:
- case NSStreamStatusError:
- {
- return NO;
- }
-
- default:
- break;
- }
-
+ NSStreamStatus outputStatus = [_outputStream streamStatus];
+
+ if (NSStreamStatusOpen != inputStatus)
+ return NO;
+
+ if (NSStreamStatusOpen != outputStatus)
+ return NO;
+
return YES;
}
@@ -198,7 +282,7 @@ - (void)_startOutput
DTBonjourDataChunk *chunk = _outputQueue[0];
- if (chunk.numberOfTransferredBytes==0)
+ if (0 == chunk.numberOfTransferredBytes)
{
// nothing sent yet
if ([_delegate respondsToSelector:@selector(connection:willStartSendingChunk:)])
@@ -207,7 +291,7 @@ - (void)_startOutput
}
}
- NSUInteger writtenBytes = [chunk writeToOutputStream:_outputStream];
+ NSInteger writtenBytes = [chunk writeToOutputStream:_outputStream];
if (writtenBytes > 0)
{
@@ -252,12 +336,13 @@ - (BOOL)sendObject:(id)object error:(NSError **)error
return NO;
}
- DTBonjourDataChunk *newChunk = [[DTBonjourDataChunk alloc] initWithObject:object encoding:self.sendingContentType error:error];
+ DTBonjourDataChunk *newChunk = [[DTBonjourDataChunk alloc]
+ initWithObject:object
+ encoding:self.sendingContentType
+ error:error];
if (!newChunk)
- {
return NO;
- }
newChunk.sequenceNumber = _chunkSequenceNumber;
@@ -267,7 +352,9 @@ - (BOOL)sendObject:(id)object error:(NSError **)error
if (queueWasEmpty && _outputStream.streamStatus == NSStreamStatusOpen)
{
- [self _startOutput];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self _startOutput];
+ });
}
return YES;
@@ -341,6 +428,8 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent
case NSStreamEventErrorOccurred:
{
NSLog(@"Error occurred: %@", [aStream.streamError localizedDescription]);
+
+ // Intentional fall-through.
}
case NSStreamEventEndEncountered:
diff --git a/DTBonjour.podspec b/DTBonjour.podspec
index 39415a5..5c230c7 100644
--- a/DTBonjour.podspec
+++ b/DTBonjour.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'DTBonjour'
- spec.version = '1.0.0'
+ spec.version = '1.1.0'
spec.summary = "Client/Server Communication of NSObjects over WiFi."
spec.homepage = "https://github.com/Cocoanetics/DTBonjour"
spec.author = { "Oliver Drobnik" => "oliver@drobnik.com" }
diff --git a/DTBonjour.xcodeproj/project.pbxproj b/DTBonjour.xcodeproj/project.pbxproj
index 336a13c..a66e993 100644
--- a/DTBonjour.xcodeproj/project.pbxproj
+++ b/DTBonjour.xcodeproj/project.pbxproj
@@ -330,6 +330,7 @@
A719022816A7F5E300C1EC52 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
DSTROOT = /tmp/DTBonjour.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Core/DTBonjour-Prefix.pch";
@@ -340,6 +341,7 @@
A719022916A7F5E300C1EC52 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
DSTROOT = /tmp/DTBonjour.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Core/DTBonjour-Prefix.pch";
diff --git a/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (OS X).xcscheme b/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (OS X).xcscheme
new file mode 100644
index 0000000..48d1e16
--- /dev/null
+++ b/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (OS X).xcscheme
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (iOS).xcscheme b/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (iOS).xcscheme
new file mode 100644
index 0000000..1a543a1
--- /dev/null
+++ b/DTBonjour.xcodeproj/xcshareddata/xcschemes/DTBonjour (iOS).xcscheme
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Documentation/Change Log-template.markdown b/Documentation/Change Log-template.markdown
new file mode 100644
index 0000000..bbff6a8
--- /dev/null
+++ b/Documentation/Change Log-template.markdown
@@ -0,0 +1,17 @@
+Change Log
+==========
+
+This is the history of version updates.
+
+**Version 1.1.0**
+
+- FIXED: Various type warnings
+- ADDED: New method on DTBonjourDataConnection which allows specifying a timeout
+- ADDED: Delegate method for when connection was opened
+- ADDED: arm64 support
+- CHANGED: Updated for building with Xcode 5
+- CHANGED: Use original NSNetService method to get connection where workaround no longer necessary
+
+**Version 1.0.0**
+
+Initial release
\ No newline at end of file
diff --git a/coveralls.rb b/coveralls.rb
new file mode 100755
index 0000000..e182aab
--- /dev/null
+++ b/coveralls.rb
@@ -0,0 +1,136 @@
+#!/usr/bin/env ruby
+
+require 'etc'
+require 'fileutils'
+require 'find'
+require 'optparse'
+
+# arraw of source subfolders to exclude
+excludedFolders = []
+extensionsToProcess = []
+coveralls_cmd = "coveralls"
+
+excludeHeaders = false
+
+# create option parser
+opts = OptionParser.new
+opts.banner = "Usage: coveralls.rb [options]"
+
+opts.on('-e', '--exclude-folder FOLDER', 'Folder to exclude') do |v|
+ excludedFolders << v
+ coveralls_cmd.concat(" -e #{v}")
+end
+
+opts.on('-h', '--exclude-headers', 'Ignores headers') do |v|
+ excludeHeaders = true
+end
+
+opts.on('-x', '--extension EXT', 'Source file extension to process') do |v|
+ extensionsToProcess << v
+ coveralls_cmd.concat(" -x #{v}")
+end
+
+opts.on_tail("-?", "--help", "Show this message") do
+ puts opts
+ exit
+end
+
+# parse the options
+begin
+ opts.parse!(ARGV)
+rescue OptionParser::InvalidOption => e
+ puts e
+ puts opts
+ exit(1)
+end
+
+# the folders
+workingDir = Dir.getwd
+derivedDataDir = "#{Etc.getpwuid.dir}/Library/Developer/Xcode/DerivedData/"
+outputDir = workingDir + "/gcov"
+
+# create gcov output folder
+FileUtils.mkdir outputDir
+
+# pattern to get source file from first line of gcov file
+GCOV_SOURCE_PATTERN = Regexp.new(/Source:(.*)/)
+
+# enumerate all gcda files underneath derivedData
+Find.find(derivedDataDir) do |gcda_file|
+
+ if gcda_file.match(/\.gcda\Z/)
+
+ #get just the folder name
+ gcov_dir = File.dirname(gcda_file)
+
+ # cut off absolute working dir to get relative source path
+ relative_input_path = gcda_file.slice(derivedDataDir.length, gcda_file.length)
+ puts "\nINPUT: #{relative_input_path}"
+
+ #process the file
+ result = %x( gcov '#{gcda_file}' -o '#{gcov_dir}' )
+
+ # filter the resulting output
+ Dir.glob("*.gcov") do |gcov_file|
+
+ firstLine = File.open(gcov_file).readline
+ match = GCOV_SOURCE_PATTERN.match(firstLine)
+
+ if (match)
+
+ source_path = match[1]
+
+ puts "source: #{source_path} - #{workingDir}"
+
+ if (source_path.start_with? workingDir)
+
+ # cut off absolute working dir to get relative source path
+ relative_path = source_path.slice(workingDir.length+1, source_path.length)
+
+ extension = File.extname(relative_path)
+ extension = extension.slice(1, extension.length-1)
+
+ puts "#{extension}"
+
+ # get the path components
+ path_comps = relative_path.split(File::SEPARATOR)
+
+ shouldProcess = false
+ exclusionMsg =""
+
+ if (excludedFolders.include?(path_comps[0]))
+ exclusionMsg = "excluded via option"
+ else
+ if (excludeHeaders == true && extension == 'h')
+ exclusionMsg = "excluded header"
+ else
+ if (extensionsToProcess.count == 0 || extensionsToProcess.include?(extension))
+ shouldProcess = true
+ else
+ exclusionMsg = "excluded extension"
+ shouldProcess = false
+ end
+ end
+ end
+
+ if (shouldProcess)
+ puts " - process: #{relative_path}"
+ FileUtils.mv(gcov_file, outputDir)
+ else
+ puts " - ignore: #{relative_path} (#{exclusionMsg})"
+ FileUtils.rm gcov_file
+ end
+ else
+ puts " - ignore: #{gcov_file} (outside source folder)"
+ FileUtils.rm gcov_file
+ end
+ end
+ end
+ end
+end
+
+#call the coveralls, exclude some files
+system coveralls_cmd
+
+#clean up
+FileUtils.rm_rf outputDir
diff --git a/readme.markdown b/readme.markdown
index 63f9e31..032a84b 100644
--- a/readme.markdown
+++ b/readme.markdown
@@ -1,6 +1,8 @@
About DTBonjour
==================
+[![Build Status](https://travis-ci.org/Cocoanetics/DTBonjour.png?branch=develop)](https://travis-ci.org/Cocoanetics/DTFoundation) [![Coverage Status](https://coveralls.io/repos/Cocoanetics/DTFoundation/badge.png?branch=develop)](https://coveralls.io/r/Cocoanetics/DTBonjour?branch=develop)
+
DTBonjour had its origin when I wanted communicate between a Mac app and an iOS app. It greatly simplifies networking over WiFi by giving you an easy method to transmit any NSObject that conforms to NSCoding.
Documentation