I'm trying to make a key for use in objc_setAssociatedObject, as in this question:
How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
I have a MyConstants.h file which defines
static NSString *myConstant = #"MyConstant";
This file is then included in MyFramework via MyFramework.h
#import ...;
#import "MyConstants.h"
#import ...;
Then in my project, the framework is included in all files via the .pch header.
#import <Cocoa/Cocoa.h>
#import <MyFramework/MyFramework.h>
This all works as expected. The problem is that when I call objc_setAssociatedObject or objc_getAssociatedObject with myConstant as the key, it doesn't work. The reason is clear enough from the log:
- (void)doSomething:(id)key { //Gets called with myConstant
NSLog(#"Associating with key at %p", &key);
objc_setAssociatedObject(self, &key, object, policy);
}
- (void)doSomethingElse:(id)key { //Gets called with myConstant
NSLog(#"Seeking association for key at %p", &key);
NSLog(#"Will find %#", objc_getAssociatedObject(self, &key));
}
Which logs:
Associating with key at 0x7fff5fbfecdf
Seeking association for key at 0x7fff5fbfecef
Will find (null)
You'll notice that the pointers shift.
However, calling the same method again shows the same pointers. It's as if the constant IS a constant, in the file that imports it, but not for the whole project.
Q: How can I correctly define and import headers with constants so that a constant is actually at a constant place in memory?
If two files have static char something, each one will have its own copy--they do not represent the same piece of memory. If you want two files to have access to the key, you must instead do this in your header:
extern const char MyConstantKey;
And this in one implementation file (.c or .m):
const char MyConstantKey;
This defines a single one-char-wide location in memory, to which all files will point when you use &MyConstantKey.
I have edited the original post to show what I think the error was. The other poster's answer may well have also been an excellent solution, but how I resolved the problem was like so:
As the &key indicates, I was passing key directly to the methods, as in myMethod:(id)key. Somehow, de-referencing this pointer was different in different cases. By simply changing all the methods to myMethod:(void *)key and passing in &key, the problem immediately went away. Or ensuring I was using a static char and not a static NSString *
I don't understand it fully, but it works.
Related
I am a newbie Objetive C developer and I am trying to fill up a vector which it is a variable property... But It doesn't working, I can not understand why or how to fix it
-(void) setUpLabels:(NSString *) _labels_path {
// Read the label list
self.label_strings.clear();
std::vector<std::string> new_values;
std::ifstream t;
t.open([_labels_path UTF8String]);
std::string line;
while (t) {
std::getline(t, line);
new_values.push_back(line);
}
self.label_strings.reserve(new_values.size());
for (int index = 0; index < new_values.size(); index++) {
self.label_strings.push_back(new_values[index]);
}
NSLog(#"size=%lu",self.label_strings.size());
t.close();
}
This is the .h file
#interface AIClassifier: Classifier
#property std::vector<std::string> label_strings;
- (id) init;
- (id) initWithParams:(NSString *)model:(NSString *) labels;
#end
The function setUpLabels is not in the file..
All the time is printing size = 0. I tried more simple versions with the same result.
One problem with this code is that you use #property and self.label_strings. #property creates a getter method that returns a value of type std::vector<std::string>. This is a value, not a pointer/reference, so it will be a new copy of the underlying object. It means that each time you call self.label_strings.push_back(x) or any other method, it operates on a copy of the initial object (which is always empty).
To solve this use _label_strings instead of self.label_strings, or use an instance variable for label_strings instead of a #property:
#interface AIClassifier: Classifier {
std::vector<std::string> label_strings;
}
#end
(Note: better move this to be a private instance variable in your .mm file)
If there's no particular reason to use C++ for this task, there are several Objective-C ways to read a file line by line.
If the file is small, you can read it completely into memory and then split into separate lines like here: https://stackoverflow.com/a/4466934/1009546
In Objective-C we use NSArray<NSString*>* instead of std::vector<string>.
Another likely reason you don't get anything is that either the _labels_path file path is wrong or that it doesn't exist on device.
You can set a breakpoint in this function and use Xcode step by step debugger to see what actually is going on and what you have in _labels_path and new_values at the time of execution.
Normally you shouldn't normally have to use C++ in Objective-C, especially not in interfaces. If you make your file ".m", and not ".mm", it will not allow C++ inside (only Objective-C).
Note 1: if you stick with C++, you don't need a second loop. Just assigning self.label_strings = new_values; will copy it (this is C++ magic).
Note 2: always check the file after opening it. In Objective-C you normally have NSError* returned from functions. In C++ you can check the failbit (see open docs example).
Note 3: Ideally use RAII to make sure that the file is closed. In this case t.close(); is not needed, because ifstream destructor will close it (see http://www.cplusplus.com/reference/fstream/ifstream/close/).
I read this answer https://stackoverflow.com/a/833124/5175709 and some other questions. From what I understand it's that since the object could expand and run out of space then the memory location my also change. I never understood why we shouldn't use ** for NSMutableData or NSMutableArray since they could also expand with any adding or appending and be in need of more space.
Please correct me where I understood wrong. I am confused.
NSMutableArray * and NSMutableData * are not pointers directly to the contained data. They are something more along this line:
struct MutableData {
void * data;
void (*appendBytes)(void * bytes);
// etc.
};
When they need to reallocate for expansion (or contraction), there is an internal pointer that they are manipulating. You, as a user of the containing instance, don't interact with that.
Object references are only required to be single pointers as it does not relate to the memory they manage. For example the NSData class (the super class of NSMutableData) probably contains the following instance variables:
(this is conjecture as I don't have access to the source code)
#interface NSData : NSObject
{
void *_data;
NSUInteger _length;
}
#end
If the NSMutableData subclass wants to add to the data buffer it can perform a realloc() on _data and increase _length.
The pointer to the NSMutableData object itself does not change at all and the memory it manages is hidden from the developer.
In my project I am working on it since last 6 months , i was using #define to define all URL in a FileManager class.but Now I came to know that I have to use two different URL depend on condition so dynamically I can not change #define values.so I decided to Use extern variables in FileManager class instead of #define.
as
in interface file
extern NSString *Url;
extern NSString *loginUrl;
in m file
#implementation UrlManager
+ (void) initialize{
Url=#"http://xxxxxxxxxx.com";
loginUrl=[NSString stringWithFormat:#"%#%#",Url,#"/ipad.php?method=audLogin&username="];
}
but when I am importing URLManager.h , and using loginUrl, still its null , please help me how can I initialize extern variables so that I can use these in app.
First, if you have a URL, use NSURL to reference it.
Secondly, name your globals consistently and descriptively. AliRootURL and AliLoginURL would be descriptive and has a prefix (Ali -- change it) to make it easy to recognize.
Finally, don't use globals at all. Instead, have class methods on your URLManager class (which really should be prefixed, too) that return the URLs:
#interface AliURLManager:NSObject
+(NSURL*)rootURL; // no need to prefix methods in your custom classes
+(NSURL*)loginURL;
#end
That way, you can implement those methods to initially return URLs directly:
+(NSURL*)rootURL
{
// use a static and dispatch_once() if you want only one instance
return [NSURL URLWithString:#"http://foobar.com/"];
}
+(NSURL*)loginURL
{
NSURL* root = [self rootURL];
NSURL* loginURL;
... calculate loginURL here... (http://stackoverflow.com/questions/718429/creating-url-query-parameters-from-nsdictionary-objects-in-objectivec)
return loginURL;
}
That way, you could easily refactor your code to, say, read URLs from the defaults database or have a different configuration for development.
Bummer -- but I can understand the desire to not change lots of files. Should be search and replaceable, though (at least, would be if your variables were a bit more descriptive).
If you are going to initialize the globals via +initialize, you need to ensure that the class is tickled. Add [URLManager class]; to your applicationDidFinishLaunching:
if you don't take #define into account, there are two notorious ways for declaring constants in objective-c project:
// Util.h
extern NSString * const MyConstant;
// Util.m
NSString * const MyConstant = #"value";
or the other directly in header file
// Util.h
static NSString *const MyConstant = #"value";
Now, there comes two questions:
1)
Both works, the second method is quite convenient, as I have only one place for editing values.
However as I saw from Apple .h files, the first method is always preferred, and I wonder if there are any downside, with the static method.
2) Looking at Apple docs, we often encounter very long constant name like: NSTextInputContextKeyboardSelectionDidChangeNotification. In the case you used a long constant name like that, what convention would you generally use to assign a value. If I want to use something descriptive I could use #"nsTextInputContextKeyboardSelectionDidChangeNotification", but sounds little odd.
I won't explain you all the specifics of the two types of constant declaration - in short:
The first one separates constant declaration from constant definition. Only declaration is included by header files. It also conveniently hides the actual value of the constant.
The second one is more problematic - the header file contains both declaration and definition of the constant. That means that whenewer you include the header, the constant is created again. I don't think this will work correctly when included from multiple files.
The second question - no problem with long constant names. Your example is a bit extreme but there is nothing wrong with it.
EDIT: Adding more information about the static NSString* const in header.
Let's have a header A.h:
//A.h
static NSString *const MyConstant = #"value";
and file including it A.m
//A.m
#import "A.h"
printA() {
NSLog(#"A.h Constant: %#", MyConstant);
}
First note that headers are removed by the preprocessor before compilation. That means that before compilation there won't be any A.h file and A.m will look like this:
//A.m
static NSString *const MyConstant = #"value";
printA() {
NSLog(#"A.h Constant: %#", MyConstant);
}
Let's create another constant header B.h and implementation file B.m with exactly the same contents:
//B.h
static NSString *const MyConstant = #"value2";
//B.m
#import "B.h"
printB() {
NSLog(#"B.h Constant: %#", MyConstant);
}
Note that the constant is declared twice, with the same name and different values. This is possible because static makes the constant private for the file that includes it. If you remove the static, you'll get a compilation error because the compiler will find two public global constants with the same name.
In theory it is possible to use static NSString* const in header files and everything will work correctly but as you can see, it doesn't do exactly what you want and can be a source of difficult-to-find bugs. That's why you should use static only from implementation files.
Just to add - the second way that you have shown doesn't make much sense.
If you are going to declare and define a constant in one place such as this, it's usually done in the .m file and not the .h file.
You do it this way for constants that are only going to be used in the class itself and not exposed.
I have a large constant (an NSString with 10^6 values). Because of its size I would like to declare it at the end of the source file (so I don't have to scroll through it every time I want to edit my code). Also because of its size I would like it to be a constant so I can load it at compile time instead of runtime. Also, because I do not want it accessible to outside users I do not want to declare it as extern in the header file.
I have it declared as a constant using the code below in the implementation file, however it is giving me a "Use of undeclared identifier 'hugeConstantString'" if I move it past the #end of the implementation (for obvious reasons).
NSString *const hugeConstantString = #"a_whooooooole_lotta_characters";
I've checked this out: Constants in Objective-C but it didn't tell me anything I didn't know already. Maybe my brain is fried, but: is there any way that I can define this huge constant AFTER my implementation and still have it accessible? Or if I declare it in another header file and import it, will it then be accessible to others?
Thanks!
I'm not sure such a large string is a good idea, but if you're going to use it, I suggest putting it in its own header file.
MyLongStringConstant.h
#define kLongString #"..."
MyClass.h
....
#import "MyLongStringConstant.h"
...
//Do something with kLongString
...
If you want to have it accessible in every file of your app, import the header inside your apps myApp_Prefix.pch file, which is imported into every file.
I am going to save the conversation of Why are you doing that and just post a simple solution for you. Thanks to Tommy in the comments here is a simpler version.
#import "LargeStringTest.h"
#implementation LargeStringTest
//Declare the string
static NSString *hugeConstantString;
- (id)init {
self = [super init];
if (self) {
NSLog(#"Large String %#", hugeConstantString);
}
return self;
}
//Place all other code here
//Assign the string
static NSString *hugeConstantString = #"a_whooooooole_lotta_characters";
#end