I'm trying to write this method that returns an NSArray. My NSMutableArray (friendUsers) adds the objects right, but outside the dispatch_async the array is empty.
I try to add the users in the main queue ( as ashowed) but the array is empty to. Any ideas ? Thanks for all your help.
- (NSArray *)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
});
NSLog(#"people:%#",friendUsers);
return [friendUsers copy];
}
you can use blocks, it can make your life easier in this case.
- (void)checkUsersInGroupWithCompleteBlock:(void(^)(NSMutableArray * resultArray))completeBlock {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// call the complete block with the result when you finished
if (completeBlock) completeBlock(friendUsers);
});
}
...and here is how you can call the method:
- (void)anyMethod {
// ... do whetever you want here before
[self checkUsersInGroupWithCompleteBlock:^(NSMutableArray *resultArray) {
NSLog(#"%#", resultArray);
}];
// ... or after
}
EDITED:
NOTE: here is another possible solution, but in your case it just suspends the main thread (which is definitely bad), so you won't gain anything with this solution but pain on the main thread, but if you are on two background threads, this solution can give a very nice example of synchronisation between the threads.
- (NSArray *)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
// our semaphore is here
dispatch_semaphore_t _semaphore = dispatch_semaphore_create(0);
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// the process finished
dispatch_semaphore_signal(_semaphore);
});
// ... we are wainitng for the semaphore's signal
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(_semaphore);
NSLog(#"people:%#",friendUsers);
return [friendUsers copy];
}
There are number of strategies to solve this, but as your operation happens on a background thread, returning the array isn't one of them. You could use NSNotificationCenter to signal that the task as finished and read the array. i.e.
- (void)checkUsersInGroup {
NSMutableArray *friendUsers = [[NSMutableArray alloc] init];
dispatch_queue_t checkUSers = dispatch_queue_create("CheckUsers", NULL);
dispatch_async(checkUSers, ^{
NSArray *totalUsers = [VVDataRead lecturaDades];
NSArray *usersToSearch = [_grup objectForKey:#"groupFriends"];
for (NSString *tempUserId in usersToSearch){
for (NSDictionary *user in totalUsers){
NSString *id = [user objectForKey:#"id"];
if ([tempUserId isEqualToString:id])
dispatch_async(dispatch_get_main_queue(), ^{
[friendUsers addObject:user];
});
}
}
// Signal background task is finished
// Make sure to add an observer to this notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"friendsAddLiteral"
object:nil];
});
}
//this method will respond to the notification
- (void) onFriendsAdded:(NSNotification*)notif {
//do something on the main thread
}
Related
In my iOS app, I want to show the network activity indicator in the top status bar.
I've added the following:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
But the activity indicator never appears.
Does anyone know what might be wrong?
Here is the full code:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
// load sets
[self loadSets];
}
-(void)loadSets{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"in loadSets");
// show loading animation
UIView *loadingView = loadingIndicator;
loadingView.center = CGPointMake(screenWidth/2, screenHeight/2);
[self.view addSubview:loadingView];
[loadingIndicator startAnimating];
self.userSets = [[NSMutableArray alloc]init]; // re-initialize userSets
dispatch_async(bgQueue, ^{
NSString *userURLString = [userBaseUrl stringByAppendingFormat:#"/%#.json?auth_token=%#", username, auth_token];
NSLog(#"userURLString %#", userURLString);
NSURL *userURL = [NSURL URLWithString:userURLString];
NSData * userData = [NSData dataWithContentsOfURL:userURL];
dispatch_async(dispatch_get_main_queue(), ^{
if(userData){
[self fetchSets:userData];
// remove loading animation
[loadingView removeFromSuperview];
}else{
// error with authentication - should log out and require relogin
// [self logoutClick];
}
});
});
});
}
-(void)fetchSets:(NSData *)responseData{
NSError * error;
NSDictionary * json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
if(json){
NSArray *sets = [json objectForKey:#"sets"];
for (NSDictionary *currentSet in sets){
Set *userSet = [[Set alloc] init];
userSet.name = [currentSet objectForKey:#"name"];
userSet.videoURL = [[currentSet objectForKey:#"media"] objectForKey:#"mp4"];
userSet.gifURL = [[currentSet objectForKey:#"media"] objectForKey:#"gif"];
userSet.imgURL = [[currentSet objectForKeyedSubscript:#"media"] objectForKey:#"img"];
userSet.setID = [currentSet objectForKey:#"id"];
[self.userSets addObject: userSet];
}
NSLog(#"trying to reload table data with userSets length %d", [self.userSets count]);
[self.collectionView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"done loading table data");
});
}
}
I have a class called APICalls that manages the calls to the API. Every View Controller calls the appropriate method (createUsername, getStates...) and pass the parameters required. When the data is received and parsed, it calls back the viewcontroller to update the UI with the info downloaded. The following code is working but I would like to know if there is an easier or more flexible/appropriate way of doing this, specially when I update the UI in the viewcontroller. Perhaps with protocols and delegates? Any suggestion is welcomed.
-(void) getObjects:(id)returnObject ofClass:(Class)returnClass fromUrl:(NSString *)urlString withPost:(NSString *)post orPut:(NSString *)put token:(NSString *)token callName:(NSString *)call andAlertTitle:(NSString *)alertTitle
{
// NSString *className = NSStringFromClass([object class]);
__block NSObject *object = returnObject;
__block Class class = returnClass;
__block NSMutableArray *array = [[NSMutableArray alloc]init] ;
__block BOOL dataReceived = NO;
[SVProgressHUD showWithStatus:#"Connecting to the server"];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
ServerConnection *sc = [[ServerConnection alloc] init]; //post/get/put
NSDictionary *jsonDict;
if ( ([post isEqualToString:#""] || !post ) && ([put isEqualToString:#""] || !put ) )
jsonDict = [sc getFromUrl:urlString withToken:token];
else if ([put isEqualToString:#""] || !put)
jsonDict = [sc postToUrl:urlString withPost:post andToken:token];
else
jsonDict = [sc putToUrl:urlString withPut:put andToken:token];
if (jsonDict)
{
NSLog(#"API: json received");
//parse the received json
NSObject *data = [self parseJson:jsonDict alertTitle:alertTitle];
if ([data isKindOfClass:[NSArray class]]) {
NSLog(#"API: Array");
dataReceived = YES;
// Iterate through the array of dictionaries
for(NSDictionary *dict in (NSArray *) data) {
object = [[class alloc] initWithJSONDictionary:dict];
[array addObject:object];
}
}
else if ([data isKindOfClass:[NSDictionary class]]){
NSLog(#"API: Dictionary");
dataReceived = YES;
object = [[class alloc] initWithJSONDictionary:(NSDictionary *)data];
if ([array count]> 0)
[array addObject:object];
}
else
NSLog(#"API: Error from API"); //alertview is shown from HandleError class
}
else{
NSLog(#"no json received");
dispatch_async(dispatch_get_main_queue(), ^(void){
[self alertStatus:#"Error when connecting to the server, please try it again" :alertTitle];
});
}
if (dataReceived)
{
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[SVProgressHUD dismiss];
if ([call isEqualToString:#"getStates"])
{
if ([self.currentViewController isKindOfClass:[SignUpViewController class]])
{
SignUpViewController *signup = (SignUpViewController *) self.currentViewController;
[signup updateStatesList:array];
}
else if ([self.currentViewController isKindOfClass:[MyProfileViewController class]])
{
MyProfileViewController *profileVC = (MyProfileViewController *) self.currentViewController;
[profileVC updateStatesList:array];
}
}
else if ([call isEqualToString:#"getPoints"])
{
PromotionSelectionViewController *promotionVC = (PromotionSelectionViewController *) self.currentViewController;
[promotionVC updatePoints:object];
}
else if ([call isEqualToString:#"getPromotions"])
{
PromotionSelectionViewController *promotionVC = (PromotionSelectionViewController *) self.currentViewController;
[promotionVC updatePromotionsList:array];
}
});
}
});
}
//CreateUser: creates an user when this sign up
-(void)createUserWithUsername:(NSString *)username name:(NSString *)name surname:(NSString *)surname birthdate:(NSString *)birthdate address:(NSString*) address city:(NSString *)city state:(int)state country:(int)country zipCode:(int)zipCode email:(NSString *)email password:(NSString *)password fromViewController:(UIViewController *)currentViewController
{
self.currentViewController = currentViewController;
//Create the post with the username and password
NSString *post =[[NSString alloc] initWithFormat:#"username=%#&name=%#&surname=%#&address=%#&city=%#&state=%d&country=%d&zipcode=%d&birthdate=%#&email=%#&password=%#&",username, name, surname, address, city, state, country, zipCode, birthdate,email,password];
NSLog(#"post: %#", post);
User *user;
[self getObjects:user ofClass:NSClassFromString(#"User") fromUrl:signupURL withPost:post orPut:nil token:nil callName:#"createUser" andAlertTitle:#"SignUp Failed"];
}
-(void) getPointsWithToken:(NSString *)token fromViewController:(UIViewController *)currentViewController{
self.currentViewController = currentViewController;
[self getObjects:nil ofClass:nil fromUrl:getPointsURL withPost:nil orPut:nil token:token callName:#"getPoints" andAlertTitle:#"Get Proints Number Failed"];
}
-(void)getStatesforCounry:(int)idCountry fromViewController:(UIViewController *) currentViewController
{
self.currentViewController = currentViewController;
NSString *url = [NSString stringWithFormat:#"%#%d", getStatesURL, idCountry];
// NSLog(#"url: %#", url);
State *state;
[self getObjects:state ofClass:NSClassFromString(#"State") fromUrl:url withPost:nil orPut:nil token:nil callName:#"getStates" andAlertTitle:#"States not loaded"];
}
...
Using a delegate protocol pattern might help, but in this situation, I think my preference would be to pass a completion-handling block into the method, then call that completion handler block on the main thread to handle the results of the API call—it feels like there's a bit too much view-controller logic going on in the API method and using a completion-handling block (or a delegate callback method) would help move that logic back to the view controller.
Also, though it doesn't really change anything, you can replace the calls
dispatch_async(dispatch_get_main_queue(), ^(void){
...
});
with
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
...
}];
(It is generally preferable to use higher-level APIs, such as NSOperationQueue, over lower-level APIs, like dispatch_async, when they are equivalent.)
I'm new to iOS development and in my app I'm seeing some strange memory usage behavior.
I'm getting objects from server in such setupDataForPage method:
- (void)setupDataForPage:(int)page actionType:(NSString *)type success:(void (^)())callback
{
__weak MyTableViewController *weakSelf = self;
// clearing image cache because feed contains a lot of images
[[SDImageCache sharedImageCache] clearMemory];
[[SDImageCache sharedImageCache] clearDisk];
MyHTTPClient *API = [MyHTTPClient new];
[API feedFor:page success:^(AFHTTPRequestOperation *operation, id data) {
NSArray *data = [data objectForKey:#"data"];
if ([data count] > 0) {
// remove all objects to refresh with new ones
if ([type isEqualToString:#"pullToRefresh"]) {
[weakSelf.models removeAllObjects];
}
// populate data
NSMutableArray *result = [NSMutableArray new];
for (NSDictionary *modelData in data) {
MyModel *model = [[MyModel alloc] initWithDictionary:modelData];
[result addObject:model];
}
[weakSelf.models addObjectsFromArray:result];
[weakSelf.tableView reloadData];
}
callback();
} failure:nil];
}
it is used in viewDidLoad while getting initial request and also for pull refresh and infinite scrolling:
- (void)viewDidLoad {
[super viewDidLoad];
__block int page = 1;
__weak MyTableViewController *weakSelf = self;
// initial load
[self setupDataForPage:page actionType:#"initial" success:^{ page += 1; }];
// pull to refresh
[self.tableView addPullToRefreshWithActionHandler:^{
[weakSelf setupDataForPage:1 actionType:#"pullToRefresh" success:^{
[weakSelf.tableView.pullToRefreshView stopAnimating];
}];
}];
// infinite scrolling
[self.tableView addInfiniteScrollingWithActionHandler:^{
[weakSelf setupItemsForPage:page actionType:#"infiniteScroll" success:^{
page += 1;
[weakSelf.tableView.infiniteScrollingView stopAnimating];
}];
}];
}
I noticed that even after pull to refresh action which returns the same data (and I'm just removing all models and add them once more) my app's memory usage grows from nearly 19mb to 24mb..
I would like someone more experienced to look at this piece of code to determine whether it contains some possible memory leaks.. Should I somehow delete NSMutableArray *result variable after assigning it to models array?
Thanks!
First of all, use #autoreleasepool here:
#autoreleasepool {
NSArray *data = [data objectForKey:#"data"];
if ([data count] > 0) {
// remove all objects to refresh with new ones
if ([type isEqualToString:#"pullToRefresh"]) {
[weakSelf.models removeAllObjects];
}
// populate data
NSMutableArray *result = [NSMutableArray new];
for (NSDictionary *modelData in data) {
MyModel *model = [[MyModel alloc] initWithDictionary:modelData];
[result addObject:model];
}
[weakSelf.models addObjectsFromArray:result];
[weakSelf.tableView reloadData];
}
}
#autoreleasepool allows you to release every object allocated in that scope IMMEDIATELY.
This is perfect situation where use it ;)
I have downloaded images and saved it to a GCD, updated the count of images and performed a post notification in queue. What is happening now is that it does not register that I have downloaded the images. Sometimes I am missing some images, and I think it is because I have missed something in my GCD logic.
Here is my code:
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//1. here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//2. 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(), ^{
//3. 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), ^{
//4. 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(), ^{
//5. 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];
});
});
});
});
}];
I think here I need process 1-2-3-4-5 to get desired result. Am I right or how can I do this queuing?
When I call "performSelectorInBackground" to process some action asynchronously.
My code struct is like:
[self performSelectorInBackground:#selector(AddTheAdditionalDataSourceToTableView) withObject:nil];
and AddTheAdditionalDataSourceToTableView's code struct is like :
-(void)AddTheAdditionalDataSourceToTableView
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
...
dispatch_async(dispatch_get_current_queue(), ^{
...
});
...
});
}
and after the viewController call 'popViewController', dealloc will never be called!!!
I know I can just call 'AddTheAdditionalDataSourceToTableView' directly. it will process asynchronously and it works fine. dealloc will called when the viewController did disappear.But I don't know why calling it using "performSelectorInBackground" will cause this exception above.
: (
- (void)reloadTableViewDataSource
{
// should be calling your tableviews data source model to reload
// put here just for demo
[self performSelectorInBackground:#selector(reFreshTableView) withObject:nil];
//[self reFreshTableView];
}
- (void)reFreshTableView
{
NSString *myPoint = [self currentLocationPoint];
if (!myPoint || [myPoint isEqualToString:#""]) {
[self enableButtons];
[tableView tableViewDidFinishedLoading];
return;
}
Interface *interface = [[Interface alloc]init];
/* 默认从page 1开始刷新数据 */
[self disableButtons];
_interface = (id)interface;
interface.delegate = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[interface aroundInfoFromServiceInterface:_distanceType
withTableType:_tableType
withSortType:_sortType
withPageNum:1
withNumPerPage:NumberOfCellInOneRequest withPoint:myPoint];
});
//[interface release];
//[tableView flashMessage:#"hahahah"];
}
- (void)aroundInfoFromServiceInterface:(NSInteger)distance
withTableType:(NSInteger)tableType
withSortType:(NSInteger)sortType
withPageNum:(NSInteger)pageNum
withNumPerPage:(NSInteger)numPerPage
withPoint:(NSString *)myPiont
{
RequestData *data1 = [RequestData requestData:[NSString stringWithFormat:#"%d", distance] key:#"aroundDistant"];
RequestData *data2 = [RequestData requestData:[NSString stringWithFormat:#"%d", tableType] key:#"itemType"];
RequestData *data3 = [RequestData requestData:[NSString stringWithFormat:#"%d", sortType] key:#"sortType"];
RequestData *data4 = [RequestData requestData:[NSString stringWithFormat:#"%d", pageNum] key:#"pageNum"];
RequestData *data5 = [RequestData requestData:[NSString stringWithFormat:#"%d", numPerPage] key:#"numPerPage"];
RequestData *data6 = [RequestData requestData:[NSString stringWithFormat:#"%#", myPiont] key:#"mappoint"];
NSLog(#"%# , %# , %# , %# , %# , %#" , [NSString stringWithFormat:#"%d", distance] ,[NSString stringWithFormat:#"%d", tableType] ,[NSString stringWithFormat:#"%d", sortType] , [NSString stringWithFormat:#"%d", pageNum], [NSString stringWithFormat:#"%d", numPerPage] , [NSString stringWithFormat:#"%#", myPiont]);
NSArray *tempArray = [NSArray arrayWithObjects:data1, data2, data3, data4, data5, data6, nil];
DataFromService *dataFromService = [[[DataFromService alloc]init]autorelease];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *strContent = [dataFromService requestData:tempArray fromURL:tableType == 0?AROUND_FRIEND_INFO_URL:AROUND_FISHING_STORE_AND_AREA_INFO_URL];
NSArray *responseArray = (NSArray *)[self checkRequestResponse:strContent];
if (responseArray) {
NSMutableArray *tempArray = [[NSMutableArray alloc]init];
switch (tableType) {
case TableType_FrinedsAround:
tempArray = [[FormatData shareInstance] formatDictToFriendsAround:responseArray];
break;
case TableType_FishingStoreAround:
tempArray = [[FormatData shareInstance] formatDictToFishingStoreAround:responseArray];
break;
case TableType_FishingAreaAround:
tempArray = [[FormatData shareInstance] formatDictToFishingAreaAround:responseArray];
break;
default:
break;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(requestDataSuccess:responses:)]) {
[self.delegate requestDataSuccess:self responses:tempArray];
return;
}
}
});
}
dispatch_get_current_queue()
Gets a background queue, because you are using performSelectorInBackground, it gets called from a background queue.
dispatch_get_main_queue()
dispatch_async returns immediately, so you don't need to use performSelectorInBackground:. You can just call dispatch_async directly:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
...
dispatch_async(dispatch_get_main_queue(), ^{
...
});
...
});