I'm using CMMotionManager in my app so I can get the device motion info. I have these two methods:
- (void)startDeviceMotion {
motionManager = [[CMMotionManager alloc] init];
motionManager.showsDeviceMovementDisplay = YES;
motionManager.deviceMotionUpdateInterval = 1.0 / 120.0;
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical];
}
Second is:
- (void)stopDeviceMotion {
[motionManager stopDeviceMotionUpdates];
[motionManager release];
motionManager = nil;
}
They're launched when the app starts and when the app finishes respectively. My problem is now multitasking. If I get my problem into background and then I bring it to foreground again, I get a message (with NSZombie activated) telling me that a [CMMotionManager retain] message is being sent to a deallocated instance.
Where could my problem be?
Thanks!
Try using Jonathan's suggestion here. Basically, to make sure only one instance of your CMMotionManager is created, put your motionManager in AppDelegate and retrieve it by this method wherever you want to use your motionManager.
-(CMMotionManager *)motionManager
{
CMMotionManager *motionManager = nil;
id appDelegate = [UIApplication sharedApplication].delegate;
if ([appDelegate respondsToSelector:#selector(motionManager)]) {
motionManager = [appDelegate motionManager];
}
return motionManager;
}
Let me know if this works for you.
Related
I'm following the RayWenderlich GameCenter Tutorial: Raywend GameCenter Tutorial but I'm trying to convert it into Swift 3.0. Basically all it does is authenticate the local player, open the matchmaking interface, and then it should call the matchmakerViewController function once two players find each other. The player is successfully authenticated, and the matchmakerViewController is presented, and the players find each-other. However, after displaying the opponent that it found and putting the blue checkmark next to their name, the app calls matchmakerViewControllerWasCancelled instead of matchmakerViewController(viewController: GKMatchmakerViewController,didFindMatch match: GKMatch) This is weird to me since I'm pretty sure that method is only called based on user interaction (it's not calling the error method). Running the objective-c version of the project works, but my swift conversion has this problem. Here are the two versions of the findMatchWithMinPlayers() method. I'm assuming I converted something wrong here:
Swift
func findMatchWithMinPlayers(minPlayers: Int, maxPlayers: Int, viewController: UIViewController, delegate: GameKitHelperDelegate) {
if !enableGameCenter {
return
}
self.matchStarted = false
self.match = nil
self.delegate = delegate
viewController.dismiss(animated: false, completion: { _ in })
var request = GKMatchRequest()
request.minPlayers = minPlayers
request.maxPlayers = maxPlayers
var mmvc = GKMatchmakerViewController(matchRequest: request)
mmvc?.matchmakerDelegate = self
viewController.present(mmvc!, animated: true, completion: { _ in })
}
Objective-C
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GameKitHelperDelegate>)delegate {
if (!_enableGameCenter) return;
_matchStarted = NO;
self.match = nil;
_delegate = delegate;
[viewController dismissViewControllerAnimated:NO completion:nil];
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
GKMatchmakerViewController *mmvc =
[[GKMatchmakerViewController alloc] initWithMatchRequest:request];
mmvc.matchmakerDelegate = self;
[viewController presentViewController:mmvc animated:YES completion:nil];
}
The only other thing I can thing of that may have been different was this:
Objective-C
+ (instancetype)sharedGameKitHelper
{
static GameKitHelper *sharedGameKitHelper;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedGameKitHelper = [[GameKitHelper alloc] init];
});
return sharedGameKitHelper;
}
Swift
And instead I just used a global var called sharedGameKitHelper and anytime in the code where they used [GameKitHelper sharedGameKitHelper] I used sharedGameKitHelper. They described the purpose of that method as creating a "singleton object" of the GameKitHelper class.
If you find nothing wrong with those, here's a full Pastebin of both of my "GameKitHelper" classes, which contain all the GameKit code.
objectice-c swift I've checked every method multiple times and restarted the project from scratch, so I must have some hole in my knowledge of objective-c conversions. Thanks for any help you can give!
I'm trying to see if there are any ongoing calls, but I'm having trouble with keeping an instance of CTCallCenter as a property. Here's basically what I'm doing now that I'm debugging (everything is in MyClass):
-(void)checkForCurrentCalls
{
CTCallCenter *newCenter = [[CTCallCenter alloc] init];
if (newCenter.currentCalls != nil)
{
NSLog(#"completely new call center says calls in progress:");
for (CTCall* call in newCenter.currentCalls) {
NSLog(call.callState);
}
}
if (self.callCenter.currentCalls != nil) {
NSLog(#"property-call center says calls in progress:");
for (CTCall* call in self.callCenter.currentCalls) {
NSLog(call.callState);
}
}
}
My self.callCenter is a #property (nonatomic, strong) CTCallCenter *callCenter;. It has synthesized setter and getter. It's initialized in MyClass' init method:
- (id)init
{
self = [super init];
if (self) {
self.callCenter = [[CTCallCenter alloc] init];
}
return self;
}
If I call another of my methods before checkForCurrentCalls, then my self.callCenter.currentCalls stops updating the way it should. More precisely, the phonecalls I make (to myself) just keep piling on, so that if I've dialed and hung up three phonecalls I get printouts of three CTCalls being in the "dialing" state. The newCenter works as expected.
All I have to do to break it is call this:
- (void)trackCallStateChanges
{
self.callCenter.callEventHandler = ^(CTCall *call)
{
};
}
I have come across answers that say that CTCallCenter has to be alloc-init'd on the main queue. I have since then taken care to only call my own init on the main queue, using dispatch_async from my app delegate:
dispatch_async(dispatch_get_main_queue(), ^{
self.myClass = [[MyClass alloc] init];
[self.myClass trackCallStateChanges];
}
My checkForCurrentCalls are later called in applicationWillEnterForeground.
I don't understand why just setting the callEventHandler-block breaks it.
Everything is tested on iphone 5 ios 7.1.2.
This has been reproduced in a new project. Filed a bug report on it.
So I have been designing an iOS game (in Sprite Kit) for a while now and just recently added a CMMotionManager to my project so that my character would be controlled by the tilt of the screen. It took some fiddling but I got it to work, and here's how I've implemented it:
In my initWithSize method I have
self.motionManager = [[CMMotionManager alloc] init];
self.referenceAttitude = nil;
Then, I've written methods beginMotionSensing and switchToLiveSensing. The first is intended to get an idea of how the phone is being held and the second starts the game itself. Using SKActions, I call the first method, wait a second, and call the second. They look something like this:
-(void)beginMotionSensing{
CMDeviceMotion *deviceMotion = self.motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
self.referenceAttitude = attitude;
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
[self setNull:motion];
}];
self.motionManager.deviceMotionUpdateInterval = .3;
}
and,
-(void)switchToLiveSensing{
gameIsLive = YES;
[self.motionManager stopDeviceMotionUpdates];
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
[self captureRoll:motion];
}];
self.motionManager.deviceMotionUpdateInterval = .02;
}
So I had all of this set up and it was working great, until I added the ability to play the game more than once. Before this, I would simply have to close out of the app and quit the game when I lost. Obviously this became annoying so I added a "game over" scene, with the ability to go back and play the game scene again. The game scene is presented again like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKTransition *transition = [SKTransition fadeWithColor:[UIColor colorWithRed:147.0/255.0 green:213.0/255.0 blue:216.0/255.0 alpha:1.0] duration:2.0f];
MyScene *gameScene = [MyScene alloc];
gameScene = [gameScene initWithSize:self.frame.size passedInFuel:100];
[self.view presentScene:gameScene transition:transition];
}
I had remembered someone saying something in a previous post about a singleton - the comment was directed at someone implementing a CMMotionManager. They had said something along the lines of, "you should probably make it accessible through a singleton if you are going to initialize your class more than once." Well, sure enough, that's what I'm doing now. I had forgotten about this until I was playing the game and noticed that suddenly the phone seemed to ignore my tilt and went along with it's own business, killing my character in the process. It hasn't happened since, but is clearly something that needs to be fixed.
Could someone help me implement this so-called "singleton"?
I'm not convinced this is the problem you had, as the CMMotionManager object should be released anyway when your SKScene goes away. Can you duplicate the problem by starting the game again quickly after it ends?
You could probably do it by adding a class method in your main view/navigation controller (which ever one stays around longest) to create a singleton and then make it available to your SKScene object.
e.g.
+ (CMMotionManager *)sharedInstance
{
static CMMotionManager *sharedCMMM = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCMMM = [[CMMotionManager alloc] init];
// Do any other initialisation stuff here
});
return sharedCMMM;
}
I'm new to iOS development.
I follow the tutorial from Ray Wenderlich to create a little location based AR app. However, the tutorial uses an AR Toolkit which has not been updated for a while. The UIAccelerometer it uses has already been deprecated since iOS 5, so when I try to run it on my iPhone (iOS 7.0.4), the Xcode says that there are 3 warnings, and all of them are caused by UIAccelerometer.
The result it leads to is that all the marks stay at the center of the screen one above another, and the tilt does not work at all.
According to my research, I guess what I need to do is to use CMMotionManager instead of UIAccelerometer, but as I said before, I'm totally new to iOS development and have no idea how to replace it.
Here is the source code. I add some little functions such that you can manually add locations that are not in the Google database, but I don't think it is these functions that result in the problem.
Thanks for you help in advance!
Try this link: https://www.inkling.com/read/learning-ios-programming-alasdair-allan-2nd/chapter-9/the-core-motion-framework
I'm learning a few tidbits that translate some-what with the UIAccelerometer
i.e.
[self setAccelometerManager [UIAccelerometer sharedAccelerometer]];
could become
[self.motionManager = [[CMMotionManager alloc] init];
Setting manual update intervals like
[[self accelerometerManager] setUpdateInterval: 0.25];
you can have
self.motionManager.accelerometerUpdateInterval = 0.25;
and releasing the delegate
self.accelerometerManager.delegate = nil;
would now be
[self.motionManager stopDeviceMotionUpdates];
Also from the link, I ended up doing something like this:
motionManager = [[CMMotionManager alloc] init];
motionManager.accelerometerUpdateInterval = 1.0/10.0; // Update at 10Hz
if (motionManager.accelerometerAvailable) {
queue = [NSOperationQueue currentQueue];
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
switch (currentOrientation) {
case UIDeviceOrientationLandscapeLeft:
viewAngle = atan2(accelerometerData.acceleration.x, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationLandscapeRight:
viewAngle = atan2(-accelerometerData.acceleration.x, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationPortrait:
viewAngle = atan2(accelerometerData.acceleration.y, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationPortraitUpsideDown:
viewAngle = atan2(-accelerometerData.acceleration.y, accelerometerData.acceleration.z);
break;
default:
break;
}
[self updateCenterCoordinate];
}];
}
I'm developing a large scale application for iOS 5 using ARC in xcode. The system seems to work well except for when I'm trying to deallocate one of my interfaces. I'm using a framework called WhirlyGlobe to create a 3D interactive globe in the first view controller.
When I switch view controllers (between the 4 I have), I notice that the memory being used for the view controller with the globe isn't being released. All the other view controllers (only using simple views and images) release their memory fine - But the globe stays resident, or so it seems. When navigating back to the globe, I get almost a 10mb jump in memory due to 1mb allocations in "glsmLoadTextureLevelBuffer".
To get on with my question - Is there anything more I can do, with ARC active, to help release my objects? I've noticed my viewDidUnload and dealloc methods are not being called at all, and that the only way I can get anything to fire is using viewDidDisappear (which is not ideal obviously) - See below:
- (void)clear
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (self.layerThread)
{
[self.layerThread cancel];
while (!self.layerThread.isFinished)
[NSThread sleepForTimeInterval:0.001];
}
self.glView = nil;
self.sceneRenderer = nil;
if (theScene)
{
delete theScene;
theScene = NULL;
}
self.theView = nil;
self.texGroup = nil;
self.layerThread = nil;
self.earthLayer = nil;
self.vectorLayer = nil;
self.labelLayer = nil;
self.interactLayer = nil;
self.pinchDelegate = nil;
self.panDelegate = nil;
self.tapDelegate = nil;
self.longPressDelegate = nil;
self.rotateDelegate = nil;
}
- (void)viewDidDisappear:(BOOL)animated {
NSLog(#"dealloc - viewDidDisappear");
[self clear];
}
I'm setting everything I no longer need to nil. Is this the best practise?
The globe setup code:
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Set up an OpenGL ES view and renderer
EAGLView *ev = [[EAGLView alloc] initWithFrame:CGRectMake(0, 0, 824, self.view.frame.size.height)];
self.glView = ev;
self.sceneRenderer = [[SceneRendererES1 alloc] init];
UIColor *whiteC = [UIColor whiteColor];
[sceneRenderer setClearColor:whiteC];
glView.renderer = sceneRenderer;
glView.frameInterval = 2; // 60 fps (2)
[self.view addSubview:glView];
self.view.backgroundColor = [UIColor blackColor];
self.view.opaque = YES;
self.view.autoresizesSubviews = YES;
//glView.frame = self.view.bounds;
glView.frame = CGRectMake(275, GLOBE_HEIGHT_FIX, 768, SCREEN_HEIGHT+STATUS_BAR_HEIGHT); // was 260 x
glView.backgroundColor = [UIColor whiteColor];
self.view.backgroundColor = [UIColor whiteColor]; // red for debug
// Create the textures and geometry, but in the right GL context
[sceneRenderer useContext];
self.texGroup = [[TextureGroup alloc] initWithInfo:[[NSBundle mainBundle] pathForResource:#"bdGlobe_info" ofType:#"plist"]];
// Need an empty scene and view
theScene = new WhirlyGlobe::GlobeScene(4*texGroup.numX,4*texGroup.numY);
self.theView = [[WhirlyGlobeView alloc] init];
[theView setFarPlane:5.0];
[theView setHeightAboveGlobe:GLOBE_HEIGHT_VIEW];
if (globeShouldAnimate) glView.alpha = 1.0;
// Need a layer thread to manage the layers
self.layerThread = [[WhirlyGlobeLayerThread alloc] initWithScene:theScene];
// Earth layer on the bottom
self.earthLayer = [[SphericalEarthLayer alloc] initWithTexGroup:texGroup];
[self.layerThread addLayer:earthLayer];
// Set up the vector layer where all our outlines will go
self.vectorLayer = [[VectorLayer alloc] init];
[self.layerThread addLayer:vectorLayer];
// General purpose label layer.
self.labelLayer = [[LabelLayer alloc] init];
[self.layerThread addLayer:labelLayer];
self.interactLayer = [[InteractionLayer alloc] initWithVectorLayer:self.vectorLayer labelLayer:labelLayer globeView:self.theView
countryShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_0_map_subunits" ofType:#"shp"]
oceanShape:[[NSBundle mainBundle] pathForResource:#"10m_geography_marine_polys" ofType:#"shp"]
regionShape:[[NSBundle mainBundle] pathForResource:#"10m_admin_1_states_provinces_shp" ofType:#"shp"]];
self.interactLayer.maxEdgeLen = [self.earthLayer smallestTesselation]/10.0;
[self.layerThread addLayer:interactLayer];
// Give the renderer what it needs
sceneRenderer.scene = theScene;
sceneRenderer.view = theView;
// Wire up the gesture recognizers
self.panDelegate = [PanDelegateFixed panDelegateForView:glView globeView:theView];
self.tapDelegate = [WhirlyGlobeTapDelegate tapDelegateForView:glView globeView:theView];
self.longPressDelegate = [WhirlyGlobeLongPressDelegate longPressDelegateForView:glView globeView:theView];
// Kick off the layer thread
// This will start loading things
[self.layerThread start];
You can use the allocations instrument for this. Using heap shot, you can mark the heap at various points in the lifetime of your application and compare the object graph that constitutes the current allocations in memory at the point of each snapshot. That should help you narrow down what's being retained and by whom.
This is anecdotal, but COULD be a similar situation.
I just had a situation where my objects were not getting released ever, in ARC, even though they were accessed in a static way (e.g., [SingletonThing instance].thing) because the autorelease pool wasn't draining. The reason for this was a bizarre endless loop that runs on its own thread. Putting a separate #autorelease block for the enclosing code.
On the other hand, even if one of your (or the libs) object has the UIView as a subview, I think your UIViewController will never viewDidUnload and therefore never dealloc. I have to check this empirically.
make sure your -(void)viewDidDisappear:(BOOL)animated is invoked.
if you use
[self.view addSubview:yourViewController.view];
and
[yourViewController.view removeFromSuperview];
then viewDidDisappear: and viewDidAppear: will not be invoked
these callback will only be invoked
when you use presentViewController: in IOS 5
or presentModalViewController:
and dismissViewControllerAnimated: in IOS 5
or dismissModalViewControllerAnimated:
or use UINavigationController to present and dismiss your viewController
I found the problem after using heap shots (thanks to Mark Adams) - Turns out I wasn't making a couple of delegates weak entities, so they weren't being released when changing the view controller. Default strong delegates stay resident.
Thanks to all the suggestions, they all helped point me in the right direction :)