Constants in Objective-C and "duplicate symbol" linker error - ios

I've declared a constant with the same name in some different classes, in their .m file, this way:
#implementation MyViewController
const NSInteger numberOfItems = 6;
...
#end
But I get a "duplicate symbol" error when trying to build the project. I've found several posts dealing with this issue regarding extern or global constants, but what I'd want is just declaring some constants private to their class, how can I do that?
Thanks

If you want to use constant only in one .m file then declare it as static.
For example:static NSString * const CONSTANT_STRING = #"Constant I am".
In case of NSInteger you can write in your every .m file:
static const NSInteger my_const = 3;
If you want globals (one constant with one value visible in every file) then write in your .h:
extern const NSInteger my_global_const;
and in your .m file you can add
const NSInteger my_global_const = 5;

Related

Override a static constant int in .h file in Objective-C?

I'm trying to build an app in OC and have a constant in .h file like this that defines how many columns should be on a menu:
// cellManager.h
static int const cellNumberPerRow = 4;
Now in my view manager file(.m), I need to change the number of columns to 3 when the font size changes. So far I have tried:
// menuManagerView.m
if ([self isBigFontSize]) {
// ....
cellNumberPerRow = 3;
// ...
But this gives me an error Cannot assign to variable 'cellNumberPerRow' with const-qualified type 'const int'; And when I tried to add identifier before like this:
static int const cellNumberPerRow = 3;
There is a warning Unused variable 'cellNumberPerRow' and the column number remains 4;
I feel like there should be an elegant way to do this but couldn't find it anywhere. I'm really new to iOS dev so would appreciate anyone's input, Thanks!
UPDATE
I defined a new integer variable, assigned the value of const variable to it, and replaced all old cellNumberPerRoe with the new variable in the .m file. Now it worked. But I wonder if there's any better way to do this?
static int newCellNumberPerRow = cellNumberPerRow;
If you want to change the value of a constant under some circumstances it implies it's no longer a constant. You could circumvent the const compiler check by using a pointer, but that's counterproductive. It'd be much easier to just drop const from definition altogether.
What I would suggest as an alternative is a computed property in your file manager class defined like this:
typedef NS_ENUM(int, CellNumberPerRow) {
defaultCellNumberPerRow = 4,
smallerCellNumberPerRow = 3,
};
#interface YourManager: NSObject
#property(readonly,nonatomic) int currentCellNumberPerRow;
#end
#implementation DocumentItem
-(int)currentCellNumberPerRow {
if ([self isBigFontSize]) {
return smallerCellNumberPerRow;
}
return defaultCellNumberPerRow;
}
#end
Now to get the appropriate cell number per row you would use currentCellNumberPerRow property instead.
Perhaps making the currentCellNumberPerRow a class property #property(class,...) instead of instance property could also turn out convenient.

Object-C Linker command failed with exit code 1

I'm very new to Object-C programming and got an assignment. While building , the build failed with linker error due to duplicate symbols. I did my own analysis seems like it broke because , in one of the class file we have this :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return kCellHeight;
}
And in another class we have this:
self.contentView.frame = CGRectMake(5,5,[[UIScreen mainScreen] applicationFrame].size.width-10,kCellHeight - 5);
where as the kCellHeight has been defined in an interface as :
NSInteger const kCellHeight = 100;
Is that correct analysis?
Development Env:
XCode: 7.3.1
Target SDK : 7.1
Thanks in advance..
-S
Perhaps (although it would be easier to tell if you gave the compiler error). I assume that when you say "kCellHeight has been defined in an interface" you mean in a header file? Constants should be declared in a header file, but defined in an implementation file. Like this:
/* in .h file */
extern const NSInteger kCellHeight;
/* in .m file */
const NSInteger kCellHeight = 100;
If defined in a header file, kCellHeight will be defined whenever that header file is imported, which will result in multiple definitions (and a linker error).

In iOS how to use NS_ENUM?

I know that such questions are asked before. I looked on the net find examples but I can not figure out how to use NS_ENUM in my code.
I have a list of error codes. To manage them I want to use NS_ENUM. I create a class which contains all my public const for the application. I want to define my enum here and use it everywhere in the app.
My code in .h file:
typedef NS_ENUM(NSInteger, wsErrEnum);
In my .m file:
typedef NS_ENUM(NSInteger, wsErrEnum) {
ERR_NONE = 0,
ERR_HOST_TIMEOUT = 1
};
I thought that I can check in the following manner:
if(result.ErrCode == wsErrEnum.ERR_NONE) {
// do stuff ...
}
, but ERR_NONE is not visible as property.
So my question is how to define and use NS_ENUM in this case.
EDIT:
Using
if(result.ErrCode == ERR_NONE) {
NSLog(#"It is OK!");
}
give me error that ERR_NONE is undefined.
EDIT 2:
I understand the problem. I declare NS_ENUM in my .h file (as some comments and answers suggest) and the error disappears. I try this before but in that case I did not use enum properly - I use is as wsErrEnum.ERR_NONE. 10x for help.
P.S. My class is #imported in the .m file where I try to make this comparison.
The only problem here is that you've declared the enum's values in your .m file.
You should declare the entire enum (values included) in your .h file. By declaring the enum type wsErrNum in your .h file and the values in your .m file, you've hidden the values from all other classes.
So, in your .h file:
typedef NS_ENUM(NSInteger, wsErrEnum) {
ERR_NONE = 0,
ERR_HOST_TIMEOUT = 1
};
Nothing needs to go in your .m file to declare this enum.
In Swift :
if result.ErrCode == wsErrEnum.ERR_NONE {
// do stuff ...
}
In Objective-C :
if(result.ErrCode == ERR_NONE) {
// do stuff ...
}
The real issue is that you have split up the declaration of wsErrEnum, remove it from the .m file and place the full delcaration in the .h:
typedef NS_ENUM(NSInteger, wsErrEnum) {
ERR_NONE = 0,
ERR_HOST_TIMEOUT = 1
};
But better is something like:
typedef NS_ENUM(NSInteger, wsErrEnum) {
wsErrEnumName = 0,
wsErrEnumTimeOut = 1
};
Just a small remark, properties like your ErrCode should not started with a capital letter.
Normally, I use Enumerations in objective-c as typedef enum. Doing like this, you don't need an implementation file (.m) and the code looks like this:
// Enumerations.h
#interface Enumerations
typedef enum:int {
ERR_NONE,
ERR_HOST_TIMEOUT
} wsErrEnum;
#end
By default, the compiler values the enumerations from 0 to N, in order of which they are declared. In the example above, ERR_NONE is 0 and ERR_HOST_TIMEOUT is 1.
But you could also value the enumerations the way you preffer:
typedef enum:int {
ERR_NONE = 0,
ERR_HOST_TIMEOUT = 504
} wsErrEnum;
Then, you could work with switch and it would consider your enumeration, so it throws a warning in case you don't implement one possible result, for example:
switch(result.ErrCode) {
case ERR_NONE:
//implementation
break;
}
The code above will warn you that you don't have a case implemented for ERR_HOST_TIMEOUT.
You can also import your Enumerations.h in the file YourProjectName-Prefix.phc so it can be used in your hole project.
//
// Prefix header
//
// The contents of this file are implicitly included at the beginning of every source file.
//
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Enumerations.h" //Add this line so you don't have to import your .h file every time you use it
#endif

Linker Command Failed with exit code 1: duplicate symbol - no .m file

I've seen this issue in other posts but in my case I am using a sphere.h file that contains the data of vertices of a sphere, that is, a 3D model for my game project. I import my sphere.h file into my objective-C Class as follows:
#import "SceneEnergyGlobe.h"
#import "sphere.h"
#interface SceneEnergyGlobe() {
}
#property (strong, nonatomic) GLKTextureInfo *textureInfo0;
#end
.....
When I compile my project, I receive this compile error. How I can fix this?
Here is the contents of sphere.h:
#ifndef SPHERE_HEADER
#define SPHERE_HEADER
unsigned int sphereNumVerts = 2280;
float sphereVerts [] = {
0.0743889747124915, -0.49384436095363, -0.0241703260695731,
0.190555012144643, -0.979722062440628, -0.0619150039460291,
0.000000, 0.95,
0.0632787269334132, -0.49384436095363, -0.0459747512867777,
0.162096012330563, -0.979722074526971, -0.11776900895863,
0.050000, 0.95,
0.125000004921036, -0.475528075643002, -0.0908176095396332,
0.269869905435848, -0.942722669663907, -0.196071931295133,
.....
For every implementation file (.m) that includes sphere.h a new copy of the vertices array will be created. When these implementation files are linked together you get your duplicate symbol error.
The best approach is to change this in sphere.h:
#define sphereNumVerts 2280
extern float sphereVerts[sphereNumVerts];
And add an implementation file (sphere.m) that contains the definition of sphereVerts:
#import "sphere.h"
float sphereVerts[sphereNumVerts] = {
0.0743889747124915, -0.49384436095363, -0.0241703260695731,
...
};
BTW, those look like big numbers for float; are you sure you don't want double?
(EDIT: My previous suggestion was wrong; you'll get duplicate symbols using const in a header file too. I have changed this to a #define).
It is a very very very bad idea to create and initialize variables in .h files!!!

get error when to use global Variable

I have 2 class in my program
first class is class1 and second class is class2.I want create and initialize global variable in class 1 and to use in class 2 but compiler give me this ERROR XD :
Undefined symbols for architecture i386:
"_saeid", referenced from:
-[class2 viewDidLoad] in class2.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I create global variable in class1 and run that in class2 with this way but don't work:
class1.h
extern int saeid; // this is global variable
#interface class1 : UITableViewController<UITableViewDataSource,UITableViewDelegate>
#property (nonatomic,strong) IBOutlet UITableView *table;
#end
class1.m
#import "class1.h"
#import "class2.h"
#implementation class1
{
int saeid;
}
#synthesize table;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int x = (indexPath.row)+1;
saeid = x; //initialize global variable
NSLog(#"X & SAEID: %d & %d",x,saeid);
}
class2.h
#import "class1.h"
#interface class2 : UIViewController<UIScrollViewDelegate>
{
}
#end
class2.m
#import "class2.h"
#implementation class2
{
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Saeid in class2 : %d",saeid);
}
There seems to be some confusion here. Most importantly, a global variable cannot be "in" a class--global variables are by definition outside of any classes. So if you really want a global variable (more on this later) then you need to take the int saeid; definition in class1.m outside of the class definition, and just have it at the file level.
After you've done that, things still won't compile. The statement extern int saeid; roughly says to the compiler "I've defined an integer named saeid somewhere else, so just pretend it exists and let the linker figure out how to hook it up." There is no reason to have this statement in class1.h because this global variable is not used anywhere in that file. Instead, you should put this extern statement near the top of class2.m. It is used in that file, so you need to assure the compiler that the variable is defined somewhere as it is compiling that file.
Those steps should get your code to compile. But now you should stop and think about whether or not you really want a global variable. Global variables tie your classes together and make it hard to change one without affecting (and possibly breaking) others. They make it harder to test your code, and they make it more confusing to read your code. Another option to consider here is to create saeid as a property on the class1 class, and add a class1* property to class2. Then when you create your class2 instance, pass along a pointer to the existing class1 instance. The class2 instance can keep that pointer and use it to access the saeid property as needed.
In Objectice-C you can't have class variables, just instance variables.
If you want to have a global var you'd write:
#import "class1.h"
#import "class2.h"
int saeid;
#implementation class1
{
}
#synthesize table;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int x = (indexPath.row)+1;
saeid = x; //initialize global variable
NSLog(#"X & SAEID: %d & %d",x,saeid);
}
But that's just a global var, it's got nothing to do with the class!

Resources