iOS(Objective-C). Application crash when getting block from array - ios

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

Related

EXC_BAD_ACCESS objective-c block

I run this code inside my viewDidLoad method to fetch data from Firebase to put it in a UIPageViewController
#interface MapViewController () <RoutesPageDelegate>
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (weak, nonatomic) RoutesPageViewController *routesPageViewController;
#property (weak, nonatomic) FIRFirestore *db;
#end
#implementation MapViewController
- (void) viewDidLoad {
[super viewDidLoad];
self.db = [FIRFirestore firestore];
for (UIViewController *obj in self.childViewControllers) {
if ([obj isKindOfClass:[RoutesPageViewController class]]) {
self.routesPageViewController = (RoutesPageViewController *)obj;
self.routesPageViewController.routesPageDelegate = self;
}
}
FIRCollectionReference *routesRef = [self.db collectionWithPath:#"routes"];
[routesRef getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (error != nil) {
// TODO: handle error
} else {
NSMutableArray<RouteModel*> *routes = [NSMutableArray array];
// For each route
for (FIRDocumentSnapshot *document in snapshot.documents) {
RouteModel *route = [[RouteModel alloc] init];
route.title = document.data[#"title"];
route.color = document.data[#"color"];
route.city = document.data[#"city"];
[routes addObject:route];
}
[self.routesPageViewController setRoutes:routes];
}
}];
}
And this is the called setRoutes method:
- (void) setRoutes:(NSMutableArray<RouteModel *> *)routes {
self.routes = routes;
NSMutableArray<RoutePageViewController *> * routeViewControllers = [NSMutableArray array];
for (RouteModel * route in routes) {
[routeViewControllers addObject:[self viewControllerAtIndex:[routes indexOfObject:route]]];
}
[self setViewControllers:routeViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
When the setRoutes method gets executed it throws the error in the image below, saying that it cannot dereference it:
The setRoutes methods gets executed inside a block.
And I get this weird thread stack:
How can I solve this?
Your problem is here:
- (void) setRoutes:(NSMutableArray<RouteModel *> *)routes {
self.routes = routes;
invoking self.routes implicity calles the setter setRoutes which causes a recursive infinite calls as indicated by your stack.
At the time the block is passed onto getDocumentsWithCompletion method is executed, routes array has already been deallocated and set to nil because no one is retaining it anywhere outside the block.
You should either move it into the block or declare it as a class property so it won't be thrown out of memory while class instance is alive.
[routesRef getDocumentsWithCompletion:^(FIRQuerySnapshot *snapshot, NSError *error) {
NSMutableArray<RouteModel*> *routes = [NSMutableArray array];
...
[self.routesPageViewController setRoutes:routes];
}];
After the update:
Doing self.routes = routes invokes setRoutes: which in turn is causing a loop in your code. You should change it to:
- (void)setRoutes:(NSMutableArray<RouteModel *> *)routes {
if (_routes != routes) {
_routes = routes;
...
}
}

How to work with one array in two methods

How can i insert value in array in one method and work with this full of value array in other method.
-(void)firstMethod {
NSMutableArray *array = [[NSMutableArray alloc] init];
../// add some value in array.
}
-(void)secondMethod {
..// here i want to work with array which consist of value from first method.
}
You can create one instance of NSMutableArray and use that in the both method.
#interface ViewController () {
NSMutableArray *array;
}
#end
Now access this array in both methods
-(void)firstMethod {
array = [[NSMutableArray alloc] init];
[array addObject:#"Hello"];
}
-(void)secondMethod {
if (array) {
[array addObject:#"World"];//Add object that you want
}
else {
array = [[NSMutableArray alloc] init];
[array addObject:#"World"];
}
}
You can declare a NSMutableArray property
#interface ViewController ()
#property(nonatomic,strong)NSMutableArray *array;
#end
create instance in init method
- (instancetype)init{
if(self == [super init]) {
_array = [NSMutableArray array];
}
return self;
}
-(void)firstMethod {
[self.array addObject:#"obj1"];
}
-(void)secondMethod {
[self.array addObject:#"obj2"];
}
YOU CAN USE BELOW CODE:
#property (nonatomic, strong) dispatch_queue_t concurrentQueue;
_concurrentQueue= dispatch_queue_create("any String", DISPATCH_QUEUE_CONCURRENT);
- (NSArray *)secondMethod
{
__block NSArray *array;
dispatch_sync(self.concurrentQueue, ^{
array= [NSArray arrayWithArray:array];
});
return array;
}
- (void)firstMethod:(NSString *)str
{
if (str) {
dispatch_barrier_async(self.concurrentQueue, ^{
[array addObject:str];
dispatch_async(dispatch_get_main_queue(), ^{
//Do some asynchronous work
});
});
}
}
Other answers suggest you to declare array as property. But usually it's more clear to just pass needed data as arguments to another method:
-(void)firstMethod {
NSMutableArray *array = [[NSMutableArray alloc] init];
../// add some value in array.
[self secondMethod:array];
}
-(void)secondMethod:(NSArray *)array {
..// here i want to work with array which consist of value from first method.
}

How to add object in singleton NSMutableArray

I used to store the array data downloaded from the server.
But I can not save them in the singleton array.
It seems without access to the object.
Why ulatitude, ulongitude, uaccuracy, uplacename is nil?...
in .h file
#import <Foundation/Foundation.h>
#interface LocationData : NSObject
{
NSMutableArray *ulatitude;
NSMutableArray *ulongitude;
NSMutableArray *uaccuracy;
NSMutableArray *uplacename;
}
#property (nonatomic, retain) NSMutableArray *ulatitude;
#property (nonatomic, retain) NSMutableArray *ulongitude;
#property (nonatomic, retain) NSMutableArray *uaccuracy;
#property (nonatomic, retain) NSMutableArray *uplacename;
+ (LocationData*) sharedStateInstance;
#end
in .m file
#import "LocationData.h"
#implementation LocationData
#synthesize uaccuracy;
#synthesize ulatitude;
#synthesize ulongitude;
+ (LocationData*) sharedStateInstance {
static LocationData *sharedStateInstance;
#synchronized(self) {
if(!sharedStateInstance) {
sharedStateInstance = [[LocationData alloc] init];
}
}
return sharedStateInstance;
}
#end
use
[manager POST:urlStr parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"%#",responseObject);
// json response array
if ([responseObject isKindOfClass:[NSArray class]]) {
NSArray *responseArray = responseObject;
NSDictionary *responseDict = [[NSDictionary alloc] init];
LocationData* sharedState = [LocationData sharedStateInstance];
for(NSUInteger i=0; i < responseArray.count; i++)
{
responseDict = [responseArray objectAtIndex:i];
double dlat = [[responseDict objectForKey:#"lat"] doubleValue];
double dlng = [[responseDict objectForKey:#"lng"] doubleValue];
[[sharedState ulatitude] addObject:[NSString stringWithFormat:#"%f",dlat]];
[[sharedState ulongitude] addObject:[NSString stringWithFormat:#"%f",dlng]];
[[sharedState uaccuracy] addObject:[responseDict objectForKey:#"rad"]];
[[sharedState uplacename] addObject:[responseDict objectForKey:#"place_name"]];
}
You always need to initialize your arrays. You should do somewhere before you try to add something to them:
arrayName = [[NSMutableArray alloc] init];
otherwise you'll always get error because they have not been initialized.
In your case you should override your LocationData init function like this:
- (instancetype)init {
self = [super init];
if (self) {
self.yourArrayName = [[NSMutableArray alloc] init];
// And so on....
}
return self;
}
You need to initialize your object properly. Basically your member variables ("ivars") are pointing to nothing ("nil").
This initializer added to your .m file code do the job.
-(instancetype)init {
if ((self = [super init])) {
self.accuracy = [NSMutableArray array];
self.latitude = [NSMutableArray array];
self.longitude = [NSMutableArray array];
self.uplacename = [NSMutableArray array];
}
return self;
}
As a singleton pattern, I'd prefer the following:
+ (LocationData*) sharedStateInstance {
static LocationData *sharedStateInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedStateInstance = [[LocationData alloc] init];
});
return sharedStateInstance;
}
Although singletons might not be as bad they are often said to be, I don't thing that this is a good usage for them. Your specific problem has nothing to do with that design choice, though.
Try this code. Write getters for your NSMutableArrays.
#import <Foundation/Foundation.h>
#interface LocationData : NSObject
#property (nonatomic, retain) NSMutableArray *ulatitude;
#property (nonatomic, retain) NSMutableArray *ulongitude;
#property (nonatomic, retain) NSMutableArray *uaccuracy;
#property (nonatomic, retain) NSMutableArray *uplacename;
+ (LocationData*) sharedStateInstance;
#end
#import "LocationData.h"
#implementation LocationData
#synthesize uaccuracy = _uaccuracy;
#synthesize ulatitude = _ulatitude;
#synthesize ulongitude = _ulongitude;
+ (LocationData*) sharedStateInstance {
static LocationData *sharedStateInstance;
#synchronized(self) {
if(!sharedStateInstance) {
sharedStateInstance = [[LocationData alloc] init];
}
}
return sharedStateInstance;
}
-(NSMutableArray*)uaccuracy
{
if(_uaccuracy == nil)
{
_uaccuracy = [[NSMutableArray alloc]init];
}
return uaccuracy;
}
-(NSMutableArray*)ulongitude
{
if(_ulongitude == nil)
{
_ulongitude = [[NSMutableArray alloc]init];
}
return ulongitude;
}
-(NSMutableArray*)ulatitude
{
if(_ulatitude == nil)
{
_ulatitude = [[NSMutableArray alloc]init];
}
return ulatitude;
}
-(NSMutableArray*)uplacename
{
if(_uplacename == nil)
{
_uplacename = [[NSMutableArray alloc]init];
}
return uplacename;
}
#end
you don't allocate/init any array...
you can create them in your singleton creation method
+ (LocationData*) sharedStateInstance {
static LocationData *sharedStateInstance;
#synchronized(self) {
if(!sharedStateInstance) {
sharedStateInstance = [[LocationData alloc] init];
sharedStateInstance.ulatitude = [[NSMutableArray alloc] init];
// (add others...)
}
}
return sharedStateInstance;
}
Replace your LocationData.m file with below code , this will work . As you have to alloc and init the array then only you can add object in array
+ (LocationData*) sharedStateInstance {
static LocationData *sharedStateInstance;
#synchronized(self) {
if(!sharedStateInstance) {
sharedStateInstance = [[LocationData alloc] init];
uaccuracy = [[NSMutableArray alloc]init];
ulatitude = [[NSMutableArray alloc]init];
ulongitude = [[NSMutableArray alloc]init];
uplacename = [[NSMutableArray alloc]init];
}
}
return sharedStateInstance;
}

iOS: Viewcontroller subclassed from MWPhotoBrowser-images are not loaded

I am trying to include MWPhotoBrowser in my project
When its used as given in the sample it working fine.
But when a new viewcontroller is subclassed from MWPhotoBrowser, photos are not loaded except empty black theme.
Delegate methods are not getting called. As the controller is subclass of MWPhotoBrowser, I assume there is no need to set it explicitly.
Storyboard is used and the nib class in it is set.
.h file
#interface MDRPhotoViewerController : MWPhotoBrowser
{
NSMutableArray *_selections;
}
#property (nonatomic, strong) NSMutableArray *photos;
#property (nonatomic, strong) NSMutableArray *thumbs;
#property (nonatomic, strong) NSMutableArray *assets;
#property (nonatomic, strong) NSMutableIndexSet *optionIndices;
#property (nonatomic, strong) UITableView *tableView;
#property (nonatomic, strong) ALAssetsLibrary *ALAssetsLibrary;
- (void)loadAssets;
#end
**.m file **
- (void)viewWillAppear:(BOOL)animated
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
NSMutableArray *thumbs = [[NSMutableArray alloc] init];
//mwphotobrowser options setup
BOOL displayActionButton = YES;
BOOL displaySelectionButtons = NO;
BOOL displayNavArrows = NO;
BOOL enableGrid = YES;
BOOL startOnGrid = NO;
BOOL autoPlayOnAppear = NO;
//loading data
NSArray *photosDataArray = [MDRDataController GetPhotos]; //creating array
for (NSString *urlString in photosDataArray) { //Formating the data source for images
NSString *urlFullString = [NSString stringWithFormat:#"%#%#",KBASEURL,urlString];
//Photos
[photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:urlFullString]]];
//thumbs
[thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:urlFullString]]];
}
// Options
self.photos = photos;
self.thumbs = thumbs;
// Create browser
self.displayActionButton = displayActionButton;
self.displayNavArrows = displayNavArrows;
self.displaySelectionButtons = displaySelectionButtons;
self.alwaysShowControls = displaySelectionButtons;
self.zoomPhotosToFill = YES;
self.enableGrid = enableGrid;
self.startOnGrid = startOnGrid;
self.enableSwipeToDismiss = NO;
self.autoPlayOnAppear = autoPlayOnAppear;
[self setCurrentPhotoIndex:0];
// Test custom selection images
// browser.customImageSelectedIconName = #"ImageSelected.png";
// browser.customImageSelectedSmallIconName = #"ImageSelectedSmall.png";
// Reset selections
if (displaySelectionButtons) {
_selections = [NSMutableArray new];
for (int i = 0; i < photos.count; i++) {
[_selections addObject:[NSNumber numberWithBool:NO]];
}
}
self.title = #"Phots";
//[self reloadData];
}
Debugging performed
Considering the image template of mwphotobrowser, tried reloading the code.
Shifted the code between viewwillappear and viewdidload.
Doesn't MWPhotoBrowser support this way or am i doing it wrong ?
For those who stumble upon this later...
If you look at MWPhotoBrowser.m you'll see various initializers:
- (id)init {
if ((self = [super init])) {
[self _initialisation];
}
return self;
}
- (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate {
if ((self = [self init])) {
_delegate = delegate;
}
return self;
}
- (id)initWithPhotos:(NSArray *)photosArray {
if ((self = [self init])) {
_fixedPhotosArray = photosArray;
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
[self _initialisation];
}
return self;
}
The problem is there's no awakeFromNib initializer. Simplest solution is to fork the project and create the awakeFromNib initializer.

Call out of an Array, objectAtIndex

I have an Array with 10 Objects. I take the first Object and put it into my Label with a String.
Then I want to have a Method that increases the objectAtIndex by 1.
This is my Code :
//.m
#interface GameViewController () {
NSInteger _labelIndex;
}
#property (nonatomic) NSArray *playerArray;
#property (nonatomic) NSString *playerLabel;
- (void)viewDidLoad {
[super viewDidLoad];
self.playerArray = [NSArray arrayWithObjects:#"FIRST", #"SECOND", #"THIRD", #"FOURTH", #"FIFTH", #"SIXT", #"SEVENTH", #"EIGTH", #"NINTH", #"TENTH", nil];
_labelIndex = 0;
[self updateTurnLabel];
self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel];
}
Here I call the Method i another Method:
-(void) flipDraws {
self.boardView.userInteractionEnabled = NO;
[self updateTurnLabel];
CardView *cv1 = (CardView *) self.turnedDrawViews[0];
CardView *cv2 = (CardView *) self.turnedDrawViews[1];
}
This is my Method:
-(void) updateTurnLabel {
self.playerLabel = [self.playerArray objectAtIndex:_labelIndex % self.playerArray.count]; _labelIndex++;
}
I tried it with a for Loop but nothing happened. I tried it with just set the objectAtIndex:1 but my Method was not called.
What I am doing wrong?
{
int a;
}
- (void)viewDidLoad {
[super viewDidLoad];
a = 0;
self.playerArray = [NSArray arrayWithObjects:#"FIRST", #"SECOND", #"THIRD", #"FOURTH", #"FIFTH", #"SIXT", #"SEVENTH", #"EIGTH", #"NINTH", #"TENTH", nil];
self.playerLabel = [self.playerArray objectAtIndex:a];
self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel];
}
-(void) updateTurnLabel {
a +=1;
if (!a<[playerArray count])
{
a = 0;
}
self.playerLabel = [self.playerArray objectAtIndex:a];
}
call self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel]; after [self updateTurnLabel];
What are you adding +1 to in the method objectAtIndex:.
You should be maintaining a variable which tracks the current index being used, then in your method use this :
-(void) updateTurnLabel {
self.playerLabel = [self.playerArray objectAtIndex:currentIndex+1];
}

Resources