Not getting Operation Count for Operation Queue - ios

RemoteImageDownloader *imgView = (RemoteImageDownloader*)[cell viewWithTag:1];
if (imgView == nil)
{
imgView = [[RemoteImageDownloader alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, cell.frame.size.height)];
imgView.tag = 1;
[cell.contentView addSubview:imgView];
}
imgView.image = nil;
imgView.backgroundColor = [UIColor grayColor];
imgView.opQueue = self.opQueue;
//[imgView performSelector:#selector(DownloadRemoteImageforURL:withCachingOption:) withObject:[_marrImgUrl objectAtIndex:indexPath.row]];
if ([self checkDocDirectoryforFileName:[[_marrImgUrl objectAtIndex:indexPath.row] lastPathComponent]])
{
[imgView setImage:[UIImage imageWithData:[self checkDocDirectoryforFileName:[[_marrImgUrl objectAtIndex:indexPath.row] lastPathComponent]]]];
}
else
{
[imgView DownloadRemoteImageforURL:[_marrImgUrl objectAtIndex:indexPath.row] withCachingOption:NSURLRequestReloadRevalidatingCacheData isNeedtoSaveinDocumentDirectory:YES];
}
-(void)DownloadRemoteImageforURL:(NSString*)strURL withCachingOption:(NSURLRequestCachePolicy)urlCachePolicy isNeedtoSaveinDocumentDirectory:(BOOL)isNeedSave
{
ImageLoader *subCategoryImgLoader = [[[ImageLoader alloc] initWithUrl:[NSURL URLWithString:strURL]] autorelease];
subCategoryImgLoader.target = self;
subCategoryImgLoader.didFinishSelector = #selector(imageDownloadDidFinishwithData:andOperation:);
subCategoryImgLoader.didFailSelector = #selector(imageDownloadfailedwithErrorDesc:andOperation:);
[self.opQueue setMaxConcurrentOperationCount:2];
if ([self.opQueue operationCount] > 0)
{
NSOperation *lastOperation = [[self.opQueue operations] lastObject];
[subCategoryImgLoader addDependency:lastOperation];
}
[self.opQueue addOperation:subCategoryImgLoader];
if (_actIndicatorView)
{
[_actIndicatorView removeFromSuperview], _actIndicatorView = nil;
}
_actIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
_actIndicatorView.tag = 100;
_actIndicatorView.center = self.center;
[self addSubview:_actIndicatorView];
[_actIndicatorView startAnimating];
}
In the above code ImageLoader is the subclass of NSOperation. While I'm checking the operation count, I'm getting zero though I'm adding operations into it. Please let me know if I've done any mistakes. I'm not getting what mistakes I've done so I'm getting zero operation count.
I have created the instance of queue and it is created only once and I'm using the same instance instead of creating again and again. After adding any operation it is showing it has one operation but while I'm going to add another one then I'm getting zero count.
RemoteImageDownloader is a subclass of UIImageView. I have created that instance in a UIViewcontroller.
Hope now it will be easy to understand what I' doing actually.
Now I commented the line [self.opQueue setMaxConcurrentOperationCount:2];. Now it is getting the operation count. Can anyone tell me why so?

The most common reason for
"I am sending message x to property y of my object but it returns 0 and it shouldn't"
is that you haven't set a value for the property. i.e. in your case self.opQueue is nil.
Edit We have eliminated the above as the problem. However, what is below is still relevant.
Having said that, you also have a race condition since the operation count may change between your test for it being greater than 0 and adding the dependency (e.g. if the operation finishes).
You should probably do something like this:
NSOperation* lastOp = [[self.opQueue operations] lastObject];
if (lastOp != nil)
{
[subCategoryImgLoader addDependency:lastOp];
}
The documentation for operationCount contains
The value returned by this method reflects the instantaneous number of objects in the queue and changes as operations are completed. As a result, by the time you use the returned value, the actual number of operations may be different. You should therefore use this value only for approximate guidance and should not rely on it for object enumerations or other precise calculations.
(my bold)
I suspect that what is happening when you set the max operations to 2 is that, by the time you get back to your code for the second time, there really are no operations left on the queue.

Related

My Sprite AI is working incorrectly. Why?

I want my two enemies to be set on attack mode, however as it stands only the last enemy added is being set on attack mode.
Is there any way around this? Any tips or suggestions is appreciated. If you need more code please let me know.
-(void)ViewDidLoad {
for (_enemyPoint in [self.enemyGroup objects]) {
self.enemy = [[CCSprite alloc] initWithFile:#"Icon.png"];
self.enemy.scale = 32.0f/57.0f;
self.enemy.position = CGPointMake([_enemyPoint[#"x"] integerValue], [_enemyPoint[#"y"] integerValue]);
[self addChild:self.enemy];
}
self.pathfinder = [HUMAStarPathfinder pathfinderWithTileMapSize:self.tileMap.mapSize
tileSize:self.tileMap.tileSize
delegate:self];
[self enemyAttack];
}
- (void)enemyAttack{
self.epath = [self.pathfinder findPathFromStart:self.enemy.position
toTarget:self.player.position];
self.eactions = [NSMutableArray array];
for (_epointValueInPath in self.epath) {
self.epoint = _epointValueInPath.CGPointValue;
self.emoveTo = [CCMoveTo actionWithDuration:1.0f position:self.epoint];
[self.eactions addObject:self.emoveTo];
}
self.esequence = [CCSequence actionWithArray:self.eactions];
[self.enemy runAction:self.esequence];
}
Look at your loop in viewDidLoad. First, you use an iVar as the loop variable. Probably not what you want. Second, you assign self.enemy in each iteration, but you call enemyAttack after the loop has completed.
Further, enemyAttack does not take any parameters, so it uses internal state. Since it is called after the loop has iterated over all objects, self.enemy will always be the last object in the collection (if there is anything in the collection).
Thus, it is not surprising that you only see the last item being activated as an enemy.
Have you tried to put the [self enemyAttack]; invocation inside the for loop?

NSOperation class run multiple operations

So I've asked a couple of questions regarding the UICollectionView. Understanding how it works, I'm trying to implement lazy loading to load 15 images onto the view controller. I found many examples 1, 2, 3...first and third examples deal with only one operation, second example I don't think uses operations at all, only threads. My question is would it be possible to use a NSOperation class and use/reuse operations? I read that you can't rerun operations but I think you are able to once you initialize them again. Here's my code:
view controller:
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
layout.sectionInset = UIEdgeInsetsMake(10, 20, 10, 20);
[layout setItemSize:CGSizeMake(75, 75)];
self.images = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 230, self.view.frame.size.width, 200) collectionViewLayout:layout];
self.images.delegate = self;
self.images.dataSource = self;
[self.images registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[self.view addSubview:self.images];
self.operation = [[NSOperationQueue alloc]init];
[self.operation addOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"getgallery.php?user=%#", userId] relativeToURL:[NSURL URLWithString:#"http://www.mywebsite.com/"]];
NSData *data = [NSData dataWithContentsOfURL:url];
//datasource for all images
self.imagesGalleryPaths = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[self.images reloadData];
//reload collection view to place placeholders
}];
}];
- (void)viewDidAppear:(BOOL)animated{
//get the visible cells right away
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
self.processedImages = [[NSMutableDictionary alloc]initWithCapacity:visibleCellPaths.count];
}
#pragma mark - collection view
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
int i;
if (self.imagesGalleryPaths.count == 0)
i = 25;
else
i = self.imagesGalleryPaths.count;
return i;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [UICollectionViewCell new];
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.layer.borderWidth = 1;
cell.layer.borderColor = [[UIColor whiteColor]CGColor];
UIImageView *image = [[UIImageView alloc]init];
if (self.imagesGalleryPaths.count != 0) {
if ([visibleCellPaths containsObject:indexPath]) {
[self setUpDownloads:visibleCellPaths];
}
image.image = [UIImage imageNamed:#"default.png"];
image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
[cell.contentView addSubview:image];
}
return cell;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
[self setUpDownloads:visibleCellPaths];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
[self setUpDownloads:visibleCellPaths];
}
- (void)setUpDownloads:(NSArray *)visiblePaths{
//I want to pass the visiblePaths to the NSOperation class if visible cells changed
GalleryOps *gallery = [[GalleryOps alloc]init];
//I will use a dictionary to keep track of which indexPaths are being downloaded...
//...so there are no duplicate downloads
}
GalleryOps.m
#implementation GalleryOps
- (void)main{
//would I initialize all the operations here and perform them?
}
It's almost pointless to show the empty GalleryOps class because I have no idea how to initialize it with multiple operations. I know I have to override the main method, and once I get the image data from URL, I'll need to update the UI, for which I need a delegate method...another thing I don't yet know how to do but there are many examples to figure that out. My biggest question is how to pass the visible cells into this class and run multiple operations? When new visible cells come in, I'll run a check to see which to cancel, which to keep. Any advice here? Thanks in advance!
Looking at your proposed solution, it looks like you want to defer the question of making the operations cancelable. Furthermore, it looks like you want to defer the use of the cache (even though it's no more complicated than your NSMutableDictionary property).
So, setting that aside, your revised code sample has two "big picture" issues:
You can dramatically simplify the image retrieval process. The use of startOperationForVisibleCells and the two scroll delegates is unnecessarily complicated. There is a much simpler pattern in which you can retire those three methods (and achieve an even better UX).
Your cellForItemForIndexPath has a problem, that you're adding subviews. The issue is that cells are reused, so every time a cell is reused, you're adding more redundant subviews.
You really should subclass UICollectionViewCell (CustomCell in my example below), put the configuration of the cell, including the adding of subviews, there. It simplifies your cellForItemAtIndexPath and eliminates the problem of extra subviews being added.
In addition to these two major issues, there were a bunch of little issues:
You neglected to set maxConcurrentOperationCount for your operation queue. You really want to set that to 4 or 5 to avoid operation timeout errors.
You are keying your imageGalleryData with the NSIndexPath. The problem is that if you ever deleted a row, all of your subsequent indexes would be wrong. I suspect this isn't an issue right now (you're probably not anticipating deleting of items), but if you keyed it by something else, such as the URL, it's just as easy, but it is more future-proof.
I'd suggest renaming your operation queue from operation to queue. Likewise, I'd rename the UICollectionView from images (which might be incorrectly inferred to be an array of images) to something like collectionView. This is stylistic, and you don't have to do that if you don't want, but it's the convention I used below.
Rather than saving the NSData in your NSMutableDictionary called imageGalleryData, you might want to save the UIImage instead. This saves you from having to reconvert from NSData to UIImage (which should make the scrolling process smoother) as you scroll back to previously downloaded cells.
So, pulling that all together, you get something like:
static NSString * const kCellIdentifier = #"CustomCellIdentifier";
- (void)viewDidLoad
{
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
layout.sectionInset = UIEdgeInsetsMake(10, 20, 10, 20);
[layout setItemSize:CGSizeMake(75, 75)];
// renamed `images` collection view to `collectionView` to follow common conventions
self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 230, self.view.frame.size.width, 200) collectionViewLayout:layout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
// you didn't show where you instantiated this in your examples, but I'll do it here
self.imageGalleryData = [NSMutableDictionary dictionary];
// register a custom class, not `UICollectionViewCell`
[self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:kCellIdentifier];
[self.view addSubview:self.collectionView];
// (a) change queue variable name;
// (b) set maxConcurrentOperationCount to prevent timeouts
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 5;
[self.queue addOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"getgallery.php?user=%#", userId] relativeToURL:[NSURL URLWithString:#"http://www.mywebsite.com/"]];
NSData *data = [NSData dataWithContentsOfURL:url];
//datasource for all images
self.imagesGalleryPaths = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[self.collectionView reloadData];
}];
}];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imagesGalleryPaths count]; // just use whatever is the right value here, don't make this unnecessarily smaller
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];
NSString *key = self.imagesGalleryPaths[indexPath.row]; // I don't know whether this was simply array, or some nested structure, so tweak this accordingly
UIImage *image = self.imageGalleryData[key];
if (image) {
cell.imageView.image = image; // if we have image already, just use it
} else {
cell.imageView.image = [UIImage imageNamed:#"profile_default.png"]; // otherwise set the placeholder ...
[self.queue addOperationWithBlock:^{ // ... and initiate the asynchronous retrieval
NSURL *url = [NSURL URLWithString:...]; // build your URL from the `key` as appropriate
NSData *responseData = [NSData dataWithContentsOfURL:url];
if (responseData != nil) {
UIImage *downloadedImage = [UIImage imageWithData:responseData];
if (downloadedImage) {
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
self.imageGalleryData[key] = downloadedImage;
CustomCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = downloadedImage;
}
}];
}
}
}];
}
return cell;
}
// don't forget to purge your gallery data if you run low in memory
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
[self.imageGalleryData removeAllObjects];
}
Now, clearly I don't have access to your server, so I couldn't check this (notably, I don't know if your JSON is returning a full URL or just a filename, or whether there was some nested array of dictionaries). But I don't want to you to get too lost in the details of my code, but rather look at the basic pattern: Eliminate your looping through visible cells and responding to scroll events, and let cellForItemAtIndexPath do all the work for you.
Now, the one thing that I introduced was the concept of CustomCell, which is a UICollectionViewCell subclass that might look like:
// CustomCell.h
#import <UIKit/UIKit.h>
#interface CustomCell : UICollectionViewCell
#property (nonatomic, weak) UIImageView *imageView;
#end
and then move cell configuration and adding of the subview here to the #implementation:
// CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer.borderWidth = 1;
self.layer.borderColor = [[UIColor whiteColor]CGColor];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[self addSubview:imageView];
_imageView = imageView;
}
return self;
}
#end
By the way, I still contend that if you want to do this properly, you should refer to my other answer. And if you don't want to get lost in those weeds of the proper implementation, then just use a third party UIImageView category that supports asynchronous image retrieval, caching, prioritizing network requests of visible cells, etc., such as SDWebImage.
You can use the queue and operations themselves to manage multiple operations. If I am reading your question correctly, you have one operation (get the list of image URLs from JSON) that you want to spawn child operations. You can do this by having the parent operation add the child operations to the queue, or by using dependent operations (child would have the parent as a dependancy).
For what you're trying to do you do not need to subclass NSOperation, NSBlockOperation should meet your needs. Subclassing NSOperation is trickier than it looks because of the KVO dependancies (it's very easy to get wrong).
But to the specifics of your question:
My question is would it be possible to use a NSOperation class and use/reuse operations? I read that you can't rerun operations but I think you are able to once you initialize them again
If you're initializing them again they're new objects (or at least, they should be). NSOperations can't be re-run because they have internal state - the tricky KVO bits I mention above. Once they go to "finished", that instance can't be returned to a clean state.
Operations should be fairly lightweight objects and there should not be any significant value in reusing them, and plenty of potential trouble. Creating new operations should be the way to go.
The Apple sample code "LazyTableImages" may give you some hints as how to accomplish what you're trying to do.
The constituent elements of an NSOperation-based lazy loading of images might include:
Create a dedicated NSOperationQueue that will be used for the download operations. Generally this is configured with a maxConcurrentOperationCount of 4 or 5 so that you enjoy concurrency, but so that you won't exceed the maximum number of concurrent network operations.
If you don't use this maxConcurrentOperationCount, with slow network connections (e.g. cellular), you risk having network requests time out.
Have a model object (e.g. an array) that backs your collection view or table view. This would generally only have some identifier for the image (e.g. the URL) not the image itself.
Implement a cache mechanism to store the downloaded images, to prevent the need to re-download images that have already been downloaded. Some implementations only do memory based cache (via NSCache). Others (e.g. SDWebImage) will do two tiers of cache, both memory (NSCache, for optimal performance) and a secondary persistent storage cache (so that when memory pressure forces you to purge the NSCache, you still have a rendition saved on the device so you don't have to re-retrieve it from the network). Others (e.g. AFNetworking) rely upon NSURLCache to cache the responses from the server into persistent storage.
Write a NSOperation subclass for downloading a single image. You want to make this cancelable operation. That implies two different design considerations
First, regarding the operation itself, you probably want to make a concurrent operation (see the Configuring Operations for Concurrent Execution section in the Concurrency Programming Guide).
Second, regarding the network request, you want a cancelable network request. If using NSURLConnection, this means using the delegate-based rendition (not any of the convenience methods). And if using NSURLConnection, the trick is that you have to schedule it in a run loop that persists. There are a number of tricks to accomplish this, but the easiest is to schedule it (with scheduleInRunLoop:forMode:) in the main run loop (though there are more elegant approaches), even though you will be running this from an operation in an NSOperationQueue. Personally I launch a new dedicated thread (like AFNetworking does) for this purpose, but the main run loop is easier and is fine for this sort of process.
If using NSURLSession, this process is conceivably a tad easier, because you can get away with using the completion block rendition of dataTaskWithRequest and not get into the delegate-based implementation if you don't want to. But this is iOS 7+ only (and if you need to do anything fancy like handle authentication challenge requests, you'll end up going the delegate-based approach anyway).
And combining those two prior points, the custom NSOperation subclass would detect when the operation is canceled and then cancel the network request and complete the operation.
By the way, instances of operations are never reused. You create a new operation instance for each image you are downloading.
By the way, if the images you've downloaded are large (e.g. they have dimensions greater than the number of pixels that the image view needs), you may want to resize the images before using them. When JPG or PNG images are downloaded, they are compressed, but when you use them in an image view they are uncompressed, usually require 4 bytes per pixel (e.g. a 1000x1000 image will require 4mb, even though the JPG is much smaller than that).
There are lots of image resizing algorithms out there, but here is one: https://stackoverflow.com/a/10859625/1271826
You will want a cellForItemAtIndexPath that then pulls the above pieces together, namely:
Check to see if the image is already in the cache, if so, use it.
If not, you will start a network request to retrieve the image. You might want to see if this cell (which may be a reused cell from your table view) already has an image operation already in progress, and if so, just cancel it.
Anyway, you can then instantiate a new NSOperation subclass for the downloading of the image and have the completion block update the cache and then also cell's image view.
By the way, when you asynchronously update the cell's image view, make sure the cell is still visible and that the cell hasn't been reused in the interim. You can do this my calling [collectionView cellForItemAtIndexPath:indexPath] (which should not be confused with the similarly named UICollectionViewDataSource method that you're writing here).
Those are the constituent parts of the process, and I'd suggest you tackle them one at a time. There's a lot involved in writing an elegant implementation of lazy loading.
The easiest solution is to consider using an existing UIImageView category (such as provided with SDWebImage) which does all of this for you. Even if you don't use that library, you'll might be able to learn quite a bit by reviewing the source code.
I figure my collection view is not something the user will come back to over and over again, just once in a while. So no reason to cache all the images.
The viewDidAppear is still the same, I get the visible cells right away. The reason why initially I put 25 cells into numberOfItems... is just to get the visible cells right away. So now my cellForItemAtIndexPath is this:
UICollectionViewCell *cell = [UICollectionViewCell new];
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
cell.layer.borderWidth = 1;
cell.layer.borderColor = [[UIColor whiteColor]CGColor];
UIImageView *image = [[UIImageView alloc]init];
if (self.imagesGalleryPaths.count != 0) {
image.image = [UIImage imageNamed:#"profile_default.png"];
image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
[cell.contentView addSubview:image];
}
return cell;
In the viewDidLoad I added this:
if (self.imagesGalleryPaths.count != 0) {
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[self.images reloadData];
[self startOperationForVisibleCells];
}];
}
This is my startOperationForVisibleCells:
[self.operation addOperationWithBlock:^{
int i=0;
while (i < visibleCellPaths.count) {
NSIndexPath *indexPath = [visibleCellPaths objectAtIndex:i];
if (![self.imageGalleryData.allKeys containsObject:indexPath]) {
NSURL *url = [#"myurl"];
NSData *responseData = [NSData dataWithContentsOfURL:url];
if (responseData != nil) {
[self.imageGalleryData setObject:responseData forKey:indexPath];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
UICollectionViewCell *cell = [self.images cellForItemAtIndexPath:indexPath];
UIImageView *image = [UIImageView new];
image.image = [UIImage imageWithData:responseData];
image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
[cell.contentView addSubview:image];
}];
}
}
i++;
}
}];
And that's how I update the cells one by one. Also when the user scrolls away:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
for (int i=0; i<visibleCellPaths.count; i++) {
NSIndexPath *indexPath = [visibleCellPaths objectAtIndex:i];
if ([self.imageGalleryData.allKeys containsObject:indexPath]) {
UICollectionViewCell *cell = [self.images cellForItemAtIndexPath:indexPath];
UIImageView *image = [UIImageView new];
image.image = [UIImage imageWithData:[self.imageGalleryData objectForKey:indexPath]];
image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
[cell.contentView addSubview:image];
}else{
[self startOperationForVisibleCells];
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//same functions as previous
}
I am sure this is a very bad way of doing it but for now, it works. The images are loaded one by one and they stop loading when the user scrolls away.

Why am I able to reuse CCActionInterval?

I'm building a simple 2D game in Cocos2d which involves having enemies cross the screen across different, predefined paths. The nextFrame method has the following code:
int indexCount = 0;
for (Enemy *e in enemies) {
if ([cocosGuy doesCollideWithRect: [e boundingBox]])
{
for (Enemy *e in enemies)
{
[self removeChild: e cleanup: YES];
}
[self startGame];
}
// ^ This code not relevant to the question
if ([e numberOfRunningActions] == 0)
{
[e setPosition: [[enemy_positions objectAtIndex:indexCount] CGPointValue]];
[e runAction: [beziers objectAtIndex: indexCount]];
}
++indexCount;
}
The code in the second if statement above is intended to take a CGPoint from the 'enemy_positions' array and a CCActionInterval from the 'beziers' array. It works - when an enemy completes its path, it is repositioned and the action reruns. But why doesn't this break after the action runs for the first time? Aren't CCActions supposed to be one time only?
I ask because I want to refactor the position and action into a single struct, and I want to make sure I know what's going on first. Am I misunderstanding the CCAction class?
Also, here is the current factory method for generating the 'beziers' array:
-(NSArray*) makeBeziers {
ccBezierConfig bezierconf1;
bezierconf1.controlPoint_1 = ccp(-200, 5);
bezierconf1.controlPoint_2 = ccp(300, 100);
bezierconf1.endPosition = ccp(1000,5);
ccBezierConfig bezierconf2;
bezierconf2.controlPoint_1 = ccp(-200, 5);
bezierconf2.controlPoint_2 = ccp(300, 100);
bezierconf2.endPosition = ccp(1000,5);
ccBezierConfig bezierconf3;
bezierconf3.controlPoint_1 = ccp(-200, 5);
bezierconf3.controlPoint_2 = ccp(300, 100);
bezierconf3.endPosition = ccp(1000,5);
NSArray *myarray;
myarray = [[NSArray arrayWithObjects: [CCBezierBy actionWithDuration:3 bezier: bezierconf1],
[CCBezierBy actionWithDuration:3 bezier: bezierconf2],
[CCBezierBy actionWithDuration:3 bezier: bezierconf3],
nil] retain];
return myarray;
}
This works "by accident". Actions are supposed to be one time only. After they've run, they will be released.
However since you store the actions in a separate array, those actions are retained. Therefore you can re-run them. This might work for some actions, other actions may show subtle issues, and some actions may not do anything, leak memory or crash immediately if you do so.
Re-using actions is generally considered bad practice, unless you know the code of each action and you have verified that reusing it doesn't do anything "bad".

UIImageView animation crashing (sigabrt)

Any idea why this crashes? What am I doing wrong?
Thanks!
-(IBAction)animationOneStart {
NSMutableArray* arrayOfImages = [[NSMutableArray alloc]initWithCapacity:10];
for(int count = 1; count <= 22; count++)
{
NSString *path = [NSString stringWithFormat:#"testDance3_%03d.jpg", count];
UIImage* img = [[UIImage alloc]initWithContentsOfFile:path];
[arrayOfImages addObject:img];
[img release];
}
loadingImageView.animationImages = arrayOfImages;
[arrayOfImages release];
loadingImageView.animationDuration = 3;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
}
Try initializing your NSMutableDictionary with capacity for the same quantity of object's you'll be storing in it. If that doesn't work, I'd try to comment out these two lines:
//loadingImageView.animationImages = nil;
//[loadingImageView release];
In the scope of this code, your other calls appear balanced. But we can't see what's happening inside loadingImageView, and so my guess is that either the loadingImageView itself or it's animations are being released prematurely.
I can't see enough code confirm this, but a couple things that are suspicious are here:
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
So, while the animation is running, you are releasing the images that are being animated? Or the view which, itself, is animating? Probably one or the other or both is the problem.
If the animation is supposed to run indefinitely, you are going to need to keep the view around indefinitely. And if it stops eventually, you should release it after it stops.
You call to addObject will fail when nil is passed. The UIImage could be nil when your system run out of memory and cannot allocate more. When that happens depends on how large your images are, but basically you can be sure that sooner of later use of loadingImageView.animationImages combined with allocating all your UIImage objects at the same time will cause your app to crash. See also playing-looping-images-sequence-in-uiview. You basically need a different approach that does not hold all the images in memory at the same time, the uncompressed images consume way too much memory.

Change accessor view on row selection

I am trying to change the accessor in a cell during some data loading : when the user selects the cell, I load data from a JSON service, and during this load I want to display a spinner as accessor. When loaded, the next view controller display what needed. I use this code but the accessor is not changed (even if I wait in the thread before returning from the method):
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityView startAnimating];
UIView *oldAccessorView = [cell accessoryView];
[cell setAccessoryView:activityView];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject: indexPath] withRowAnimation:UITableViewRowAnimationFade];
MembersViewController *membersViewController = [[MembersViewController alloc] init];
membersViewController.title = #"Members";
// load the members, must be done in a separate thread and must change the cell accessor type to a spinner...
JSONLoader *loader = [[JSONLoader alloc] init];
NSString *membersURL = [NSString stringWithFormat:#"%#/members/all.json", jug.apiURL];
NSMutableArray *members = [loader getMembersFromURL:membersURL];
membersViewController.members = members;
[loader release];
[members release];
[self.navigationController pushViewController:membersViewController animated:YES];
[membersViewController release];
// restore old accessor view
[NSThread sleepForTimeInterval:5];
[activityView stopAnimating];
[activityView release];
[cell setAccessoryView:oldAccessorView];
Not 100% sure I'm clear on the question. But the following might help:
1) It's not clear from this code that you are giving iOS the opportunity to update the display. It only does that at the end of the run loop. But from what I can make out all your code is running in the same pass as the run loop. If that diagnosis is right, then some judicious use of [self performSelector: #selector(method:) withObject: nil afterDelay: 0.0] will be needed.
This question might be helpful background. The answer to a different questions illustrates how to deploy the performSelector:withObject:afterDelay: technique.
2) Unless I'm missing something, you need to add [oldAccessoryView retain] before your [cell setAccessoryView: activityView] call. Then release it again after the subsequent `[cell setAccessoryView:oldAccessoryView]'. But maybe you are retaining the accessory view elsewhere.
i agree with Matthew on point 1:
update on ui IS made at the end of the loop, so you have to do your REST call asynchronously.
For point 2:
given on what was in the accessory view, you may not need to save it.

Resources