I use ZXing for an app, this is mainly the same code than the ZXing original code except that I allow to scan several time in a row (ie., the ZXingWidgetController is not necesseraly dismissed as soon as something is detected).
I experience a long long freeze (sometimes it never ends) when I press the dismiss button that call
- (void)cancelled {
// if (!self.isStatusBarHidden) {
// [[UIApplication sharedApplication] setStatusBarHidden:NO];
// }
[self stopCapture];
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}
with
- (void)stopCapture {
decoding = NO;
#if HAS_AVFF
if([captureSession isRunning])[captureSession stopRunning];
AVCaptureInput* input = [captureSession.inputs objectAtIndex:0];
[captureSession removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[captureSession.outputs objectAtIndex:0];
[captureSession removeOutput:output];
[self.prevLayer removeFromSuperlayer];
/*
// heebee jeebees here ... is iOS still writing into the layer?
if (self.prevLayer) {
layer.session = nil;
AVCaptureVideoPreviewLayer* layer = prevLayer;
[self.prevLayer retain];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12000000000), dispatch_get_main_queue(), ^{
[layer release];
});
}
*/
self.prevLayer = nil;
self.captureSession = nil;
#endif
}
(please notice that the dismissModalViewController that remove the view is within the delegate method)
I experience the freeze only while dismissing only if I made several scans in a row, and only with an iPhone 4 (no freeze with a 4S)
Any idea ?
Cheers
Rom
According to the AV Cam View Controller Example calling startRunning or stopRunning does not return until the session completes the requested operation. Since you are sending these messages to the session on the main thread, it freezes all the UI until the requested operation completes. What I would recommend is that you wrap your calls in an Asynchronous dispatch so that the view does not lock-up.
- (void)cancelled
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self stopCapture];
});
//You might want to think about putting the following in another method
//and calling it when the stop capture method finishes
wasCancelled = YES;
if (delegate != nil) {
[delegate zxingControllerDidCancel:self];
}
}
Related
I wrote a SpriteKit app last year targeting 10.10 (Yosemite). Everything ran fine, but when I upgraded to El Capitan this year it freezes in one particular spot. It's a tough problem to diagnose because there is a lot of code so I'll try to be as descriptive as possible. I've also created a YOUTUBE screen recording of the issue.
App's Purpose
The app is basically a leaderboard that I created for a tournament at the school that I teach at. When the app launches, it goes to the LeaderboardScene scene and displays the leaderboard.
The app stays in this scene for the rest of the time. The sword that says "battle" is a button. When it is pressed it creates an overlay and shows the two students that will be facing each other in video form (SKVideoNode).
The videos play continuously and the user of the app eventually clicks on whichever student wins that match and then the overlay is removed from the scene and the app shows the leaderboard once again.
Potential Reasons For High CPU
Playing video: Normally the overlay shows video, but I also created an option where still images are loaded instead of video just in case I had a problem. Whether I load images or video, the CPU usage is super high.
Here's some of the code that is most likely causing this issue:
LeaderboardScene.m
//when the sword button is pressed it switches to the LB_SHOW_VERSUS_SCREEN state
-(void) update:(NSTimeInterval)currentTime {
switch (_leaderboardState) {
...
case LB_SHOW_VERSUS_SCREEN: { //Case for "Versus Screen" overlay
[self showVersusScreen];
break;
}
case LB_CHOOSE_WINNER: {
break;
}
default:
break;
}
}
...
//sets up the video overlay
-(void) showVersusScreen {
//doesn't allow the matchup screen to pop up until the producer FLASHING actions are complete
if ([_right hasActions] == NO) {
[self addChild:_matchup]; //_matchup is an object from the Matchup.m class
NSArray *producers = #[_left, _right];
[_matchup createRound:_round WithProducers:producers VideoType:YES]; //creates the matchup with VIDEO
//[_matchup createRound:_round WithProducers:producers VideoType:NO]; //creates the matchup without VIDEO
_leaderboardState = LB_CHOOSE_WINNER;
}
}
Matchup.m
//more setting up of the overlay
-(void) createRound:(NSString*)round WithProducers:(NSArray*)producers VideoType:(bool)isVideoType {
SKAction *wait = [SKAction waitForDuration:1.25];
[self loadSoundsWithProducers:producers];
[self runAction:wait completion:^{ //resets the overlay
_isVideoType = isVideoType;
[self removeAllChildren];
[self initBackground];
[self initHighlightNode];
[self initOutline];
[self initText:round];
if (_isVideoType)
[self initVersusVideoWithProducers:producers]; //this is selected
else
[self initVersusImagesWithProducers:producers];
[self animationSequence];
_currentSoundIndex = 0;
[self playAudio];
}];
}
...
//creates a VersusSprite object which represents each of the students
-(void) initVersusVideoWithProducers:(NSArray*)producers {
Producer *left = (Producer*)[producers objectAtIndex:0];
Producer *right = (Producer*)[producers objectAtIndex:1];
_leftProducer = [[VersusSprite alloc] initWithProducerVideo:left.name LeftSide:YES];
_leftProducer.name = left.name;
_leftProducer.zPosition = 5;
_leftProducer.position = CGPointMake(-_SCREEN_WIDTH/2, _SCREEN_HEIGHT/3);
[self addChild:_leftProducer];
_rightProducer = [[VersusSprite alloc] initWithProducerVideo:right.name LeftSide:NO];
_rightProducer.name = right.name;
_rightProducer.zPosition = 5;
_rightProducer.xScale = -1;
_rightProducer.position = CGPointMake(_SCREEN_WIDTH + _SCREEN_WIDTH/2, _SCREEN_HEIGHT/3);
[self addChild:_rightProducer];
}
VersusSprite.m
-(instancetype) initWithProducerVideo:(NSString*)fileName LeftSide:(bool)isLeftSide {
if (self = [super init]) {
_isVideo = YES;
_isLeftSide = isLeftSide;
self.name = fileName;
[self initVideoWithFileName:fileName]; //creates videos
[self addProducerLabel];
}
return self;
}
...
//creates the videos for the VersusSprite
-(void) initVideoWithFileName:(NSString*)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES);
NSString *desktopPath = [paths objectAtIndex:0];
NSString *resourcePath = [NSString stringWithFormat:#"%#/vs", desktopPath];
NSString *videoPath = [NSString stringWithFormat:#"%#/%#.mp4", resourcePath, fileName];
NSURL *fileURL = [NSURL fileURLWithPath:videoPath];
AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:fileURL];
_vid = [SKVideoNode videoNodeWithAVPlayer:avPlayer];
//[_vid setScale:1];
[self addChild:_vid];
[_vid play];
avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
}
//used to get the videos to loop
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
UPDATE
The issue has been identified and is very specific to my project, so it probably won't help anyone else unfortunately. When clicking on the "sword" icon that says "Battle", the scene gets blurred and then the overlay is put on top of it. The blurring occurs on a background thread as you'll see below:
[self runAction:[SKAction waitForDuration:1.5] completion:^{
[self blurSceneProgressivelyToValue:15 WithDuration:1.25];
}];
I'll have to handle the blur in another way or just remove it altogether.
I have been developing a game which allows for multiplayer matches. I had previous tested the multiplayer invitations and they had all worked. Sending a request from one device displayed a banner on the other and if the invite was accepted the game started.
Just before submitting the app, two nights ago, I tested this functionality again only to find that it has stopped working.
- (void)authenticateLocalUser:(UIViewController *)viewController :(id<GCHelperDelegate>)theDelegate
{
delegate = theDelegate;
self.presentingViewController = viewController;
if (!gameCenterAvailable) {
// Game Center is not available.
userAuthenticated = FALSE;
}
else{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
/*
The authenticateWithCompletionHandler method is like all completion handler methods and runs a block
of code after completing its task. The difference with this method is that it does not release the
completion handler after calling it. Whenever your application returns to the foreground after
running in the background, Game Kit re-authenticates the user and calls the retained completion
handler. This means the authenticateWithCompletionHandler: method only needs to be called once each
time your application is launched. This is the reason the sample authenticates in the application
delegate's application:didFinishLaunchingWithOptions: method instead of in the view controller's
viewDidLoad method.
Remember this call returns immediately, before the user is authenticated. This is because it uses
Grand Central Dispatch to call the block asynchronously once authentication completes.
*/
//ios 6
[localPlayer setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) {
if (viewcontroller != nil){
userAuthenticated = FALSE;
[self.presentingViewController presentViewController: viewcontroller animated: YES completion:nil];
}
else if (localPlayer.isAuthenticated){
// Enable Game Center Functionality
userAuthenticated = TRUE;
[self checkForInvite:self.presentingViewController :delegate];
if (! self.currentPlayerID || ! [self.currentPlayerID isEqualToString:localPlayer.playerID]) {
// Current playerID has changed. Create/Load a game state around the new user.
self.currentPlayerID = localPlayer.playerID;
// get friends of local player
[localPlayer loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil)
{
[self loadPlayerData: friends];
}
}];
}
}
else{
userAuthenticated = FALSE;
}
[scoreHandler setGameCentreAvailable:userAuthenticated];
})];
}
}
- (void)checkForInvite :(UIViewController *)viewController :(id<GCHelperDelegate>)theDelegate
{
delegate = theDelegate;
self.presentingViewController = viewController;
NSLog(#"Invite handler installed");
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert application-specific code here to clean up any games in progress.
if (acceptedInvite){
NSLog(#"Accepted");
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
[viewController presentViewController: mmvc animated: YES completion:nil];
} else if (playersToInvite) {
NSLog(#"Match Request");
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = playersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[viewController presentViewController: mmvc animated: YES completion:nil];
}
};
}
The debug window in xcode shows the following:
2013-03-27 18:06:20.112 MyApp[791:907] Authentication changed: player authenticated.
2013-03-27 18:06:21.219 MyApp[791:907] Invite handler installed
Mar 27 18:06:21 Neils-iPhone MyApp[791] <Notice>: 18:06:21.356712 com.apple.GameKitServices: -[GKDiscoveryManager startAdvertisingLocalPlayer:discoveryInfo:]: I am [<nil>] [7989F444CF2BDA83] discoveryInfo [{
e = 2;
h = A42FD7FD;
}]
Is the "I am []..." significant in the lines above?
I have even downloaded and run the tutorial from Ray Wenderlich's site for creating a multiplayer game and tried that. That exhibits the same issues, unless it is running in the foreground on both devices. My app does not display invitation requests even if running in the foreground.
Has anyone else experienced this problem or have any ideas what is going on? authenticateLocalUser is called from applicationDidFinishLaunching
The only way I could make invites-by-name work is by going to Settings/Notifications/Game Center and making Game Center display Alerts, not Banners.
If you have GC display alerts, you get a popup box like this:
This dialog acts like a big parent. If the user hits Accept, then your [GKMatchmaker sharedMatchmaker].inviteHandler gets invoked.
If the user hits Decline, then your game never knows that he was invited to any party ever. User hitting Decline means the parent rips up the invitation and never tells his child he got invited to a game at all.
This is the only way I could get invite-by-name to work.
Here is my code:
-(IBAction)saveDownloadedImage
{
NSLog(#"Test"); EXECUTED
indicatorView.hidden = NO; NOT EXECUTED
[indicatorView startAnimating]; NOT EXECUTED
[statusLabel setText:#"WHY?"]; NOT EXECUTED
[currentPicture setImage:[imageView image]]; EXECUTED
ImageFileManager *fileManager = [[ImageFileManager alloc] init]; EXECUTED
[fileManager saveImageToDisk:currentPicture]; EXECUTED
indicatorView.hidden = YES;
[statusLabel setText:#"Image saved successfully."]; EXECUTED
saveButton.enabled = NO; EXECUTED
}
The proccess of saving takes about 5 seconds. So it would be normal to see the indicator in the UI. But nothing happens! Any idea?
Everything is executed. Your problem is that the saveImageToDisk call is synchronous and you are calling it from the UI thread. When you are blocking the UI thread, nothing is ever repainted. The indicator is shown but it cannot be drawn to the screen until the IBAction returns when it will have been hidden again.
You have to call the saving method asynchronously.
Blocking UI thread is never a good idea.
Edit: see the answer for the following question for the correct solution: asynchronous calls to database in ios
Edit2: One of the possible solutions (not tested)
-(IBAction)saveDownloadedImage {
indicatorView.hidden = NO; //Note you can use hidesWhenStopped property for this
[indicatorView startAnimating];
[statusLabel setText:#"BECAUSE..."];
[currentPicture setImage:[imageView image]];
[NSThread detachNewThreadSelector:#selector(save) toTarget:self withObject:nil]
}
- (void)save {
#autoreleasepool {
ImageFileManager *fileManager = [[ImageFileManager alloc] init];
[fileManager saveImageToDisk:currentPicture];
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil waitUntilDone:NO];
}
}
- (void)updateUI {
indicatorView.hidden = YES;
[statusLabel setText:#"Image saved successfully."];
saveButton.enabled = NO;
}
Are you sure that
1) indicatorView and statusLabel are not null, and
2) indicatorView and statusLabel are added as subviews to self.view?
In My Perception your are starting the Activity Indicator main Thread.
Instead Showing the Indicator on main thread
you should call Indicator on seperate Thread as Below.
-(IBAction)saveDownloadedImage
{
NSLog(#"Test"); EXECUTED
commented below code
//indicatorView.hidden = NO; NOT EXECUTED
// [indicatorView startAnimating]; NOT EXECUTED
//instead of main thread create new Thread as Below
[NSThread detachNewThreadSelector:#selector(showloader) toTarget:self withObject:nil];
[statusLabel setText:#"WHY?"]; NOT EXECUTED
[currentPicture setImage:[imageView image]]; EXECUTED
ImageFileManager *fileManager = [[ImageFileManager alloc] init]; EXECUTED
[fileManager saveImageToDisk:currentPicture]; EXECUTED
[statusLabel setText:#"Image saved successfully."]; EXECUTED
saveButton.enabled = NO;
[indicatorView stopAnimating:YES];
indicatorView.hidden = YES;
}
//Custome method For shoing the Indicator.
-(void)showloader{
//call below method here indicatorView object created already.
[indicatorView startAnimating]
}
It'll definitely work
You need to declare your method with a sender like this
-(IBAction)saveDownloadedImage:(id)sender
I have an iPhone game in which players can tweet their score.
I use TWTweetComposeViewController for this.
Since the Twitter sheet can take some time to load, I would like a "loading..." layer to show up after the player has clicked the tweet button, while waiting for the Twitter sheet to show up.
My problem is that the layer (named "colcol" in the code below) only shows up once the tweet sheet is ready! It's as if the layer waited for the tweet sheet to be ready to show up. Which is definitely not what I expect.
Any idea why ?
Thank you!
Here is the tweetScore function, called when the user touches a CCMenuItemImage:
- (void) tweetScore: (CCMenuItem *) menuItem {
colcol=[CCLayerColor layerWithColor:ccc4(0,0, 0, 200) width:50 height:50];
colcol.position=ccp(winSize.width/2-25,winSize.height/2-25);
[self addChild:colcol z:15];
if ([TWTweetComposeViewController canSendTweet])
{
TWTweetComposeViewController *tweetSheet = [[TWTweetComposeViewController alloc] init];
[tweetSheet setInitialText:[NSString stringWithFormat:#"I scored %d!", playerScore]];
tweetSheet.completionHandler = ^(TWTweetComposeViewControllerResult result) {
[appDelegate.viewController dismissModalViewControllerAnimated:YES];
switch (result) {
case TWTweetComposeViewControllerResultCancelled:
[self removeChild:colcol cleanup:YES];
break;
case TWTweetComposeViewControllerResultDone:
[[GCHelper sharedInstance] reportAchievementIdentifier:#"tweet_score" percentComplete:100.0];
[self removeChild:colcol cleanup:YES];
break;
default:
[self removeChild:colcol cleanup:YES];
break;
}
};
[appDelegate.viewController presentModalViewController:tweetSheet animated:YES];
}
else
{
// handle this case
}
}
In general, screen updates in CoreAnimation, UIKit, and OS X are deferred until the end of the current pass through the run loop. You are adding the CALayer, then doing some time-consuming work (setting up the TWTweetComposeViewController), then returning so the run loop can finish -- so there's no time for a screen update to happen in between.
Try setting up the TWTweetComposeViewController in a separate pass through the run loop, using dispatch_async(dispatch_get_main_queue(), ^{ /* your code here */ }).
I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// I've tried to use GCD but similar issue and can't get UI updated.
//dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
//dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});
}
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
if (numSolutions == 0) {
solveButton.title = #"Not Solvable";
} else {
solveButton.title = #"Solve Puzzle";
}
}
Needs to run in background so UI can be updated:
-(void)createTreeFromNode:(TreeNode *)node
{
// Tried using GCD
dispatch_queue_t main_queue = dispatch_get_main_queue();
...Create Tree Node and find Children Code...
if (!solutionFound){
// Solution not found yet so check other children by recursion.
[self createTreeFromNode:newChild];
} else {
// Solution found.
numSolutions ++;
if (maxMoves < newChild.numberOfMoves) {
maxMoves = newChild.numberOfMoves;
}
if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
solutionNode = newChild;
minMoves = newChild.numberOfMoves;
// Update UI on main Thread
dispatch_async(main_queue, ^{
minMovesLabel.stringValue = [NSString stringWithFormat:#"%d",minMoves];
numSolutionsLabel.stringValue = [NSString stringWithFormat:#"%d",numSolutions];
maxMovesLabel.stringValue = [NSString stringWithFormat:#"%d",maxMoves];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added NO.
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// NO - do not wait or enable here.
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);
- (void)process {
dispatch_async(backgroundQueue, ^(void){
//background task
[self processHtml];
dispatch_async(main, ^{
// UI updates in main queue
[self workDone];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
__weak MyClass *weakSelf = self; //must be weak to avoid retain cycle
//Assign async work
dispatch_async(queue,
^{
[weakSelf doWork];
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf workDone];
});
});
queue = nil; //Using ARC, we nil out. Block always retains the queue.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.