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)
Related
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
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!
i am working on threading....i came across this:
-(void)didRecieveResponse:(NSArray*)responseDic{
//Update DB status to '2', which means everything needs to b deleted
[[MediaService sharedInstance] updateMedia:2];
self.projectArray = responseDic;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int j = 0; j<[self.projectArray count]; j++) {
ProjectJsonModel *pModel = [self.projectArray objectAtIndex:j];
[self parseArray:pModel.imageArray project:pModel ptype:1];
[self parseArray:pModel.pdfArray project:pModel ptype:2];
[self parseArray:pModel.videoArray project:pModel ptype:3];
Media *tmpMedia = [[Media alloc] init];
tmpMedia.projectId = pModel.projetId;
tmpMedia.projectName = pModel.projectName;
// NSLog(#"DB project naem = %#",pModel.projectName);
tmpMedia.fileType = 4;
tmpMedia.fileLink = NULL;
tmpMedia.fileName = NULL;
//NSURL *fileUrl = [NSURL URLWithString:tmpMedia.fileLink];
//tmpMedia.fileName = [fileUrl lastPathComponent];
tmpMedia.status = 4;
[[MediaService sharedInstance] insertMedia:tmpMedia];
[tmpMedia release];
}
[self checkDbForDownload];
dispatch_async(dispatch_get_main_queue(), ^(void) {
//Stop your activity indicator or anything else with the GUI
//Code here is run on the main thread
});
});
which stores all online data into database.and it will displays in app. When it is checking for update application stops working.
In that break point is not entering in this loop, why?
The code in dispatch_async would run asynchronously. Debugging this from outer block of code would step out of the entire dispatch_async as a single line and will not step into the code block within. Put an additional break point in for (int j = 0; j<[self.projectArray count]; j++) { instead of stepping in from outer block to debug the code. This would stop the asyc background thread to stop when executing.
I have 3 blocks of code that must execute one by one after previous finished. My implementation not works. I need some help to do it. My code bellow.
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// here I save image on disk and get path
NSString *path = [ImageManager saveImageToDisk:image toEntity:entity withparams:#{#"save" : #"lala"}];
__block NSMutableDictionary *attachments = [NSMutableDictionary dictionary];
__block NSMutableArray *photoPaths = [NSMutableArray array];
dispatch_async(dispatch_get_main_queue(), ^{
//block1. here I load entity and dictionary from it with NSKeyedUnarchiver from CD and set to it image path
if (entity.attachments)
{
attachments = [NSKeyedUnarchiver unarchiveObjectWithData:entity.attachments];
if (attachments[type])
{
photoPaths = attachments[type];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//block2. here I check all images equality ti themselves in entity
BOOL haveDublicate = NO;
NSData *i = [ImageManager imageDataFromPath:path];
NSArray *photoImages = [ImageManager imageDatasFromPaths:photoPaths];
for (NSData *saved in photoImages)
{
if ([saved isEqualToData: i])
{
haveDublicate = YES;
}
}
if (!photoPaths)
{
photoPaths = [NSMutableArray array];
}
dispatch_async(dispatch_get_main_queue(), ^{
//block3. and finally if all ok I save image path, change load counter and post notification
if (path.length
&& ![photoPaths containsObject:path]
&& !haveDublicate
)
{
[photoPaths addObject:path];
[savedLinks setObject:photoPaths forKey:type];
entity.attachments = [NSKeyedArchiver archivedDataWithRootObject:savedLinks];
[self saveContext];
}
[RequestAPI sharedInstance].downloadsCount -= 1;
[[NSNotificationCenter defaultCenter]postNotificationName:kReloadFeedData object:nil];
});
});
});
});
}];
As dispatch_async says they will be executed asynchronous and not synchronous as you expected. Use dispatch_sync instead.
If you want to execute your code on a separate thread simple do the following
// create your thread
dispatch_queue_t queue = dispatch_queue_create("My Other Queue", 0);
// execute your synchronous block on the thread you've just created
dispatch_sync(queue,^{
// add your implementation here to be executed on your separate thread
dispatch_sync(dispatch_get_main_queue()^{
// update your UI here. Don't forget you can only update UI on the main thread
});
});
Im fetching data from database through a php-API with this code:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadDataWithSpinner];
[self reloadAllData];
}
- (void) loadDataWithSpinner {
if (!self.spinner) [self setupSpinnerView];
self.sessions = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
self.userId = [[NSUserDefaults standardUserDefaults] integerForKey:#"CurrentUserId"] ;
self.sessions = [self getAllSessionsForUserId:self.userId];
dispatch_async(dispatch_get_main_queue(), ^(void){
if (self.sessions) {
self.spinnerBgView.hidden = YES;
[self setupChartsAndCountingLabelsWithData];
}
});
});
}
- (NSArray *) getAllSessionsForUserId: (int) userId {
NSData *dataURL = nil;
NSString *strURL = [NSString stringWithFormat:#"http://webapiurl.com/be.php?queryType=fetchAllBasicSessions&user_id=%i", userId];
dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
NSError *error = nil;
if (dataURL) {
NSArray *sessions = [NSJSONSerialization JSONObjectWithData:dataURL options:kNilOptions error:&error];
return sessions;
} else {
return Nil;
}
}
Is there something wrong with the code? I'm getting the correct data when testing in a web-browser with the same database call. But in the app it sometimes updates straight away, as it should, and sometimes it takes up to five minutes for the data to update. Even though i remove the app from the phone/simulator, the data sometime hasn't been update when opening the app again.
I would really appreciate some help.
I finally found the answer. Its nothing wrong with my code, as I thought. The problem lies on the server side. And, as they say in this thread:
NSURLConnection is returning old data
It seems to be badly configured server-side or proxy. Which makes the cache act all wierd. I tried another server and everything worked just fine!
Hope this helps someone with the same issue, cause it sure wasted me ALOT of time.