GKSession displayNameForPeer prevents releasing the session (iOS 4.0, 4.1) - ios

I can reliably crash the simulator with this when releasing the GKSession after calling displayNameForPeer for another peer (not self), and I'm not sure if it's something I'm doing wrong or if it's a bug with Apple's Gamekit framework (and whether I need to worry about it, since I only see the crash under 4.0 and 4.1, not 4.2+).
The output is:
found available peer; checking name and ID... m4, 26176566
*** -[GKSessionInternal lock]: message sent to deallocated instance 0x7508900
Here's the minimal reproducible code set -- note that another GKSession must be visible on the network (so that there's an available peer found to call displayNameForPeer on) to trigger the crash. Running this same code on another device but without the makeUnavailable and killSession calls is adequate.
- (void)viewDidLoad
{
[self createSession];
[self makeAvailable];
peerListAvailable = [[NSMutableArray alloc] initWithArray:[currentSession peersWithConnectionState:GKPeerStateAvailable]];
for (NSString *peer in peerListAvailable)
{
// this method guarantees the crash on session release
NSLog(#"found available peer; checking name and ID... %#, %#",[currentSession displayNameForPeer:peer], peer);
}
[peerListAvailable release];
peerListAvailable = nil;
[self makeUnavailable];
[self killSession];
[super viewDidLoad];
}
- (void) createSession
{
if (!currentSession)
{
currentSession = [[GKSession alloc] initWithSessionID:#"GKTester" displayName:nil sessionMode:GKSessionModePeer];
currentSession.delegate = self;
currentSession.disconnectTimeout = 30;
[currentSession setDataReceiveHandler: self withContext:nil];
}
}
-(void) killSession
{
if (currentSession)
{
[currentSession disconnectFromAllPeers];
[currentSession setDelegate:nil];
[currentSession setDataReceiveHandler:nil withContext:nil];
[currentSession release]; // crash occurs after this
currentSession = nil;
}
}
-(void) makeAvailable
{
while (currentSession && !currentSession.available)
{
[currentSession setAvailable:YES];
[NSThread sleepForTimeInterval:.5];
}
}
-(void) makeUnavailable
{
while (currentSession && currentSession.available)
{
[NSThread sleepForTimeInterval:.5];
[currentSession setAvailable:NO];
}
}

You have an over-release in your code:
[currentSession disconnectFromAllPeers];
[currentSession setDelegate:nil];
[currentSession setDataReceiveHandler:nil withContext:nil];
[currentSession release]; // This is an over-release
currentSession = nil; // You are trying to access a variable after it's been released
You should release the currentSession member variable in dealloc only, like so:
- (void)dealloc
{
[currentSession release];
[super dealloc];
}

Related

iOS Multipeer Connectivity didReceiveInvitationFromPeer did not

I know this question has been asked many times, but after reading each of them more than a few times, I still can't get my Multipeer Connectivity to work. I am sending but not receiving the invitation. Here is the code:
#implementation MPCManager
- (id)init {
self = [super init];
if (self) {
_myPeerID = nil;
_session = nil;
_browser = nil;
_advertiser = nil;
}
return self;
}
- (void)automaticBrowseAndAdvertiseWithName:(NSString *)displayName {
_myPeerID = [[MCPeerID alloc] initWithDisplayName:displayName];
_session = [[MCSession alloc] initWithPeer:_myPeerID];
_session.delegate = self;
_advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_myPeerID
discoveryInfo:nil
serviceType:#"trm-s"];
_advertiser.delegate = self;
[_advertiser startAdvertisingPeer];
_browser = [[MCNearbyServiceBrowser alloc] initWithPeer:_myPeerID
serviceType:#"trm-s"];
_browser.delegate = self;
[_browser startBrowsingForPeers];
}
- (void)session:(MCSession *)session
didReceiveCertificate:(NSArray *)certificate
fromPeer:(MCPeerID *)peerID
certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
didReceiveInvitationFromPeer:(MCPeerID *)peerID
withContext:(NSData *)context
invitationHandler:(void (^)(BOOL,
MCSession *))invitationHandler {
NSLog(#"This is NOT getting called");
}
- (void)browser:(MCNearbyServiceBrowser *)browser
didNotStartBrowsingForPeers:(NSError *)error {
NSLog(#"%#", [error localizedDescription]);
}
- (void)browser:(MCNearbyServiceBrowser *)browser
foundPeer:(MCPeerID *)peerID
withDiscoveryInfo:(NSDictionary *)info {
NSLog(#"This IS getting called");
}
- (void)invitePeer:(MCPeerID *)peerID {
NSLog(#"This IS getting called");
[_browser invitePeer:peerID toSession:_session withContext:nil timeout:30];
}
I am running it on two simulators, and it was working for some time, but stopped suddenly. Any ideas on how or where to look for the problem?
Make sure that you are serializing and reusing your MCPeerID objects whenever possible. Each time you call - (instancetype)initWithDisplayName:(NSString *)myDisplayName it returns a unique instance.
What often happens in a dev environment is that you end up with a flood of advertisers and browsers and a ton of ghost duplicates in the Bonjour advertising space. This can cause everything to just go wonky.
If you are using simulators then resetting them may help. On hardware you can restart or toggle airplane mode.
Take a look at this year's WWDC session on Multipeer named "Cross Platform Nearby Networking". It has some good best practices to follow that will help immensely.

NSOperation + setCompletionBlock

I have few different questions about NSOperation and NSOperationQueue and I know guys that yours answers will help me;
I have to load a big amount of images and I have created my own loader based on NSOperation, NSOperationQueue and NSURLConnection (asynchronous loading);
Questions:
If I set maxConcurrentOperationCount (for example 3) for queue (NSOperationQueue), does it mean that only 3 operations performed in the same time even queue has 100 operations?
When I set property maxConcurrentOperationCount for queue sometimes "setCompletionBlock" doesn't work and count (operationCount) only increases; Why?
MyLoader:
- (id)init
{
self = [super init];
if (self) {
_loadingFiles = [NSMutableDictionary new];
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 3;
_downloadQueue.name = #"LOADER QUEUE";
}
return self;
}
- (void)loadFile:(NSString *)fileServerUrl handler:(GetFileDataHandler)handler {
if (fileServerUrl.length == 0) {
return;
}
if ([_loadingFiles objectForKey:fileServerUrl] == nil) {
[_loadingFiles setObject:fileServerUrl forKey:fileServerUrl];
__weak NSMutableDictionary *_loadingFiles_ = _loadingFiles;
MyLoadOperation *operation = [MyLoadOperation new];
[operation fileServerUrl:fileServerUrl handler:^(NSData *fileData) {
[_loadingFiles_ removeObjectForKey:fileServerUrl];
if (fileData != nil) {
handler(fileData);
}
}];
[operation setQueuePriority:NSOperationQueuePriorityLow];
[_downloadQueue addOperation:operation];
__weak NSOperationQueue *_downloadQueue_ = _downloadQueue;
[operation setCompletionBlock:^{
NSLog(#"completion block :%i", _downloadQueue_.operationCount);
}];
}
}
MyOperation:
#interface MyLoadOperation()
#property (nonatomic, assign, getter=isOperationStarted) BOOL operationStarted;
#property(nonatomic, strong)NSString *fileServerUrl;
#property(nonatomic, copy)void (^OnFinishLoading)(NSData *);
#end
#implementation MyLoadOperation
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)fileServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler {
#autoreleasepool {
self.fileServerUrl = fileServerUrl;
[self setOnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
}
- (void)main {
#autoreleasepool {
[self stop];
}
}
- (void)start {
[self setOperationStarted:YES];
[self willChangeValueForKey:#"isFinished"];
_finished = NO;
[self didChangeValueForKey:#"isFinished"];
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
_executing = NO;
[self didChangeValueForKey:#"isFinished"];
}
else
{
[self willChangeValueForKey:#"isExecuting"];
_finished = NO;
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
- (void)cancel {
[self.connection cancel];
if ([self isExecuting])
{
[self stop];
}
[super cancel];
}
#pragma mark -NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self OnFinishLoading]) {
[self OnFinishLoading](_data);
}
if (![self isCancelled]) {
[self stop];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
;
if (![self isCancelled]) {
[self stop];
}
}
- (void)stop {
#try {
__weak MyLoadOperation *self_ = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self_ completeOperation];
});
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
- (void)completeOperation {
if (![self isOperationStarted]) return;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
You must start the connection in the Operation's start method, and not in fileServerUrl:handler:.
I would remove this method altogether, and only provide an init method with all required parameters where you can completely setup the operation. Then, in method start start the connection.
Additionally, it's not clear why you override main.
Modifying the state variables _executing and _finished could be more concise and more clear (you don't need to set them initially, since the are already initialized to NO). Only set them in the "final" method completeOperation including KVO notifications.
You also do not need a #try/#catch in stop, since function dispatch_async() does not throw Objective-C exceptions.
Your cancel method is not thread safe, and there are also a few other issues. I would suggest the following changes:
#implementation MyOperation {
BOOL _executing;
BOOL _finished;
NSError* _error; // remember the error
id _result; // the "result" of the connection, unless failed
completion_block_t _completionHandler; //(your own completion handler)
id _self; // strong reference to self
}
// Use the "main thread" as the "synchronization queue"
- (void) start
{
// Ensure start will be called only *once*:
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled && !_finished && !_executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
_self = self; // keep a strong reference to self in order to make
// the operation "immortal for the duration of the task
// Setup connection:
...
[self.connection start];
}
});
}
- (void) cancel
{
dispatch_async(dispatch_get_main_queue, ^{
[super cancel];
[self.connection cancel];
if (!_finished && !_executing) {
// if the op has been cancelled before we started the connection
// ensure the op will be orderly terminated:
self.error = [[NSError alloc] initWithDomain:#"MyOperation"
code:-1000
userInfo:#{NSLocalizedDescriptionKey: #"cancelled"}];
[self completeOperation];
}
});
}
- (void)completeOperation
{
[self willChangeValueForKey:#"isExecuting"];
self.isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = self.result;
NSError* error = self.error;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result, error);
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self onFinishLoading]) {
[self onFinishLoading](self.result);
}
[self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.error == nil) {
self.error = error;
}
[self completeOperation];
}
In answer to your questions:
Yes, a maxConcurrentOperationCount of three means that only three will run at a time. Doing network requests like this is perfect example of when you'd want to use maxConcurrentOperationCount, because failure to do so would result in too many network requests trying to run, most likely resulting in some of the connections failing when using a slower network connection.
The main issue here, though, is that you're calling your operation's fileServerUrl method (which is starting the connection) from MyLoader. You've disconnected the request from the operation's start (defeating the purpose of maxConcurrentCount of 3 and possibly confusing the state of the operation).
The start method should be initiating the connection (i.e. don't start the request until one of those three available concurrent operations is available). Furthermore, since you cannot pass the URL and the handler to the start method, you should move your logic that saves those values to a customized rendition of your init method.
There are other minor edits we might suggest to your operation (main not needed, operationStarted is a little redundant, simplify the _executing/_finished handling, etc.), but the starting of the connection in fileServerUrl rather than being initiated by the start method is the key issue.
Thus:
- (id)initWithServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
// do your saving of `fileServerURL` and `handler` here, e.g.
self.fileServerUrl = fileServerUrl;
self.OnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
}
return self;
}
- (void)startRequest {
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
- (void)start {
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self setOperationStarted:YES]; // personally, I'd retire this and just reference your `executing` flag, but I'll keep it here for compatibility with the rest of your code
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[self startRequest];
}
For the first question, the answer is yes, if set 3 as a max number of operations, only 3 can be running togheter.
The second is bit strange problem and I'm not totally sure that this answer will be correct. When you leave operations to an NSOperationQueue, you can't be sure on which thread they will be executed, this lead a huge problem with async connection. When you start an NSURLConnection as usual you receive the delegate callbacks without a problem, that is because the connection is running on a thread with a living run loop. If you start the connection on a secondary thread, callbacks will be called on that thread, but if you don't keep the run loop alive they will be never received.That's where probably my answer isn't correct, GCD should take care of living run loops, because GCD queues runs on living threads. But if not, the problem could be that operations are started on a different thread, the start method is called, but the callbacks are never called. Try to check if the thread is always the main thread.

How to fix GADDelegateManager didYouNilOutYourDelegate:selector: error

I am using Admob in my app.
I am getting following error:
[GADDelegateManager didYouNilOutYourDelegate:selector:] at GADDelegateManager.m:48
I am releasing my Admob Banner object in dealloc.
Can anyone tell what can be the possible fix for this error.
-(void)displayAds {
self.aBannerView.adUnitID = AdmobPublisherID;
[self.aBannerView setDelegate:self];
[self.aBannerView setRootViewController:roorViewController];
if (isAdLoaded) {
[self.view addSubview:self.aBannerView];
} else {
// Initiate a generic request to load it with an ad.
[self.view addSubview:self.aBannerView];
[self.aBannerView loadRequest:[self createRequest]];
isAdLoaded = YES;
}
}
#pragma mark GADRequest generation
- (GADRequest *)createRequest {
GADRequest *request = [GADRequest request];
request.testDevices = [NSArray arrayWithObjects: nil];
return request;
}
- (void)adViewDidReceiveAd:(GADBannerView *)adView {
NSLog(#"Received ad successfully popup");
if(self.aBannerView !=nil) {
self.aBannerView.hidden = NO;
}
}
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error {
NSLog(#"Failed to receive ad with error: %#", [error localizedFailureReason]);
[self.aBannerView removeFromSuperview];
[self showInhouseAd];
}
- (void)adViewWillLeaveApplication:(GADBannerView *)bannerView {
}
- (void)dealloc {
[aBannerView release];
}
Thanks,
The selector name seems rather explanatory to me. Try setting the delegate to nil before releasing the banner view:
- (void)dealloc {
[aBannerView setDelegate:nil];
[aBannerView release];
}
As the error actually says [GADDelegateManager didYouNilOutYourDelegate:selector:]. I would actually try
- (void)dealloc
{
aBannerView.delegate = nil;
[aBannerView release];
}
That seems like your problem as indicated by the error message.

Using RestKit, use block to load object, when and how to cancel the request?

[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"app/site_pattern" usingBlock:^(RKObjectLoader* loader) {
[loader setObjectMapping:clientMappring];
loader.delegate = self;
shopLoader = loader;
}];
Above, I use the block function to load some data in my app, but when I pop this viewcontroller, I don't know when and how to cancel this request .
Any idea?
- (void)showSelectShop
{
SelectShopViewController * selectShopViewController = [[SelectShopViewController alloc] initWithNibName:#"SelectShopViewController" bundle:nil];
[self.navigationController pushViewController:selectShopViewController animated:YES];
}
More:
I try to cancel it in the viewDidUnload
- (void)viewDidUnload
{
[super viewDidUnload];
[shopLoader cancel];
}
But it didn't work. I still getting error.
I solved this by adding
- (void)viewWillDisappear:(BOOL)animated
{
[shopLoader cancel];
shopLoader.delegate = nil;
shopLoader = nil;
}
I still want to know if I don't want to cancel this request in viewWillDisappear, which function do those lines should be written in?

A very strange crash. How do I fix it?

I have a very strange crash. I don't release the data variable.
My log is:
[NSConcreteData retain]: message sent to deallocated instance 0xa446a00
and code
- (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data
{
if (!isThreadCreated)
{
self.isThreadCreated = YES;
[NSThread detachNewThreadSelector:#selector(createNewEncodingThread:) toTarget:self withObject:nil];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[[AudiobookAppDelegate delegate].dateManager saveTimeLastActivities];
if(trackFile && !isFotrbidGetNewData)
{
if (data)
{
self.downloadedLengthOfFile+= data.length;
[encodingArray addObject:data];
}
}
}
encodingArray got released somehow, so relevant code.
Add this line of code before adding object.
if(!encodingArray) {
encodingArray = [[NSMutableArray alloc] init];
}
[encodingArray addObject:data];
Hope this will solve your problem.

Resources