I was trying to get a basic prototype of an API working. I have the web service setup and now I'm trying to have an iOS app request the data and display it in a table. It's pretty basic, however, random parts of the data are displaying as null in the table and I haven't been able to find a common thread.
But basically, I make a call to this API, I parse the data, i use a StopCollection class to createItem (of type StopItem) and add it to an NSMutableArray in StopCollection. And then in the MainTableViewController, I use that array as a data source.
The data comes in correctly from the API. I'm able to add it the NSMutableArray just fine. However, it seems that in the MainTableViewController, once I get to the following line, I no longer have access to the same data in the array and bunch of the values are null:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
Here's what I'm working with:
MainTableViewController.m
#import "MainTableViewController.h"
#import "StopCollection.h"
#import "StopItem.h"
#interface MainTableViewController ()
#property (nonatomic, strong) NSURLSession *session;
#end
#implementation MainTableViewController
- (instancetype)init
{
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
[self fetchFeed];
}
return self;
}
- (void)fetchFeed
{
NSString *requestString = #"http://nexttrain.dev/station?include=stopTime";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
for(NSDictionary *stop in jsonObject[#"data"])
{
for (NSDictionary *stopTime in stop[#"stopTime"][#"data"]) {
[[StopCollection sharedStore] createItem:stop[#"station"]
withRoute:stopTime[#"route"]
withHeadsign:stopTime[#"trip"]
withArrivalTime:stopTime[#"arrival_time"]];
NSLog(#"%#", stop[#"station"]);
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
[dataTask resume];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:#"UITableViewCell"];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[StopCollection sharedStore] stops] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"
forIndexPath:indexPath];
NSArray *allStops = [[StopCollection sharedStore] stops];
StopItem *stop = allStops[indexPath.row];
NSString *formattedText = [NSString stringWithFormat:#"%# - %# - %#", stop.station, stop.route, stop.arrivalTime];
cell.textLabel.text = formattedText;
cell.textLabel.font = [UIFont systemFontOfSize:12];
return cell;
}
StopCollection.m
#import "StopCollection.h"
#import "StopItem.h"
#interface StopCollection()
#property (nonatomic) NSMutableArray *privateStops;
#end
#implementation StopCollection
// Override Accessor for Stops, and return from privateStops.
- (NSArray *)stops
{
return [self.privateStops copy];
}
// Singleton Access to Object.
+ (instancetype)sharedStore
{
// Static will be Strong and will remain.
static StopCollection *sharedStore;
// Create initial instance.
if (!sharedStore) {
sharedStore = [[self alloc] initPrivate];
}
// Return instance if exists.
return sharedStore;
}
// Make defualt init inaccessible.
- (instancetype)init
{
// Throw Exception if accesssed.
[NSException raise:#"Singleton"
format:#"Use + (instancetype)sharedStore"];
return nil;
}
// Private initializer will call Super init.
- (instancetype)initPrivate
{
self = [super init];
if (self) {
_privateStops = [[NSMutableArray alloc] init];
}
return self;
}
- (void)createItem:(NSString *)station withRoute:(NSString *)route withHeadsign:(NSString *)headsign withArrivalTime:(NSString *)arrivalTime
{
StopItem *stop = [[StopItem alloc] initWithStation:station
lattitude:22
longitude:56
route:route
tripHeadsign:headsign
arrivalTime];
[self.privateStops addObject:stop];
}
#end
StopCollection.h
#import <Foundation/Foundation.h>
#interface StopCollection : NSObject
+ (instancetype)sharedStore;
#property(nonatomic, readonly, copy) NSArray *stops;
- (void)createItem:(NSString *)station
withRoute:(NSString *)route
withHeadsign:(NSString *)headsign
withArrivalTime:(NSString *)arrivalTime;
#end
StopItem.m
#import "StopItem.h"
#implementation StopItem
- (instancetype)initWithStation:(NSString *)station lattitude:(NSInteger)lattitude longitude:(NSInteger)longitude route:(NSString *)route tripHeadsign:(NSString *)headsign arrivalTime:(NSString *)arrivalTime
{
self = [super init];
if (self) {
_station = station;
_lattitude = lattitude;
_longitude = longitude;
_route = route;
_tripHeadsign = headsign;
_arrivalTime = arrivalTime;
}
return self;
}
- (instancetype)init {
return [self initWithStation:#"Hey" lattitude:0 longitude:0 route:#"" tripHeadsign:#"" arrivalTime:[[NSString alloc] init]];
}
#end
StopItem.h
#interface StopItem : NSObject
#property (nonatomic, weak) NSString *station;
#property (nonatomic) NSInteger lattitude;
#property (nonatomic) NSInteger longitude;
#property (nonatomic, weak) NSString *route;
#property (nonatomic, weak) NSString *tripHeadsign;
#property (nonatomic, weak) NSString *arrivalTime;
- (instancetype)initWithStation:(NSString *)station
lattitude:(NSInteger)lattitude
longitude:(NSInteger)longitude
route:(NSString *)route
tripHeadsign:(NSString *)headsign
arrivalTime:(NSString *)arrivalTime;
#end
And here's the JSON data set I'm working with:
{
"data":[
{
"station":"2 Av",
"lattitude":"40.723402",
"longitude":"-73.989938",
"stopTime":{
"data":[
{
"arrival_time":"02:40:30",
"trip":"JAMAICA - 179 ST",
"route":"some longer word with a -"
}
]
}
},
{
"station":"2 Av",
"lattitude":"40.723402",
"longitude":"-73.989938",
"stopTime":{
"data":[
{
"arrival_time":"00:54:00",
"trip":"CONEY ISLAND - STILLWELL AV",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Delancey St",
"lattitude":"40.718611",
"longitude":"-73.988114",
"stopTime":{
"data":[
{
"arrival_time":"02:39:00",
"trip":"JAMAICA - 179 ST",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Delancey St",
"lattitude":"40.718611",
"longitude":"-73.988114",
"stopTime":{
"data":[
{
"arrival_time":"00:55:30",
"trip":"CONEY ISLAND - STILLWELL AV",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Essex St",
"lattitude":"40.718315",
"longitude":"-73.987437",
"stopTime":{
"data":[
{
"arrival_time":"01:23:30",
"trip":"JAMAICA CENTER - PARSONS/ARCHER",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Essex St",
"lattitude":"40.718315",
"longitude":"-73.987437",
"stopTime":{
"data":[
{
"arrival_time":"00:52:30",
"trip":"BROAD ST",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Bowery",
"lattitude":"40.72028",
"longitude":"-73.993915",
"stopTime":{
"data":[
{
"arrival_time":"01:22:00",
"trip":"JAMAICA CENTER - PARSONS/ARCHER",
"route":"some longer word with a -"
}
]
}
},
{
"station":"Bowery",
"lattitude":"40.72028",
"longitude":"-73.993915",
"stopTime":{
"data":[
{
"arrival_time":"00:54:00",
"trip":"BROAD ST",
"route":"some longer word with a -"
}
]
}
}
]
}
That's not related to dispatch. It's because you have weak properties, which get nilled whenever possible, i.e. when there is no reference holding them. Change weak to strong or copy in your StopItem model, and everything will work.
Related
Is there anyway to check the value of 'type' variable with completionHandler.
-(void)sendApiMethod:(NSString*)apiName ApiType:(NSString*)type
{
[SendAPI setAPIWithName:#"APIName" completionHandler:^(NSArray *errors) {
if([type isEqualToString:#"Login"])
{
/// Call Some Other function
}
}];
}
I wrote a small piece of code to verify if works (only reading your question I would say yes as Droppy)
I added all there code in a ViewController in a Simple View App.
some assumption:
- all code there for sake of semplicity ....
- I have added a singleton as it seems You are calling a class method.
- instance method is a bit rough, it simply saves name and block
- I added a typedef for blocks to better reading it.
#import "ViewController.h"
typedef void (^CompletionBlock)(NSArray *errors);
#interface SendAPI : NSObject
-(void)setAPIWithName:(NSString*)name completionHandler: (CompletionBlock)completionHandler;
+(void)setAPIWithName:(NSString*)name completionHandler: (CompletionBlock)completionHandler;
+(SendAPI*)sharedInstance;
#property (strong) CompletionBlock completionBlock;
#property (strong) NSString * name;
#end
#implementation SendAPI : NSObject
static SendAPI * _singleton = nil;
+(SendAPI*)sharedInstance
{
if (_singleton == nil)
{
_singleton = [[SendAPI alloc] init];
}
return _singleton;
}
-(void)setAPIWithName:(NSString*)name completionHandler: (CompletionBlock)completionHandler;
{
self.completionBlock = completionHandler;
self.name = [name copy];
__weak SendAPI * weakRef = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSError* err = [NSError errorWithDomain: #"delayed"
code:1111
userInfo: #{#"info": self.name}
];
weakRef.completionBlock(#[err]);
});
}
+(void)setAPIWithName:(NSString*)name completionHandler: (CompletionBlock)completionHandler;
{
[[SendAPI sharedInstance]setAPIWithName:name completionHandler:completionHandler];
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self sendApiMethod:#"HELLO" ApiType: #"Login"];
}
-(void)sendApiMethod:(NSString*)apiName ApiType:(NSString*)type{
[SendAPI setAPIWithName:#"APIName" completionHandler:^(NSArray *errors) {
if([type isEqualToString:#"Login"])
{
/// Call Some Other function
NSLog(#"%#", errors);
}
}];
}
it does LOG correctly
I want to check if the user has an open basket if so send them to the menu if not then send send them to my open basket view but i m getting an error.
So my question is how to get rid of this problem ? and is my logic right ?
'NSInvalidArgumentException', reason: '-[Merchant merchantId]: unrecognized selector sent to instance 0x7fad19529490'.
i m using this method to check for open baskets when the button addToOrderButtonTapped is press in the menuview
[[OrderManager sharedManager]getBasketsForMerchant:merchant success:^(NSArray *baskets) {
for (Basket *basket in baskets){
if ([basket.status isEqualToString:kBasketStatusOpen]) {
[self.openBaskets addObject:basket];
}
}if (self.openBaskets.count > 0){
self.openBaskets = self.openBaskets;
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:kSegueMenu sender:self];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:KSegueOpenBasket sender:self];
});
}
} failure:^(NSError *error, NSHTTPURLResponse *response) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *localizedString = NSLocalizedString(#"Order.ErrorLoadingOpenOrders", "Get error message");
[self showMessage:localizedString withTitle:MessageTypeError];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
});
}];
The menuview
//
// MenuItemViewController.m
// BaseApp
//
#import "MenuItemViewController.h"
#import "RoundedButton.h"
#import "MenuOptionCell.h"
#import "MenuItem.h"
#import "MenuOptionButton.h"
#import "MenuChoice.h"
#import "OrderManager.h"
#import "TypeOfChoiceCell.h"
#import "MenuOption.h"
#import "OrderPricingTableViewCell.h"
#import "RKManagedObjectStore.h"
#import "NSManagedObjectContext+RKAdditions.h"
#import "MerchantManager.h"
#import "Merchant.h"
#import "OrdersViewController.h"
#import "MenuItem.h"
#import "MenuViewController.h"
#import "OrderConfirmationViewController.h"
#import "OrderManager.h"
#import "APIClient.h"
#import "MenuManager.h"
#import "DiscoverViewController.h"
#import "DiscoverCollectionViewCell.h"
#import "MerchantManager.h"
#import "MenuViewController.h"
#import "MenuManager.h"
#import "MerchantDetailsViewController.h"
#import "OpenOrdersViewController.h"
typedef NS_ENUM(NSUInteger, MenuOptionsSection) {
MenuOptionsSectionRequired = 0,
MenuOptionsSectionOptional = 1
};
static NSString *const OpenBasketsSegue = #"OpenBaskets";
static NSString *const kSegueMenu = #"ShowMenu";
static NSString *const KSegueOpenBasket = #"OpenBaskets";
#interface MenuItemViewController () <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, weak) IBOutlet UILabel *menuItemName;
#property (nonatomic, weak) IBOutlet UILabel *quantityLabel;
#property (nonatomic, weak) IBOutlet UILabel *menuItemPrice;
#property (nonatomic, weak) IBOutlet UILabel *subTotalLabel;
#property (nonatomic, weak) IBOutlet UITextView *menuItemDescription;
#property (nonatomic, weak) IBOutlet RoundedButton *addToOrderButton;
#property (nonatomic, weak) IBOutlet UITableView *tableView;
#property (nonatomic, weak) IBOutlet UIView *cartView;
#property (nonatomic, weak) IBOutlet UIButton *subtractButton;
#property (nonatomic) NSDecimalNumber *temporarySubtotal;
#property (nonatomic, strong) NSMutableArray<MenuOption *> *requiredChoices;
#property (nonatomic, strong) NSMutableArray<MenuOption *> *optionalChoices;
#property (nonatomic, strong) NSMutableArray<NSMutableArray *> *optionChoicesSection;
#property (nonatomic, strong) NSMutableArray<NSDictionary *> *options;
#property (nonatomic, strong) NSMutableArray <Basket *>*openBaskets;
#property (nonatomic) BOOL launchViewFirstTime;
#end
#implementation MenuItemViewController
#pragma - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = #"Menu Item";
self.menuItemName.text = self.menuCategory.selectedBasketLine.menuItem.name;
self.menuItemPrice.text = [NSString stringWithFormat:#"%#", [Basket formattedCurrencyStringForAmount:self.menuCategory.selectedBasketLine.menuItem.price]];
self.quantityLabel.text = self.menuCategory.selectedBasketLine.quantity.stringValue;
self.temporarySubtotal = [self calculateTemporarySubtotal:[OrderManager sharedManager].currentBasket.subtotal menuItemPrice:self.menuCategory.selectedBasketLine.menuItem.price];
[self setSubtotalText:self.temporarySubtotal];
self.menuItemDescription.text = self.menuCategory.selectedBasketLine.menuItem.menuItemDescription;
self.subtractButton.alpha = 0.65;
self.requiredChoices = [[NSMutableArray alloc] init];
self.optionalChoices = [[NSMutableArray alloc] init];
self.optionChoicesSection = [[NSMutableArray alloc] init];
self.options = [[NSMutableArray alloc] init];
[self initializeChoiceArrays];
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.launchViewFirstTime = YES;
if (self.optionChoicesSection.count > 0) {
[self.tableView reloadData];
} else {
self.tableView.hidden = YES;
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.launchViewFirstTime = NO;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
for (MenuOption *menuOption in self.requiredChoices) {
[menuOption resetNumberOfChoicesSelected];
}
for (MenuOption *menuOption in self.optionalChoices) {
[menuOption resetNumberOfChoicesSelected];
}
self.menuCategory = nil;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.menuItemDescription setContentOffset:CGPointZero animated:YES];
}
#pragma - IBActions
- (IBAction)addButtonTapped:(id)sender {
NSInteger count = self.quantityLabel.text.integerValue;
count++;
if (count > 1) {
self.subtractButton.alpha = 1;
}
NSDecimalNumber *newSubTotal = [self.temporarySubtotal decimalNumberByAdding:self.menuCategory.selectedBasketLine.menuItem.price];
[self modifyCurrentBasketSubtotal:newSubTotal quantity:count];
}
- (IBAction)subtractButtonTapped:(id)sender {
NSInteger count = self.quantityLabel.text.integerValue;
if (count > 1) {
count--;
NSDecimalNumber *newSubTotal = [self.temporarySubtotal decimalNumberBySubtracting:self.menuCategory.selectedBasketLine.menuItem.price];
[self modifyCurrentBasketSubtotal:newSubTotal quantity:count];
if (count == 1) {
self.subtractButton.alpha = 0.65;
}
}
}
- (IBAction)addToOrderButtonTapped:(id)sender {
MenuOption *menuOption;
Merchant *merchant = [[Merchant alloc]init];
// First check if there are any missing required options that have to be selected
if ((menuOption = [self checkMissingRequiredOptionsHaveBeenSelected])) {
NSString *localizedString = NSLocalizedString(#"AddMenuItem.RequiredChoicesNotSelected", #"Get string for error");
NSString *formattedString = [NSString stringWithFormat:localizedString, menuOption.name];
[self showMessage:formattedString withTitle:MessageTypeError];
return;
}
// Now check if the minimum and maximum choice seletion have been met for the menu options
if ((menuOption = [self validateMinMaxForMenuOptionsHaveBeenMet])) {
NSString *localizedString = NSLocalizedString(#"AddMenuItem.MinMaxNotFulfilled", #"Get string for error");
NSString *formattedString = [NSString stringWithFormat:localizedString, menuOption.name];
[self showMessage:formattedString withTitle:MessageTypeError];
return;
}
// Add the menu item to the basket
if (self.menuItemAddedBlock) {
self.menuItemAddedBlock(self, self.menuCategory);
// [self dismissViewControllerAnimated:YES completion:nil];
//checking for open basket here
[[OrderManager sharedManager]getBasketsForMerchant:merchant success:^(NSArray *baskets) {
for (Basket *basket in baskets){
if ([basket.status isEqualToString:kBasketStatusOpen]) {
[self.openBaskets addObject:basket];
}
}if (self.openBaskets.count > 0){
self.openBaskets = self.openBaskets;
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:kSegueMenu sender:self];
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:KSegueOpenBasket sender:self];
});
}
} failure:^(NSError *error, NSHTTPURLResponse *response) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *localizedString = NSLocalizedString(#"Order.ErrorLoadingOpenOrders", "Get error message");
[self showMessage:localizedString withTitle:MessageTypeError];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
});
}];
}
}
- (IBAction)cancelButtonTapped:(UIBarButtonItem *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma - UITableViewDelegate, UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.optionChoicesSection.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.optionChoicesSection[section].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MenuOptionCell *cell = (MenuOptionCell *)[tableView dequeueReusableCellWithIdentifier:[MenuOptionCell reuseIdentifier]];
cell.menuOption = self.optionChoicesSection[indexPath.section][indexPath.row];
cell.menuOptionCellButtonPressedBlock = ^(MenuOptionButton *button, MenuOption *option, MenuChoice *choice) {
[self adjustSelectedOptions:option choice:choice indexPath:indexPath];
};
cell.menuOptionCellDefaultOptionDetectedBlock = ^(MenuOptionButton *button, MenuOption *option, MenuChoice *choice) {
[self adjustSelectedOptions:option choice:choice indexPath:indexPath];
};
cell.menuOptionCellDeselectedBlock = ^(MenuOptionButton *button, MenuOption *option, MenuChoice *choice) {
[self adjustSelectedOptions:option choice:choice indexPath:indexPath];
};
[cell configureCell:self.launchViewFirstTime];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
TypeOfChoiceCell *cell = (TypeOfChoiceCell *)[tableView dequeueReusableCellWithIdentifier:[TypeOfChoiceCell reuseIdentifier]];
switch ((MenuOptionsSection)section) {
case MenuOptionsSectionRequired:
if (self.requiredChoices.count > 0) {
cell.title = NSLocalizedString(#"AddMenuItem.RequiredChoiceText", #"Get string for title");
return cell;
} else if (self.optionalChoices.count > 0) {
cell.title = NSLocalizedString(#"AddMenuItem.OptionalChoiceText", #"get string for title");
return cell;
}
case MenuOptionsSectionOptional:
if (self.optionalChoices.count > 0) {
cell.title = NSLocalizedString(#"AddMenuItem.OptionalChoiceText", #"Get string for title");
return cell;
}
}
return nil;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
MenuOption *option = self.optionChoicesSection[indexPath.section][indexPath.row];
return [MenuOptionCell heightForMenuOption:option];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return [TypeOfChoiceCell height];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.01f;
}
#pragma - Helpers
- (void)setSubtotalText:(NSDecimalNumber *)subtotal {
if (!subtotal) {
self.subTotalLabel.text = NSLocalizedString(#"AddMenuItem.SubtotalDefault", #"Get string for subtotal");
} else {
NSString *localizedString = NSLocalizedString(#"AddMenuItem.SubtotalFormatted", #"Get string for subtotal");
self.subTotalLabel.text = [NSString stringWithFormat:localizedString, [Basket formattedCurrencyStringForAmount:subtotal]];
}
}
- (void)modifyCurrentBasketSubtotal:(NSDecimalNumber *)subtotal quantity:(NSInteger)quantity {
self.menuCategory.selectedBasketLine.quantity = [NSNumber numberWithInteger:quantity];
self.menuCategory.selectedBasketLine.subtotal = subtotal;
self.temporarySubtotal = subtotal;
[self setSubtotalText:subtotal];
self.quantityLabel.text = self.menuCategory.selectedBasketLine.quantity.stringValue;
}
- (NSDecimalNumber *)calculateTemporarySubtotal:(NSDecimalNumber *)orderManagerSubTotal menuItemPrice:(NSDecimalNumber *)menuItemPrice {
if (orderManagerSubTotal == 0) {
return menuItemPrice;
} else {
return [orderManagerSubTotal decimalNumberByAdding:menuItemPrice];
}
}
- (MenuOption *)checkMissingRequiredOptionsHaveBeenSelected {
for (MenuOption *menuOption in self.requiredChoices) {
if (!menuOption.selected) {
return menuOption;
}
}
return nil;
}
- (MenuOption *)validateMinMaxForMenuOptionsHaveBeenMet {
for (MenuOption *menuOption in self.requiredChoices) {
if ([menuOption validateIndividualMinMaxForMenuOption]) {
return menuOption;
}
}
for (MenuOption *menuOption in self.optionalChoices) {
if (menuOption.selected) {
if ([menuOption validateIndividualMinMaxForMenuOption]) {
return menuOption;
}
}
}
return nil;
}
- (void)initializeChoiceArrays {
NSArray<MenuOption *> *menuOptions = [self.menuCategory.selectedBasketLine.menuItem.menuOptions allObjects];
for (MenuOption *menuOption in menuOptions) {
if (menuOption.minimumChoices == nil) {
menuOption.minimumChoices = [NSNumber numberWithInt:0];
}
if (menuOption.maximumChoices == nil) {
menuOption.maximumChoices = [NSNumber numberWithInt:0];
}
// For now make an optional choice required if minimumChoices > 0
if (menuOption.isRequired || [menuOption.minimumChoices intValue] > 0) {
menuOption.isRequired = YES;
[self.requiredChoices addObject:menuOption];
} else {
[self.optionalChoices addObject:menuOption];
}
}
if (self.requiredChoices.count > 0) {
[self.optionChoicesSection addObject:self.requiredChoices];
}
if (self.optionalChoices.count > 0) {
[self.optionChoicesSection addObject:self.optionalChoices];
}
}
- (void)adjustSelectedOptions:(MenuOption *)option choice:(MenuChoice *)choice indexPath:(NSIndexPath *)indexPath {
self.menuCategory.selectedBasketLine.subtotal = self.menuCategory.selectedBasketLine.menuItem.price;
if (option.selected && option.menuChoices.count == 0) {
[self.options addObject:#{kOption: option.menuOptionId ?: #"", kChoice: #""}];
if (option.price) {
self.temporarySubtotal = [self.temporarySubtotal decimalNumberByAdding:option.price];
}
if (option.isRequired) {
self.requiredChoices[indexPath.row].selected = option.selected;
self.requiredChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
} else {
self.optionalChoices[indexPath.row].selected = option.selected;
self.optionalChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
}
} else {
if (option.menuChoices.count == 0) {
[self.options removeObject:#{kOption: option.menuOptionId ?: #"", kChoice: #""}];
if (option.price) {
self.temporarySubtotal = [self.temporarySubtotal decimalNumberBySubtracting:option.price];
}
if (option.isRequired) {
self.requiredChoices[indexPath.row].selected = option.selected;
self.requiredChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
} else {
self.optionalChoices[indexPath.row].selected = option.selected;
self.optionalChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
}
}
}
if (choice.selected && option.menuChoices.count > 0) {
[self.options addObject:#{kOption: choice.menuOption.menuOptionId ?: #"", kChoice: choice.menuChoiceId ?: #""}];
if (choice.price) {
self.temporarySubtotal = [self.temporarySubtotal decimalNumberByAdding:choice.price];
}
if (option.isRequired) {
self.requiredChoices[indexPath.row].selected = choice.selected;
self.requiredChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
} else {
self.optionalChoices[indexPath.row].selected = choice.selected;
self.optionalChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
}
} else {
if (option.menuChoices.count > 0) {
[self.options removeObject:#{kOption: choice.menuOption.menuOptionId ?: #"", kChoice: choice.menuChoiceId ?: #""}];
if (choice.price) {
self.temporarySubtotal = [self.temporarySubtotal decimalNumberBySubtracting:choice.price];
}
if (option.isRequired) {
if ([option.numberOfChoicesSelected intValue] == 0) {
self.requiredChoices[indexPath.row].selected = option.selected;
} else {
self.requiredChoices[indexPath.row].selected = !choice.selected;
}
self.requiredChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
} else {
self.optionalChoices[indexPath.row].selected = choice.selected;
self.optionalChoices[indexPath.row].numberOfChoicesSelected = option.numberOfChoicesSelected;
}
}
}
[self setSubtotalText:self.temporarySubtotal];
self.menuCategory.selectedBasketLine.attributes = self.options;
}
#end
This is the line of code thats giving me the error
+ (void)getBasketsForMerchant:(Merchant *)merchant success:(void (^)(NSArray *basket))success failure:(void (^)(NSError *, NSHTTPURLResponse *))failure {
NSMutableDictionary* params = #{#"expand": #"merchant"}.mutableCopy;
if (merchant) {
params[#"merchant"] = merchant.merchantId;
}
[[RKObjectManager sharedManager] getObjectsAtPath:kOrdersEndpoint parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
NSArray* items = mappingResult.array;
if (success) {
success(items);
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
if (failure) {
failure(error, operation.HTTPRequestOperation.response);
} else {
_defaultFailureBlock(operation, error);
}
}];
}
This error:
'NSInvalidArgumentException', reason: '-[Merchant merchantId]: unrecognized selector sent to instance 0x7fad19529490'.
Means that you have an object at location 0x7fad19529490 and you tried to call "merchantId" on it. That object does not respond to merchantId.
So, look very carefully at the definition of Merchant. Did you get the spelling of merchantId right in fieldMappings? Is it merchantID with a capital D?
If you are sure that merchant has a property named merchantId exactly, then, the next likely thing is that the object at 0x7fad19529490 is not a Merchant.
The easiest thing to do is to add an exception breakpoint (use the +) at the bottom of the breakpoint navigator. When this exception happens, grab the pointer address from the error (it might be different each time) and see what it is in the debugger. To do that, at the (lldb) prompt, type:
po (NSObject*)(0x7fad19529490)
but use the address from the error. If it's a Merchant, I expect it to say something like:
<Merchant: 0x7fad19529490>
Or, if you have overridden description, it will be the output of that.
I have a Shearwell stick reader, which reads in cattle tags, sheep tags and goat tags via BLE. When connecting the BLE device, the device list shows up in the tableView of the SDLBluetoothConnectViewController then disappears. I am also not able to grab the information from the device using a segue. The bluetooth icon flashes 3 times and does not pair with the device. Can anyone see where my error is i've looked for hours!! thanks in advance!
here is the SDLStickreader.m
// A StickReader is a wrapper for a CBPeripheral that has been discovered using a filter for StickReader UUID.
#import "SDLStickReader.h"
#import "SDLStickReaderManager.h"
#import "SDLStickReaderPrivate.h"
#import "SDLSerialPort.h"
//#import "SDLIdentifier.h"
#interface SDLStickReader () <SerialPortDelegate>
#property (strong, nonatomic) CBPeripheral *peripheral;
#end
#implementation SDLStickReader
static SDLStickReaderManager * _manager;
NSMutableData *_data;
SDLSerialPort *serialPort;
BOOL waitingForConfigInfo = YES;
NSMutableString *configString;
+(SDLStickReaderManager *)manager {
if (nil == _manager) {
_manager = [[SDLStickReaderManager alloc] init];
}
return _manager;
}
+ (instancetype)forPeripheral: (CBPeripheral *)peripheral {
return [[self alloc] initWithPeripheral:peripheral];
}
- (instancetype)initWithPeripheral: (CBPeripheral *)peripheral {
self = [super init];
if (self) {
_detail = #"";
self.peripheral = peripheral;
//peripheral.delegate = self;
__tagReadService = [CBUUID UUIDWithString:SDL_STICKREADER_TAGREAD_UUID];
if (peripheral.state == CBPeripheralStateConnected) {
NSLog(#"periperal is connected");
} else {
NSLog(#"periperal is NOT connected");
}
_data = [[NSMutableData alloc] init];
serialPort = [[SDLSerialPort alloc] initWithPeripheral:peripheral andDelegate:self];
peripheral.delegate = self;
[serialPort open ];
}
return self;
}
-(BOOL)hasPeripheral: (CBPeripheral *)peripheral {
return [self.peripheral isEqual: peripheral];
}
-(NSUUID *)identifier {
return self.peripheral.identifier;
}
-(NSString *)name {
return self.peripheral.name;
}
-(NSString *)description {
return [NSString stringWithFormat: #"Peripheral: %#", self.name];
}
-(NSString *)state {
switch (self.peripheral.state) {
case CBPeripheralStateConnected:
return SDL_STICKREADER_STATE_CONNECTED;
case CBPeripheralStateConnecting:
return SDL_STICKREADER_STATE_CONNECTING;
case CBPeripheralStateDisconnected:
return SDL_STICKREADER_STATE_DISCONNECTED;
}
}
// Every time the peripheral sends new data, it calls the delegate peripheral:didUpdateValueForCharacteristic:error: method. The second argument contains the characteristic that you can read.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic: (CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(#"Error");
return;
}
if ([self.characteristics containsObject: characteristic.UUID]) {
NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// Have we got everything we need?
NSUInteger startOfEom = [stringFromData rangeOfString: SDL_STICKREADER_EOM].location;
if (NSNotFound == startOfEom) {
// it is not the EOM so append the data to what we have so far and wait for more.
[_data appendData: characteristic.value];
} else {
// contains EOM, so remove the
NSString *lastPart = [stringFromData substringToIndex:startOfEom];
NSString *message = [[NSString alloc] initWithData: _data encoding:NSUTF8StringEncoding];
if (nil != lastPart && lastPart.length > 0) {
message = [message stringByAppendingString:lastPart];
}
if (nil != self.listener) {
[self.listener stickReader:self didReadTag:message];
}
[_data setLength:0];
}
}
}
// Method that ensures that the CBCentral knows when a notification state for a given characteristic changes. Track it in order to understand when a characteristic state changes (update app values). You should check if the characteristic notification has stopped. If it has, you should disconnect from it:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (![self.characteristics containsObject: characteristic.UUID]) {
return;
}
if (characteristic.isNotifying) {
NSLog(#"Notification began ");
} else {
// Notification has stopped
NSLog(#"Notification stopped ");
//[_manager cancelPeripheralConnection:peripheral];
}
}
/////////////////////////////////////////////////////////////////////////////////////
- (void) port: (SDLSerialPort*) serialPort event : (SPEvent) ev error: (NSInteger)err {
if (SP_EVT_OPEN == ev) {
//NSLog(#"serialPortOpened");
configString = [[NSMutableString alloc] init];
[serialPort write: [#"c\r" dataUsingEncoding: NSUTF8StringEncoding]];
} else {
NSLog(#"serialPortClosed");
}
}
- (void) writeComplete: (SDLSerialPort*) serialPort withError: (NSInteger)err {
NSLog(#"writeComplete");
}
- (void) port: (SDLSerialPort*) serialPort receivedData: (NSData*)data {
if (data.length > 0) {
NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString: #"|()#"];
NSString *str = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"receivedData: %#", str);
if ([str hasPrefix: #"Shearwell"]) {
_detail = str;
//NSLog(#"updating: %#", str);
[[SDLStickReader manager] addedShearwellStickReader: self];
} else if (NSNotFound != [str rangeOfCharacterFromSet: charSet].location ) {
// Data
if (waitingForConfigInfo) {
[configString appendString:str];
if (NSNotFound != [configString rangeOfString: #"(cS)"].location) {
// got end of config string, parse config.
NSRange found = [configString rangeOfString: #"#18|"];
if (NSNotFound != found.location) {
NSRange getRange = NSMakeRange(found.location + found.length, 1);
NSString *str = [configString substringWithRange: getRange];
self.eidFormat = [str integerValue];
NSLog(#"StickReader format: %d", self.eidFormat);
}
waitingForConfigInfo = NO;
[serialPort write: [#"v\r" dataUsingEncoding: NSUTF8StringEncoding]];
}
} else {
// Users data
[self.listener stickReader:self didReadData: str];
}
} else {
// Tag
if (nil != self.listener) {
[self.listener stickReader:self didReadTag: str];
}
}
}
}
and my SDLBluetoothConnectViewController.m
#import "SDLBluetoothConnectViewController.h"
#import "SDLStickReaderManager.h"
#import "SDLStickReader.h"
#import "SDLViewController.h"
#interface SDLBluetoothConnectViewController () <UITableViewDataSource, UITableViewDelegate, SDLStickReaderManagerListener>
#property (weak, nonatomic) IBOutlet UITableView *stickReaderListView;
#property (strong, nonatomic) IBOutlet UILabel *stickReaderNameLabel;
#property (strong, nonatomic) IBOutlet UILabel *stickReaderDescLabel;
#end
/*
This class starts the process of scanning in the 'viewDidLoad' method by setting the SDLStickReaderManager 'singleton' to self. When the scan button is pressed the SDLStickReaderManager scan is called, and each stick reader found is returned via the listener call. This listener call is used to refresh the table view.
When a user selects a stick reader from the list the StickReader view is started and passed the selected stick reader.
*/
#implementation SDLBluetoothConnectViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.stickReaderListView setDelegate: self];
[self.stickReaderListView setDataSource: self];
[SDLStickReader manager].listener = self;
[self.stickReaderListView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)scanButtonPressed:(id)sender {
[SDLStickReader.manager scan];
}
- (IBAction)cancelButtonPressed:(id)sender {
SDLStickReader.manager.listener = nil;
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)viewWillDisappear:(BOOL)animated {
/*
Every time the view disappears, you should stop the scanning process.
*/
[SDLStickReader.manager stopScan];
}
/////////////////////////////////////////////////////////////////////////////////
/////// Table handling //////////////////////////////////////////////////////////
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *stickReaders = SDLStickReader.manager.discoveredStickReaders;
SDLStickReader * stickReader = [stickReaders objectAtIndex: indexPath.row];
[SDLStickReader.manager stopScan];
[self performSegueWithIdentifier:#"deviceInfoSegue" sender:self];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *stickReaders = SDLStickReader.manager.discoveredStickReaders;
return stickReaders.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: simpleTableIdentifier];
if (nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: simpleTableIdentifier];
}
NSArray *stickReaders = SDLStickReader.manager.discoveredStickReaders;
SDLStickReader * stickReader = [stickReaders objectAtIndex: indexPath.row];
NSString *desc = [stickReader name];
cell.textLabel.text = desc;
return cell;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"deviceInfoSegue"]) {
//some additional info here
}
}
////////////////////////////////////////////////////////////////////////////////// /
/// SDLStickReaderManager callbacks ///////////////////////////////////////////////
-(void)stickReader: (SDLStickReader *)stickReader addedToList: (NSArray *)discoveredStickReaders {
//NSLog(#"View StickReaderAdded");
[self.stickReaderListView reloadData];
//[SDLStickReader.manager connect: stickReader];
}
-(void)connectedStickReader: (SDLStickReader *)stickReader {
//NSLog(#"View StickReader connected");
[self.stickReaderListView reloadData];
}
-(void)disconnectedStickReader: (SDLStickReader *)stickReader {
//NSLog(#"View StickReader disconnected");
[self.stickReaderListView reloadData];
}
Have a question about blocks in objective-c.
For example I have a list of actions.
I'm initializing an array of blocks:
self.actions = #[
^() { [self showObject:self.object_1]; },
^() { [self showObject:self.object_2]; },
^() { [self showObject:self.object_3]; }
];
And calling them when some row is pressed:
- (void)pressedRowAtIndex:(NSInteger)index {
if (index < actions.count) {
void (^action)() = [actions objectAtIndex:index];
if (action != nil) {
action();
}
}
}
And all works fine without problem. But when I init my actions array by using initWithObjects method:
self.actions = [NSArray alloc] initWithObjects:
^() { [self showObject:self.object_1]; },
^() { [self showObject:self.object_2]; },
^() { [self showObject:self.object_3]; },
nil
];
Than I get crash trying to get action by index by using objectAtIndex method of NSArray class.
I understand the difference between this inits. First one don't increase reference count like first do. But can someone explain why it crash?
Edit:
All that I've found. Maybe I'm nub and somewhere else is another useful information.
There is no crash info in terminal:
Code for Onik IV:
Small example:
#interface ViewController () {
NSArray *actions;
}
#property (nonatomic, strong) NSString *object1;
#property (nonatomic, strong) NSString *object2;
#property (nonatomic, strong) NSString *object3;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
actions = [[NSArray alloc] initWithObjects:
^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() {[self showObject:self.object3]; },
nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.object1 = #"object 1";
self.object2 = #"object 2";
self.object3 = #"object 3";
void(^firsSimpleBlock)(void) = [actions lastObject];
firsSimpleBlock();
void(^simpleBlock)(void) = [actions firstObject];
simpleBlock();
}
-(void)showObject:(NSString *)object
{
NSLog(#"Show: %#",object);
}
#end
Try something like this.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
(^someBlock)(void) = ^void(void){
self.object1;
};
actions = [[NSArray alloc] initWithObjects:
[someBlock copy],
[someOtherBlock copy],
[anotherBlock copy],
nil];
}
Blocks are allocated on the stack and are therefor removed when the frame is removed from the stack leading to sail pointers for all pointers pointing to that block. When you allocate a object with the literal "#" sign the object is allocated in a pool so all literals that are the "same" point to the same instance and are never deallocated.
NSString *a = #"A";
NSString *b = #"A";
points to the same instance of a string, while:
NSString *a = [NSString stringWithFormat:#"A"];
NSString *b = [NSString stringWithFormat:#"A"];
are two different objects.
So it works when you are creating a literal array but when you add the blocks dynamically they will be removed when its time to use them therefor the BAD_ACCESS. Solution is to send "copy" message to the block that will copy it to the heap and the block will not be released.
It´s the same, you must have another kind of problem (sintax?).
Try this:
#interface ViewController ()
#property (nonatomic, strong) NSString *object1;
#property (nonatomic, strong) NSString *object2;
#property (nonatomic, strong) NSString *object3;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.object1 = #"object 1";
self.object2 = #"object 2";
self.object3 = #"object 3";
NSArray *actions = #[^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() {[self showObject:self.object3]; }
];
NSArray *secondActions = [[NSArray alloc] initWithObjects:
^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() { [self showObject:self.object3];},
nil
];
void(^firsSimpleBlock)(void) = [actions lastObject];
firsSimpleBlock();
void(^simpleBlock)(void) = [secondActions firstObject];
simpleBlock();
}
-(void)showObject:(NSString *)object
{
NSLog(#"Show: %#",object);
}
#end
I'm working with NSCache in Objective-C and Cocoa for iOS. Every time I restart the project, the getCacheRecommend call returns null and I expect it to return a value.
#import <Foundation/Foundation.h>
#class ASJsonDiscoverModel;
#interface ASUserCache : NSObject
+ (ASUserCache *)sharedInstance;
- (void)clear;
- (void)setCacheRecommend:(ASJsonDiscoverModel *)discover;
- (ASJsonDiscoverModel *)getCacheRecommend;
ASJsonDiscoverModel is my custom object class.
#import "ASUserCache.h"
#interface ASUserCache ()
#property (nonatomic,strong) NSCache *cache;
#end
#implementation ASUserCache
+ (ASUserCache *)sharedInstance
{
__strong static ASUserCache *cache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [[ASUserCache alloc] init];
});
return cache;
}
- (instancetype)init
{
if (self = [super init]) {
_cache = [[NSCache alloc] init];
}
return self;
}
- (void)setCacheRecommend:(ASJsonDiscoverModel *)discover
{
NSString *key = #"channelRecommend";
[_cache removeObjectForKey:key];
[_cache setObject:discover forKey:key];
}
- (ASJsonDiscoverModel *)getCacheRecommend
{
NSString *key = #"channelRecommend";
return [_cache objectForKey:key];
}
- (void)clear
{
if (_cache) {
[_cache removeAllObjects];
}
}
- (NSString *)keyforUserID:(NSString *)userID
{
return [NSString stringWithFormat:#"**%#",userID];
}