synchronizing without internet - multipeer connectivity (ios)
DESCRIPTION
Slides of my presentation in Codemotion 2014, Madrid talking about Multipeer connectivity and how to sync data between devices without need an internet connection or without to create an infraestructure network. See materials in https://github.com/patoroco/Codemotion-2014-Multipeer-ConnectivityTRANSCRIPT
Synchronizingwithout internetCodemotion 2014, Madrid
Who am I?Jorge Maroto (@patoroco)
http://maroto.me
· iOS Developer @ticketeaeng
· Playing with iOS since 2010
· Fanboy
Synchronize
¿Internet?
LAN (wired / wi-fi)
What about iOS?
Changelog· iOS 3: Game Kit
· iOS 4: Game Center· iOS 5: Core Bluetooth
· iOS 6: Core Bluetooth advertising· iOS 7: Multipeer Connectivity &
iBeacons· iOS 8: Handoff
Changelog· iOS 3: Game Kit
· iOS 4: Game Center· iOS 5: Core Bluetooth
· iOS 6: Core Bluetooth advertising· iOS 7: Multipeer Connectivity &
iBeacons· iOS 8: Handoff
MultipeerConnectivity
Multipeer connectivity· Appears in iOS7.
· Ability to connect to a mesh of peers.· Able to connect to peers over WiFi, ad-
hoc wireless, and Bluetooth.· Doesn't require server infraestructure.
· Peers must be 'nearby'.
MultipeerConnectivity.frameworkBonjour | CFNetwork
Two phases· Discovery· Session
Classes#import <MultipeerConnectivity/MultipeerConnectivity.h>
Session infoDiscovery
Advertisement
Session info· MCPeerId· MCSession
MCPeerIdMCPeerID *peerId = [[MCPeerID alloc] initWithDisplayName:self.deviceName];
MCSessionMCSession *session = [[MCSession alloc] initWithPeer:self.peerId];
session.delegate = self;
<MCSessionDelegate>- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ switch (state): { case MCSessionStateConnected: ... case MCSessionStateConnecting: ... case MCSessionStateNotConnected: ... }}
<MCSessionDelegate>// DATA- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {}
// RESOURCES- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress {}- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{}
// STREAMS- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{}
Discovery· MCBrowserViewController· MCNearbyServiceBrowser
MCBrowserViewControllerNSString * const serviceIdentifier = @"codemotion-demo";
MCBrowserViewController *browser = [[MCBrowserViewController alloc] initWithServiceType:serviceIdentifier session:self.appdelegate.session];
browser.delegate = self;
[self presentViewController:browser animated:YES completion:nil];
<MCBrowserViewControllerDelegate>- (void)browserViewControllerDidFinish:(MCBrowserViewController *)b {}
- (void)browserViewControllerWasCancelled:(MCBrowserViewController *)b {}
Advertisement· MCAdvertiserAssistant
· MCNearbyServicesAdvertiser
MCAdvertiserAssistantNSString * const serviceIdentifier = @"codemotion-demo";
MCAdvertiserAssistant *advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:self.serviceIdentifier discoveryInfo:nil session:session];
[advertiser start];
Session phase
Sending data· NSData to an array of peers.
· NSURL resource to a peer.· NSStream to a peer.
Send NSData to an array of peers- (BOOL)sendData:(NSData *)data toPeers:(NSArray *)peerIDs withMode:(MCSessionSendDataMode)mode error:(NSError **)error;
Modes· MCSessionSendDataReliable· MCSessionSendDataUnreliable
DEMOHello world in MC
Video
Send resource to a peer
- (NSProgress *)sendResourceAtURL:(NSURL *)resourceURL withName:(NSString *)resourceName toPeer:(MCPeerID *)peerID withCompletionHandler:(void(^)(NSError *error))completionHandler;
ExampleShare images from photo
library
@property (weak, nonatomic) IBOutlet UIImageView *picture;
- (IBAction)choosePhoto:(UIButton *)sender{ UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.allowsEditing = NO; picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; picker.mediaTypes = @[(NSString *)kUTTypeImage];
[self presentViewController:picker animated:YES completion:nil];}
<UIImagePickerDelegate>#pragma mark - UIImagePickerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ UIImage *selectedImage = info[UIImagePickerControllerOriginalImage]; NSData *jpegImg = UIImageJPEGRepresentation(selectedImage, 0.5);
NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingString:@"pic.jpg"]; self.imageURL = [NSURL fileURLWithPath:tmpPath];
[jpegImg writeToURL:self.imageURL atomically:NO];
self.picture.image = selectedImage; [picker dismissViewControllerAnimated:YES completion:nil];}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [picker dismissViewControllerAnimated:YES completion:NULL];}
<MCSessionDelegate>#pragma mark - - (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{ dispatch_async(dispatch_get_main_queue(), ^{ self.picture.image = nil; self.picture.backgroundColor = [UIColor yellowColor]; });}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{ NSData *data = [NSData dataWithContentsOfURL:localURL]; UIImage *image = [[UIImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{ self.picture.image = image; });}
VideoShare images from photo
library
Streaming- (NSOutputStream *)startStreamWithName:(NSString *)streamName toPeer:(MCPeerID *)peerID error:(NSError **)error;
NSOutputStream: open
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ ... if (state == MCSessionStateConnected) { NSError *error; NSOutputStream *output = [session startStreamWithName:@"streamName" toPeer:peer error:&error];
if (error) { return; }
[output scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [output open]; } ...}
NSOutputStream: write
NSData *data = [NSData data]; [self.output write:data.bytes maxLength:data.length];
NSInputStream- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{ stream.delegate = self; [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [stream open];}
<NSStreamDelegate>- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{ switch (eventCode) { case NSStreamEventOpenCompleted: ... case NSStreamEventEndEncountered: ... case NSStreamEventHasBytesAvailable: { NSInputStream *inputStream = (NSInputStream *)aStream;
uint8_t buffer[1024]; NSInteger size = [inputStream read:(uint8_t *)buffer maxLength:1024];
NSData *data = [NSData dataWithBytes:buffer length:size]; ... // Manage data received ... break; } default: break; }}
ExampleShared whiteboard
UIPanGestureRecognizer- (IBAction)panReceived:(UIPanGestureRecognizer *)sender{ CGPoint point = [sender locationInView:sender.view];
[self.drawable drawPoint:point state:sender.state];
NSData *data = [NSData drawDataWithGestureState:sender.state point:[sender locationInView:sender.view]]; [self.output write:data.bytes maxLength:data.length];}
- (void)drawPoint:(CGPoint)point state:(UIGestureRecognizerState)state{ switch (state) { case UIGestureRecognizerStateBegan: { lastPoint = point; break; } case UIGestureRecognizerStateChanged: { CGPoint currentPoint = point;
UIGraphicsBeginImageContext(self.layout.frame.size); [self.tmpLayout.image drawInRect:CGRectMake(0, 0, self.layout.frame.size.width, self.layout.frame.size.height)]; CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 10); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0, 0, 0, 1.0); CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
CGContextStrokePath(UIGraphicsGetCurrentContext()); self.tmpLayout.image = UIGraphicsGetImageFromCurrentImageContext(); [self.tmpLayout setAlpha:1]; UIGraphicsEndImageContext();
lastPoint = currentPoint; break; } case UIGestureRecognizerStateEnded: { UIGraphicsBeginImageContext(self.layout.frame.size); [self.layout.image drawInRect:CGRectMake(0, 0, self.layout.frame.size.width, self.layout.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0]; [self.tmpLayout.image drawInRect:CGRectMake(0, 0, self.layout.frame.size.width, self.layout.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0]; self.layout.image = UIGraphicsGetImageFromCurrentImageContext(); self.tmpLayout.image = nil; UIGraphicsEndImageContext(); break; }
default: break; }}
- (void)clearScreen{ UIGraphicsBeginImageContext(self.layout.frame.size); CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(ctx, 255.0, 255.0, 255.0, 1.0); CGContextFillRect(ctx, CGRectMake(0, 0, self.tmpLayout.frame.size.width, self.tmpLayout.frame.size.height)); self.layout.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();}
VideoShared whiteboard
All together
'simple sync' example· n-devices.
· Create a hash by each register based in time.
· First sync in batch using a file.· NSOutputstream to send 'ligth data'.
· Off and on send counters & timers using MCSessionSendDataReliable.
'simple sync' example· n-devices.
· Create a hash by each register based in time.
· First sync in batch using a file.· NSOutputstream to send 'ligth data'.
· Off and on send counters & timers using MCSessionSendDataReliable.
How far can I ...
How far can I ...
- RADIOUS2.
- DevRocket3
...
3 DeckRocket: https://github.com/jpsim/DeckRocket.
2 RADIOUS: https://itunes.apple.com/nz/app/radious-walkie-talkie/id738480541?mt=8.
Questions?
Thanks@patoroco