AsyncDisplayKit Image Display Blinks - ios

I'm trying to combine between facebook's AsyncDisplayKit and DMLazyScrollView. When moving to next/previous image, view blinks as seen here:
Without the AsyncDisplayKit, this problem doesn't occur.
Here's the part of the code that is in charge of getting the image:
- (UIViewController *) controllerAtIndex:(NSInteger) index {
if (index > viewControllerArray.count || index < 0) return nil;
id res = [viewControllerArray objectAtIndex:index];
if (res == [NSNull null]) {
UIViewController *contr = [[UIViewController alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ASNetworkImageNode *_mediaNode = [[ASNetworkImageNode alloc] init];
_mediaNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
_mediaNode.URL = [NSURL URLWithString:[imagesUrl objectAtIndex:index]];
_mediaNode.frame = contr.view.bounds;
dispatch_async(dispatch_get_main_queue(), ^{
[contr.view addSubview:_mediaNode.view];
});
});
[viewControllerArray replaceObjectAtIndex:index withObject:contr];
return contr;
}
return res;
}
Glad to know if anyone has solved this problem.

Here we go, here's the answer why it flashes:
When converting an app to use AsyncDisplayKit, a common mistake is to add nodes directly to an existing view hierarchy. Doing this will virtually guarantee that your nodes will flash as they are rendered.
async display kit docs

Related

Crash Only in iPod

The CRASH is:
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes.  This will cause an exception in a future release.
It is clear that the problem is that every time the ui is changed must be done on the main thread. In theory this might work
dispatch_async(dispatch_get_main_queue(), {
// code here
})
My Function is:
- (void)requestInBlock:(NSString *)keyThumbnails withArrayQuantity:(NSMutableArray *)quantityArray andOriginalQuantity:(NSInteger)originalQuantity
{
int limit = 5;
NSMutableArray *arrayFive = [[NSMutableArray alloc] init];
NSInteger idQuantityOriginalCount = [quantityArray count];
for (NSInteger i = 0; i < MIN(limit, idQuantityOriginalCount); i ++) {
[arrayFive addObject:[quantityArray objectAtIndex:0]];
[quantityArray removeObjectAtIndex:0];
}
NSInteger idLimitArrayCount = [arrayFive count];
NSInteger __block countProductsRequestLimit = 0;
for (NSNumber *j in arrayFive) {
UIImageView * thumbnailImageView = [[UIImageView alloc] initWithFrame:CGRectMake(xposThumbnails, 0, ratio * 2, 47)];
[thumbnailsView addSubview:thumbnailImageView];
NSURL *imageUrl = [NSURL URLWithString:[NSString stringWithFormat:#"https://s3-us-west-2.amazonaws.com/xxxxxxxxxxxxxx/%#_%#.jpg",keyThumbnails,j]];
[Utils loadFromURL:imageUrl callback:^(UIImage *image) {
countProductsRequestLimit++;
countThumbnailsGlobal++;
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
if (!image) {
NSMutableDictionary *collectInfoFailImage = [[NSMutableDictionary alloc] init];
[collectInfoFailImage setValue:thumbnailImageView forKey:#"image"];
[collectInfoFailImage setValue:imageUrl forKey:#"imageUrl"];
[thumbnailsWithError addObject:collectInfoFailImage];
collectInfoFailImage = nil;
}
if (countThumbnailsGlobal == originalQuantity) {
[arrayFive removeAllObjects];
[self performSelector:#selector(reloadThumbnailsWithError) withObject:nil afterDelay:3];
} else if (idLimitArrayCount == countProductsRequestLimit) {
[arrayFive removeAllObjects];
[self requestInBlock:keyThumbnails withArrayQuantity:quantityArray andOriginalQuantity:originalQuantity];
}
}];
xposThumbnails += (ratio * 2);
}
loadingTimeLine.layer.zPosition = -1;
lblloadingTimeLine.layer.zPosition = -1;
}
I think the mistake is happening here loadFromURL. (It is not sure about this, because in all devices with I do my tests, this never happens, someone outside informed me and sent me error logs)
My question is:
which part of this code may be modifying the autoLayout, maybe [cutVideoScroll addSubview:thumbnailsView];?
Why only occurs on an iPod?
UPDATE:
I'm testing with.
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
});
But error persist.
Thanks for your time.
You got it right, adding subview to view trigger layoutSubviews. So you should addViews only in main thread.
Maybe you have some code specifically for iPhone / iPod in any method which you override?
It seems to be a combination of 2 things.
dispatch_async(dispatch_get_main_queue(), ^{
[thumbnailImageView setImage:image];
[cutVideoScroll addSubview:thumbnailsView];
});
and in all cases, he was added to thumbnailImageView the image regardless validation if (! image)

iOS - accessing array returned by API gives EXC_BAD_ACCESS (code=1)

I am trying to make a Facebook/9gag like comment page in an iOS app. Briefly speaking, there will be a list of images in a list view, and there is a comment button, wheneven users press it, a comment list view will be shown just like those in Facebook and 9gag.
In my project, I created several classes to achieve this, which are:
APIOperator, which defines the path of the api to call, and called whenever a request is made to the server
MyPhotoCell, which is a custom UITableViewCell class forming the photo list view, consisting an image and a comment button
CommentViewController, which is the controller for the comment list displayed
CommentCell, which is a custom UITableViewCell forming the comment list displayed
And below are the code segments regarding the issue:
Firstly, when the user presses the comment button in one of the MyPhotoCell, it will invoke the creation of CommentListViewController:
//MyPhotoCell.m
- (void)click_btn_comment:(id)sender
{
CommentViewController *comment_view = [[CommentViewController alloc] initWithNibName:#"CommentViewController" bundle:nil];
comment_view.targetPhotoid = self.photoItem.photoid;
[[MyAppCoreData coreData].navController pushViewController:comment_view animated:YES];
}
Following with:
//CommentViewController.h
#property (assign, nonatomic) NSInteger targetPhotoid;
#property (strong, nonatomic) NSMutableArray *arr_commentList;
//CommentViewController.m
#interface SecondCreation_CommentViewController ()<UITableViewDataSource, UITableViewDelegate>
{
ASIHTTPRequest *request_comment;
}
#property (strong, nonatomic) UITableView *tbl_comment;
#property (strong, nonatomic) NSNumber *page;
#end
- (void)viewWillAppear:(BOOL)animated
{
if(!self.tbl_comment){
self.tbl_comment = [[UITableView alloc] init];
self.tbl_comment.delegate = self;
self.tbl_comment.dataSource = self;
self.tbl_comment.frame = CGRectMake(0, 55, PHOTOWIDTH, SCREEN_HEIGHT - 100);
[self.view addSubview:self.tbl_comment];
}
if([NSArray checkEmptyArray:self.arr_commentList]){
self.page = 0;
[self sendRequest:1];
}
}
- (void)addDisplayData:(NSArray *)arr_addData withReset:(BOOL)reset
{
//NSLog here shows arr_addData has a count of 6 (6 comments inside, which is normal)
//NSLog here shows arr_commentList has a count of 0 (which is also expected)
if (reset) {
[self.arr_commentList removeAllObjects];
}else {
if (!self.arr_commentList) {
self.arr_commentList = [NSMutableArray array];
}
}
[self.arr_commentList addObjectsFromArray:arr_addData];
//NSLog here shows arr_addData still has a count of 6
//NSLog here shows arr_commentList has a count of 6
}
- (void)sendRequest:(NSInteger)page
{
__block typeof(self)blockSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
blockSelf -> request_comment = [APIOperator getCommentListForPhoto:#(self.targetPhotoid) andPage:page WithCompletionHandler:^(APISTATUS apiStatus, CommentListItem *commentListItem, ASIHTTPRequest *request) {
if (request != blockSelf -> request_comment) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (apiStatus == APISTATUS_SUCCESS) {
//I tried to access and print out the content of the returned commentList here, and it gives EXC_BAD_ACCESS (code = 1 ......)
//e.g. NSLog(((CommentItem *)[commentListItem.commentList objectAtIndex:0]).message);
//I tried to assign the returned commentList to my controller through various ways like:
//[blockSelf.arr_commentList addObjectsFromArray:commentListItem.commentList];
//[blockSelf.arrcommentList = commentListItem.commentList;
//[blockSelf addDisplayData:commentListItem.commentList withReset:YES];
//I tried to access the content of controller's arr_commentList, it will give EXC_BAD_ACCESS (code = 1, .....) too
} else {
}
});
}];
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"commentcell";
CommentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell)
cell = [[CommentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
//acessing the local arr_commentList here will also gives EXC_BAD_ACCESS (code = 1.....)
return cell;
}
And finally, the APIOperator class where the api request is made(ASIHTTPREQUEST is a wrapper class that used to send HTTP request to server and interacts with RESTFUL api, link: link here):
//APIOperator.m
+ (ASIHTTPRequest *)getCommentListForPhoto:(NSNumber *)photoid andPage:(NSInteger)page WithCompletionHandler:(void(^)(APISTATUS apiStatus, CommentListItem *apiItem, ASIHTTPRequest *request))completionBlock
{
NSString *str_page = [NSString stringWithFormat:#"%#", #(page)];
//API_URL here is the url to call the api in the server
NSString *str_API = [NSString stringWithFormat:#"%#",API_URL];
ASIHTTPRequest *_request = [ASIHTTPRequest createAPIRequest:str_API];
__block ASIHTTPRequest *request = _request;
__block void(^tempBlock)(APISTATUS apiStatus, CommentListItem *apiItem, ASIHTTPRequest *request) = completionBlock;
[_request setCompletionBlock:^{
if ([request responseStatusCode] == 200 || [request responseStatusCode] == 302 || [request responseStatusCode] == 304) {
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
JSONParse *jsonParse = [[JSONParse alloc] initWithData:[request responseData]];
CommentListItem *obj = (CommentListItem *)[jsonParse parser2OneObject:#"CommentListItem" withPath:nil];
//I tried to print out the contents of the comments here, and everything are as expected
NSLog(((CommentItem *)[obj.commentList objectAtIndex:0]).message);
jsonParse = nil;
if (tempBlock) {
tempBlock(APISTATUS_SUCCESS,obj,request);
}
tempBlock = nil;
request = nil;
} else {
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
if (tempBlock) {
tempBlock(apiStatus,nil,request);
}
tempBlock = nil;
request = nil;
}
}];
[_request setFailedBlock:^{
DEBUGMSG(#"%d %#",[request responseStatusCode], request.url)
if (tempBlock) {
tempBlock(apiStatus,nil,request);
}
tempBlock = nil;
request = nil;
}];
[_request performSelector:#selector(startAsynchronous)];
return _request;
}
I searched on the internet and found out that EXC_BAD_ACCESS code = 1 is about accessing/releasing a variable that is already released/deallocated before, but I still can't figure out the problem here. I can't find where it is released/deallocated, and why it still give me a count of 6 if it is released/deallocated. Hope I can get some help here about what is causing this problem and how to deal with it, as I've already dealing with this problem for a long time, but still have no idea about what the problem is and what to do. Thanks a lot!

completionBlock error handling

I am trying to handle the completionBlock error and catch it if there is any exception.
Following is my code:
ParseOperation *parser = [[ParseOperation alloc] initWithData:self.appListData];
if([[ParseOperation alloc] initWithData:self.appListData] == nil)
NSLog(#"[[ParseOperation alloc] initWithData:self.appListData] is nill");
__weak ParseOperation *weakParser = parser;
parser.completionBlock = ^(void) {
if (weakParser.appRecordList) {
dispatch_async(dispatch_get_main_queue(), ^{
RootViewController *rootViewController = (RootViewController*)[(UINavigationController*)self.window.rootViewController topViewController];
rootViewController.entries = weakParser.appRecordList;
if(weakParser.appRecordList == nil)
NSLog(#"weakParser.appRecordList is nill");
if(weakParser.appRecordList != nil)
NSLog(#"weakParser.appRecordList is Not nill");
[rootViewController.tableView reloadData];
});
}
self.queue = nil;
};
I implemented the following in my class to get the error result on the console:
parser.errorHandler = ^(NSError *parseError) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleError:parseError];
NSLog(#"[self handleError:parseError] %#", parseError);
});
};
weakParser.appRecordList returns nil most of the time (case of error).
Is the problem inside the ParseOperation class?
The problem is, you need to add __block before weakParser, otherwise, when completion block is running, there will be no reference to the weakParser.
Please note that variables are assigned to block at the moment of the declaration, and if they are not set as __block, their value will be passed to the blocks (as opposed to there pointer a.k.a. reference) and because it is a weak reference in your case, when it gets to the bottom of the method, it will be released and block never gets the chance to act on it.
Hope this helps: Apple Programming Guide: Working with blocks
[Edit] P.S.: I'm not sure but I think removing __weak from weakParser may solve the problem too.
I think it is something about __weak using in block, following code help the dispatch method to pass the appRecordList value:
id appRecordList = weakParser.appRecordList;
if (appRecordList) {
dispatch_async(dispatch_get_main_queue(), ^{
RootViewController *rootViewController = (RootViewController*)[(UINavigationController*)self.window.rootViewController topViewController];
rootViewController.entries = appRecordList;
if(appRecordList== nil)
NSLog(#"weakParser.appRecordList is nill");
if(appRecordList != nil)
NSLog(#"weakParser.appRecordList is Not nill");
[rootViewController.tableView reloadData];
});
}

How to fix this leak?

I am getting a leak on the helper.offlineQueue line where I alloc a NSOperationQueue object. The problem is, I am not quite sure where to release it in this method...
+ (void)flushOfflineQueue
{
// TODO - if an item fails, after all items are shared, it should present a summary view and allow them to see which items failed/succeeded
// Check for a connection
if (![self connected])
return;
// Open list
NSMutableArray *queueList = [self getOfflineQueueList];
// Run through each item in the quietly in the background
// TODO - Is this the best behavior? Instead, should the user confirm sending these again? Maybe only if it has been X days since they were saved?
// - want to avoid a user being suprised by a post to Twitter if that happens long after they forgot they even shared it.
if (queueList != nil)
{
SHK *helper = [self currentHelper];
if (helper.offlineQueue == nil)
helper.offlineQueue = [[NSOperationQueue alloc] init];
SHKItem *item;
NSString *sharerId, *uid;
for (NSDictionary *entry in queueList)
{
item = [SHKItem itemFromDictionary:[entry objectForKey:#"item"]];
sharerId = [entry objectForKey:#"sharer"];
uid = [entry objectForKey:#"uid"];
if (item != nil && sharerId != nil)
[helper.offlineQueue addOperation:[[[SHKOfflineSharer alloc] initWithItem:item forSharer:sharerId uid:uid] autorelease]];
}
// Remove offline queue - TODO: only do this if everything was successful?
[[NSFileManager defaultManager] removeItemAtPath:[self offlineQueueListPath] error:nil];
}
}
Thanks!
I expect you should just do:
helper.offlineQueue = [[[NSOperationQueue alloc] init] autorelease];
The SHK object itself should be retaining the queue and will release it when it is done. The reference you are holding due to the alloc can be released immediately.

How do I fix this leak?

Analyzer keeps saying that I have a leak in the line with the * at the beginning and end, how would I fix this leak so it gets rid of the warning?
+ (void)flushOfflineQueue
{
// TODO - if an item fails, after all items are shared, it should present a summary view and allow them to see which items failed/succeeded
// Check for a connection
if (![self connected])
return;
// Open list
NSMutableArray *queueList = [self getOfflineQueueList];
// Run through each item in the quietly in the background
// TODO - Is this the best behavior? Instead, should the user confirm sending these again? Maybe only if it has been X days since they were saved?
// - want to avoid a user being suprised by a post to Twitter if that happens long after they forgot they even shared it.
if (queueList != nil)
{
SHK *helper = [self currentHelper];
if (helper.offlineQueue == nil)
***helper.offlineQueue = [[NSOperationQueue alloc] init];***
SHKItem *item;
NSString *sharerId, *uid;
for (NSDictionary *entry in queueList)
{
item = [SHKItem itemFromDictionary:[entry objectForKey:#"item"]];
sharerId = [entry objectForKey:#"sharer"];
uid = [entry objectForKey:#"uid"];
if (item != nil && sharerId != nil)
[helper.offlineQueue addOperation:[[[SHKOfflineSharer alloc] initWithItem:item forSharer:sharerId uid:uid] autorelease]];
}
// Remove offline queue - TODO: only do this if everything was successful?
[[NSFileManager defaultManager] removeItemAtPath:[self offlineQueueListPath] error:nil];
}
}
Thanks!
When you use properties they will often perform the proper memory management. In your situation you need to autorelease the class you set.
helper.offlineQueue = [[[NSOperationQueue alloc] init] autorelease];

Resources