Skip to content

Instantly share code, notes, and snippets.

@indragiek
Last active August 29, 2015 14:01
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indragiek/610b1f4b466ecba8d5db to your computer and use it in GitHub Desktop.
Save indragiek/610b1f4b466ecba8d5db to your computer and use it in GitHub Desktop.
ReactiveCocoa-based wrapper around NSNetServiceBrowser
//
// LTClientBrowser.h
// LayerTreeServer
//
// Created by Indragie Karunaratne on 2014-05-21.
// Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <LayerTreeKit/LayerTreeKit.h>
@interface LTClientBrowser : NSObject
/**
* Searches for LayerTree clients.
*
* Trying to search while an existing search is still executing will result in a signal
* that errors immediately.
*
* @return A signal that sends an array of `NSNetService`s found once the search completes.
*/
- (RACSignal *)searchForClients;
@end
//
// LTClientBrowser.m
// LayerTreeServer
//
// Created by Indragie Karunaratne on 2014-05-21.
// Copyright (c) 2014 Indragie Karunaratne. All rights reserved.
//
#import "LTClientBrowser.h"
#import <LayerTreeKit/LayerTreeKit.h>
typedef NS_ENUM(NSInteger, LTClientBrowserState) {
LTClientBrowserStateStopped = 0,
LTClientBrowserStateSearching,
};
typedef NS_ENUM(NSInteger, LTCollectionMutationType) {
LTCollectionMutationTypeNone = 0,
LTCollectionMutationTypeInsertion,
LTCollectionMutationTypeRemoval
};
@interface LTCollectionMutation : NSObject
@property (nonatomic, assign, readonly) LTCollectionMutationType type;
@property (nonatomic, strong) id object;
- (instancetype)initWithType:(LTCollectionMutationType)type object:(id)object;
@end
static NSString * const LTClientBrowserErrorDomain = @"LTClientBrowserErrorDomain";
static NSError * LTClientBrowserAlreadySearchingError()
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"A search is already in progress. Please wait until the existing search has completed before attempting to start another one."};
return [NSError errorWithDomain:LTClientBrowserErrorDomain code:1 userInfo:userInfo];
}
@interface LTClientBrowser () <NSNetServiceBrowserDelegate>
@property (nonatomic, strong, readonly) NSNetServiceBrowser *serviceBrowser;
@property (nonatomic, assign) LTClientBrowserState state;
@end
@implementation LTClientBrowser
- (id)init
{
if ((self = [super init])) {
_serviceBrowser = [[NSNetServiceBrowser alloc] init];
_serviceBrowser.delegate = self;
[_serviceBrowser scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
}
return self;
}
- (void)dealloc
{
[_serviceBrowser removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
}
#pragma mark - Searching
- (RACSignal *)mutationSignalForSelector:(SEL)sel type:(LTCollectionMutationType)type
{
return [[self
rac_signalForSelector:sel fromProtocol:@protocol(NSNetServiceDelegate)]
reduceEach:^(NSNetServiceBrowser *browser, NSNetService *service, NSNumber *moreComing) {
LTCollectionMutation *mutation = [[LTCollectionMutation alloc] initWithType:type object:service];
return RACTuplePack(mutation, moreComing);
}];
}
- (RACSignal *)searchForClients
{
if (self.state == LTClientBrowserStateSearching) {
return [RACSignal error:LTClientBrowserAlreadySearchingError()];
}
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
self.state = LTClientBrowserStateSearching;
RACSignal *insertion = [self mutationSignalForSelector:@selector(netServiceBrowser:didFindService:moreComing:) type:LTCollectionMutationTypeInsertion];
RACSignal *removal = [self mutationSignalForSelector:@selector(netServiceBrowser:didRemoveService:moreComing:) type:LTCollectionMutationTypeRemoval];
NSMutableArray *services = [[NSMutableArray alloc] init];
RACDisposable *results = [[[[[RACSignal
merge:@[insertion, removal]]
reduceEach:^(LTCollectionMutation *mutation, NSNumber *moreComing) {
if (mutation.type == LTCollectionMutationTypeInsertion) {
[services addObject:mutation.object];
} else if (mutation.type == LTCollectionMutationTypeRemoval) {
[services removeObject:mutation.object];
}
return moreComing;
}]
takeUntilBlock:^BOOL(NSNumber *moreComing) {
return [moreComing isEqualToNumber:@NO];
}]
then:^RACSignal *{
return [RACSignal return:services];
}]
subscribe:subscriber];
RACDisposable *failure = [[[[[self
rac_signalForSelector:@selector(netServiceBrowser:didNotSearch:) fromProtocol:@protocol(NSNetServiceBrowserDelegate)]
take:1]
reduceEach:^(NSNetServiceBrowser *browser, NSDictionary *errorDict) {
return [RACEvent eventWithError:LTErrorFromBonjourErrorDictionary(errorDict)];
}]
dematerialize]
subscribe:subscriber];
[self.serviceBrowser searchForServicesOfType:LTServiceType inDomain:LTServiceDomain];
return [RACDisposable disposableWithBlock:^{
[results dispose];
[failure dispose];
[self.serviceBrowser stop];
self.state = LTClientBrowserStateStopped;
}];
}] setNameWithFormat:@"<%@:%p> -searchForClients", self.class, self];
}
#pragma mark - NSNetServiceBrowserDelegate
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing {}
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didRemoveService:(NSNetService *)netService moreComing:(BOOL)moreServicesComing {}
- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didNotSearch:(NSDictionary *)errorInfo {}
@end
@implementation LTCollectionMutation
- (instancetype)initWithType:(LTCollectionMutationType)type object:(id)object
{
if ((self = [super init])) {
_type = type;
_object = object;
}
return self;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment