Inviting Peer to MCSEssion - multipeer-connectivity

This is my first time posting on stackoverflow, and I'm aware of the strict posting requirements. Please let me know if I'm not following any of the guidelines.
I'm currently writing an IOS (8.4) application in Xcode, using Objective-C. The goal is to use MCSessions in order to stream data between IOS devices. I'm currently struggling with the concept of sessions, despite reading numerous posts here and elsewhere that attempt to clarify the topic. Here are the resources I'm already aware of:
https://developer.apple.com/videos/play/wwdc2013-708/
https://medium.com/#phoenixy/using-multipeer-connectivity-120abacb9db
Here's my current understanding: At the most basic level, you have an advertiser, and a browser. The advertiser has a local session, which allows them to "advertise". When the browser sees an advertiser, the browser sends an invite to the advertiser to his (the browser's) local MCSession. Assuming this is all correct, here's where I'm getting confused. The advertiser can accept the invite, and in the process, passes his local session to the invitationHandler.
I have implemented the following logic in code, as shown below. However, in tracing MCSession state changes for both the advertiser and browser, a connection is attempted, but the final state is always didNotNonnect.
Code for sending invitation (Browser):
[self.broadcasterBrowser invitePeer:[broadcasterPeerIDs objectAtIndex:indexPath.row]
toSession: self.appDelegate.mpcHandler.session withContext:nil timeout:30.0 ];
Code for accepting invitation (Advertiser):
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
ArrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:peerID.displayName
message:#"Would like to create a session with you"
delegate:self
cancelButtonTitle:#"Decline"
otherButtonTitles:#"Accept", nil];
[alertView show];
if (alertViewResult)
{
void (^invitationHandler)(BOOL, MCSession *) = [ArrayInvitationHandler objectAtIndex:0];
invitationHandler(YES, self.appDelegate.mpcHandler.session);
}
}
Any help is greatly appreciated!
Austin

I ran into a similar problem trying to use MPC. I created a custom class to handle all of the MPC connectivity details. While testing though, every time my advertiser would accept the invite, it would complain about wrong connection data and fail. I discovered that the problem was that I was vending out the MCPeerID object for my device from a class variable I created as below:
static var peerObject : MCPeerID {
return MCPeerID(displayName: deviceNameString)
}
lazy var sessionIVar = MCSession (peer: MyConnectivityClass.peerObject)
func startAdvertisingForConnectivity () {
advertiserService = MCNearbyServiceAdvertiser (peer: MyConnectivityClass.peerObject, discoveryInfo: nil, serviceType: "my-multipeer-connectivity-service-identifier")
}
Then when I got an invitation I would initialize a MCSession object using the "peerObject" computed property and return it in the invitation handler, like this:
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: #escaping (Bool, MCSession?) -> Swift.Void) {
invitationHandler(true, sessionIVar)
}
I assumed that each time I called for "MyConnectivityClass.peerObject" it would give back an identical peerID because I was always initializing it with the same display name. It turns out that's not true. So when I was advertising I was using one peerID object and then when I was responding to the invitation, I was responding with a MCSession object that contained an entirely different peerID.
So the solution was to change the "MyConnectivityClass.peerObject" computed class property to a constant, or an Ivar, in my connection handler class. Like this:
let peerObject : MCPeerID = MCPeerID(displayName: deviceNameString)
Then the rest of the code just worked because no matter how many times I called for the MCPeerID object, it was always the same. Looking back I don't know why I started out with it the way I did. :-)
Then in my connectivity class I archived and stored the MCPeerID objects for both the browser and the advertiser so that I could have the advertiser automatically accept the invitation for trusted MCPeerIDs. That's not possible if you create the MCPeerID object each time you use it, even if you always initialize it with the same DisplayName.

Related

WidgetKit not calling urlSessionDidFinishEvents

I'm trying to implement downloading timeline data in a widget and so I created a background URLSession with a corresponding data task to download the JSON:
let session = URLSession(
configuration: .background(withIdentifier: identifier),
delegate: self,
delegateQueue: nil
)
let request = URLRequest(url: ...)
session.dataTask(with: request).resume()
On my widget I then added the onBackgroundURLSessionEvents to store the completion handler, as per the Apple docs:
.onBackgroundURLSessionEvents { identifier in
return SessionCache.shared.isValid(for: identifier)
} _: { identifier, completion in
let data = SessionCache.shared.sessionData(for: identifier)
data.sessionCompletion = completion
}
However, neither the urlSessionDidFinishEvents(forBackgroundURLSession:) nor the onBackgroundURLSessionEvents methods are called. When the network download completes, it just calls the normal urlSession(_:task:didCompleteWithError:) method.
I'm clearly missing something here, but I'm just not seeing what.
Apple's documentation isn't 100% clear on this, but you need to use URLSession.downloadTask instead of dataTask for background sessions. URLSessionDataTasks deliver bytes to those specific delegate methods in your in-memory process. Background download & upload tasks are handed off to nsurlsessiond and only delivered back to your app when they are fully resolved.

ReactiveCocoa binding "networkActivityIndicator" Crushes

I have this code:
RAC(self.viewModel , password) = self.signupCell.passwordTextField.rac_textSignal;
RAC(self.viewModel , userName) = self.signupCell.usernameTextField.rac_textSignal;
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = self.viewModel.executeRegister.executing;
At my LogIn page.
At first is runs perfect, But it user Logout and gets to the register page once again, the app crushes at the line:
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = self.viewModel.executeRegister.executing;
With Error:
'Signal name: is already bound to key path "networkActivityIndicatorVisible" on object , adding signal name: is undefined behavior'
I'm guessing it has something to do with subscribing to UIApplication events. But I'm not sure what else can i do beside sending subscriber completed as so:
[subscriber sendCompleted]
Any one had the same problem?
thanks.
EDIT
With the help of #erikprice and #powerj1984 I found a solution:
RAC([UIApplication sharedApplication], networkActivityIndicatorVisible) = [self.viewModel.executeRegister.executing takeUntilBlock:^BOOL(id x) {
return _viewShowing;
}];
The "_viewShowing" veritable is setted to YES on ViewWillAppear, And to NO on ViewWillDisapear.
This is not the best coding.. So if anyone has a better option i would be happy to use it.
Thanks.
That error message means that you're trying to call RAC(UIApplication.sharedApplication, networkActivityIndicatorVisible) more than once. Make sure you only make that call on that specific property of that specific object one time, ever. (Or at least until such time as you dispose of the subscription, as #powerj1984 suggests.)

How would you write fetching a collection the "Reactive Cocoa" way?

The client I'm building is using Reactive Cocoa with Octokit and so far it has been going very well. However now I'm at a point where I want to fetch a collection of repositories and am having trouble wrapping my head around doing this the "RAC way"
// fire this when an authenticated client is set
[[RACAbleWithStart([GHDataStore sharedStore], client)
filter:^BOOL (OCTClient *client) {
return client != nil && client.authenticated;
}]
subscribeNext:^(OCTClient *client) {
[[[client fetchUserRepositories] deliverOn:RACScheduler.mainThreadScheduler]
subscribeNext:^(OCTRepository *fetchedRepo) {
NSLog(#" Received new repo: %#",fetchedRepo.name);
}
error:^(NSError *error) {
NSLog(#"Error fetching repos: %#",error.localizedDescription);
}];
} completed:^{
NSLog(#"Completed fetching repos");
}];
I originally assumed that -subscribeNext: would pass an NSArray, but now understand that it sends the message every "next" object returned, which in this case is an OCTRepository.
Now I could do something like this:
NSMutableArray *repos = [NSMutableArray array];
// most of that code above
subscribeNext:^(OCTRepository *fetchedRepo) {
[repos addObject:fetchedRepo];
}
// the rest of the code above
Sure, this works, but it doesn't seem to follow the functional principles that RAC enables. I'm really trying to stick to conventions here. Any light on capabilities of RAC/Octokit are greatly appreciated!
It largely depends on what you want to do with the repositories afterward. It seems like you want to do something once you have all the repositories, so I'll set up an example that does that.
// Watch for the client to change
RAC(self.repositories) = [[[[[RACAbleWithStart([GHDataStore sharedStore], client)
// Ignore clients that aren't authenticated
filter:^ BOOL (OCTClient *client) {
return client != nil && client.authenticated;
}]
// For each client, execute the block. Returns a signal that sends a signal
// to fetch the user repositories whenever a new client comes in. A signal of
// of signals is often used to do some work in response to some other work.
// Often times, you'd want to use `-flattenMap:`, but we're using `-map:` with
// `-switchToLatest` so the resultant signal will only send repositories for
// the most recent client.
map:^(OCTClient *client) {
// -collect will send a single value--an NSArray with all of the values
// that were send on the original signal.
return [[client fetchUserRepositories] collect];
}]
// Switch to the latest signal that was returned from the map block.
switchToLatest]
// Execute a block when an error occurs, but don't alter the values sent on
// the original signal.
doError:^(NSError *error) {
NSLog(#"Error fetching repos: %#",error.localizedDescription);
}]
deliverOn:RACScheduler.mainThreadScheduler];
Now self.repositories will change (and fire a KVO notification) whenever the repositories are updated from the client.
A couple things to note about this:
It's best to avoid subscribeNext: whenever possible. Using it steps outside of the functional paradigm (as do doNext: and doError:, but they're also helpful tools at times). In general, you want to think about how you can transform the signal into something that does what you want.
If you want to chain one or more pieces of work together, you often want to use flattenMap:. More generally, you want to start thinking about signals of signals--signals that send other signals that represent the other work.
You often want to wait as long as possible to move work back to the main thread.
When thinking through a problem, it's sometimes valuable to start by writing out each individual signal to think about a) what you have, b) what you want, and c) how to get from one to the other.
EDIT: Updated to address #JustinSpahrSummers' comment below.
There is a -collect operator that should do exactly what you're looking for.
// Collect all receiver's `next`s into a NSArray. nil values will be converted
// to NSNull.
//
// This corresponds to the `ToArray` method in Rx.
//
// Returns a signal which sends a single NSArray when the receiver completes
// successfully.
- (RACSignal *)collect;

Can we fire an event when ever there is Incoming and Outgoing call in iphone?

Can I fire an event when ever there is Incoming and Outgoing call ends on iphone? Axample of an event is calling a webservice .
Yes you can, but not necessarily immediately.
There is a framework, called the CoreTelephony framework, which has a CTCallCenter class. One of the properties on this class is the callEventHandler property. This is a block that gets fired when then state of a phone call changes. For example:
CTCallCenter *callCenter = ...; // get a CallCenter somehow; most likely as a global object or something similar?
[callCenter setCallEventHandler:^(CTCall *call) {
if ([[call callState] isEqual:CTCallStateConnected]) {
//this call has just connected
} else if ([[call callState] isEqual:CTCallStateDisconnected]) {
//this call has just ended (dropped/hung up/etc)
}
}];
That's really about all you can do with this. You don't get access to any phone numbers. The only other useful tidbit of information is an identifier property on CTCall so you uniquely identify a CTCall object.
CAUTION:
This event handler is not invoked unless your app is in the foreground! If you make and receive calls while the app is backgrounded, the event handler will not fire until your app becomes active again, at which point (according to the documentation linked to above) the event handler will get invoked once for each call that has changed state while the app was in the background.
No but you do get callbacks into the app when those events happen.
-(void)applicationWillResignActive:(UIApplication *)application{
//our app is going to loose focus since thier is an incoming call
[self pauseApp];
}
-(void)applicationDidBecomeActive:(UIApplication *)application{
//the user declined the call and is returning to our app
[self resumeApp];
}
No. As of the current SDK, this is not possible. Apple does not allow apps to have such hooks.

How is it better to wait an asynchronous method to be finished in iPhone app?

everybody.
I want to understand, how i shoud procceed situations when an asynchronous method has "didFinish:#selector(SEL)" parameter.
My code example is:
//
// Authentication check
- ( void )authenticationSuccess: ( GDataServiceTicket* ) ticket
authenticatedWithError: ( NSError* ) error {
if ( error == nil )
{
NSLog( #"authentication success" );
}
else
{
NSLog( #"authentication error" );
}
}
//
- ( void ) fetchFeedOfSpreadsheets {
//create and authenticate to a google spreadsheet service
if ( !(mService) )
{
GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
[mService autorelease];
mService = [service retain];
}
// check autentication success ( invoke "authenticationSuccess" method for debug success & error )
[mService authenticateWithDelegate: self
didAuthenticateSelector:#selector(authenticationSuccess:
authenticatedWithError:) ];
// HERE I WANT TO MAKE A PAUSE AND WHAIT THE RESULT, EITHER I AUTHENTICATED OR NOT
// AND MAKE AN "IF" STATEMENT TO CONTINTUE WORKING ON SERVER, OR RETURN ERROR
//fetch retrieves the feed of spreadsheets entries
NSURL *feedURL = [ NSURL URLWithString: kGDataGoogleSpreadsheetsPrivateFullFeed ];
GDataServiceTicket *ticket;
ticket = [mService fetchFeedWithURL: feedURL
delegate: self
didFinishSelector: #selector(spreadsheetsTicket:finishedWithFeed:
error: ) ];
// HERE I WANT TO WAIT SECOND TIME. I WANT "spreadsheetsTicket:
// finishedWithFeed:error:" TO PROCCEED ERROR AND PUT A FEED IN SOME NSARRAY OBJECT
// AND AFTER THAT I WANT TO WORK WITH THAT NSARRAY RIGHT HERE
}
I's clear, that i can push the code i want into the end of "authenticationSuccess" method section, but it's also clear, that it's a wrong a way to solve the proble. There a number of situations like this, where i call an asynchronous method with a selector parameter, and i want to find a solution providing me a flexible code writing.
Thanks in advance.
It's a standard practice in Objective-C to put the code to be executed after the authentication in the authenticationSucess: method. You might not like it, but that is life.
Many people had the same complaint as you, so
on iOS 4 and later, there's something called blocks which allow you to write the code to be executed after the authentication in the method which initiates the authentication, as in
[mService authenticateAndExecute:^{
code to be executed when successfully authenticated ;
} whenError:^{
code to be executed when authentication failed;
} ];
But in this case you need to modify the API, which is possible by using categories. See this blog post by Mike Ash. He has many other posts on blocks on the same blog, which are also very instructive.
If you're going to use a library that works asynchronously (and therefore doesn't block your UI), you should have a good reason for trying to force it to work synchronously.
You should be checking for an authentication error at the end of your authenticationSuccess:authenticatedWithError: method, and calling the next request from there if there's a success. Similarly, in your spreadsheetsTicket:finishedWithFeed:error: check for an error, and continuing processing if there isn't one. It might be a better design to do that continued work in a separate method, but that's up to you.
Is there a specific reason you want to use the GData API in a synchronous fashion?