This is an Objective-C macOS project created with Xcode version 13.2.1. Inside the project, I have a templated class named "plistModifier". The class is meant to set custom types of values for any plist (e.g. NSString, NSNumber etc.). The header file of the class is called "plistModifier.h" and it looks like this:
#ifndef plistModifier_h
#define plistModifier_h
#include <iostream>
#include <string>
#import <Foundation/Foundation.h>
#include <unistd.h>
template <class type>
class plistModifier {
public:
void modifyPref(std::string key, type value);
type getPref(std::string key);
};
#endif /* plistModifier_h */
The implementation of the class is as follows in a seperate "plistModifier.mm" file:
#include "plistModifier.h"
template <class type>
void plistModifier<type>::modifyPref(std::string key, type val) {
NSString* preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/com.rA9.LeetDownPreferences.plist"];
NSDictionary* dict=[[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
[dict setValue:val forKey:[NSString stringWithUTF8String:val]];
[dict writeToFile:preferencePlist atomically:YES];
}
template <class type>
type plistModifier<type>::getPref(std::string key) {
NSString *preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/com.rA9.LeetDownPreferences.plist"];
NSDictionary *dict=[[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
return dict[key];
}
The issue is, when I create a plistModifier object and call it's methods, the compiler throws the error Undefined symbols for architecture x86_64: "plistModifier<int>::modifyPref(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, int)", referenced from: -[SettingsVC debuggingToggle:] in SettingsVC.o
This is how I call the methods of the object in SettingsVC.mm file:
#import "SettingsVC.h"
#include "plistModifier.h"
plistModifier<int> plistObject;
- (IBAction)debuggingToggle:(id)sender {
plistObject.modifyPref("DebugEnabled", _debugToggle.state);
}
I have tried using the same templated class in an empty C++ project, and it didn't give me any errors. What do you think the problem is?
It's not possible to split template declaration (.h) and definition (.mm/.cpp).
You have to keep the template methods implementation in the .h file.
Refer to this explanation
https://isocpp.org/wiki/faq/templates#templates-defn-vs-decl
But since your implementation is not using the generic type type properties, you could split it by implementing this in terms of id type in .mm, and then adding a templated wrapper in .h file:
// .mm
void plistModifierImpl::modifyPref(std::string key, id val) {
NSString *preferencePlist = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/com.rA9.LeetDownPreferences.plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:preferencePlist];
[dict setValue:val forKey:[NSString stringWithUTF8String:val]];
[dict writeToFile:preferencePlist atomically:YES];
}
// .h
class plistModifierImpl; // define this in .mm
template <class type>
class plistModifier {
// private loosely-typed implementation
std::unique_ptr<plistModifierImpl> p_impl;
public:
// public strongly-typed wrapper
void modifyPref(std::string key, type value) {
p_impl->modifyPref(key, value);
}
...
}
This works as long as type can be implicitly casted to id, which is true for NSString, NSNumber, NSDate, NSArray and NSDictionary.
If you need to support primitive types like int, you need to specialize the template with an adjusted implementation.
Related
I linked against a framework that implements extended methods on NSData.
The framework is not open-source so I don't have its implementation.
The extended methods are :
testheader.h
#import <Foundation/Foundation.h>
#interface NSData (TestObj)
- (id)test2:(id)arg1;
- (id)test:(id)arg1;
- (id)test3:(id)arg1;
#end
However, when I import that header file and try to use it that way:
#import "testheader.h"
#implementation ViewController
static void test_header_func () {
NSData *test_var = [NSData test];
}
I'm getting an error says
"No known class method for selector 'test'"
The category provides extra instance methods. You are attempting to use them as a class method. And you are not passing the required argument.
static void test_header_func () {
NSData *test_var = [NSData new]; // or some existing `NSData` instance you have
id someResult = [var test:someArgument];
}
I have a C (Objective-C) structure defined:
struct ResultadoVentaPUP{
NSString *autenticadoPorPin1;
NSString *autenticadoPorPin2;
NSString *tipoPago;
NSString *importe;
};
Then I declare a variable of this type globally (at top of the file):
ResultadoVentaPUP resven;
In a function I set values for this structure, for example:
resven.importe=#"12.45";
but when I try to view the content of "importe" in another function from the same file), ir returns (null).
NSLog(#"Result: %#",resven.importe);
What am I doing wrong? should I define the struct with 'static'?
Thank you!
Storing Obj-C objects in a C structure is a rather bad idea nowadays anyway, with ARC (Automatic Reference Counting), it is not even allowed any longer (the compiler will complain if you do that). Why not using an object instead? If you don't want to use assessor methods because you fear the overhead, just use an object with public ivars. Public ivars are bad IMHO, yet a struct is pretty much the same as an object with public ivars.
#interface ResultadoVentaPUP : NSObject
{
#public
NSString * autenticadoPorPin1;
NSString * autenticadoPorPin2;
NSString * tipoPago;
NSString * importe;
}
#end
#implementation ResultadoVentaPUP
#end
ResultadoVentaPUP * resven;
void someFunction () {
resven = [[ResultadoVentaPUP alloc] init];
resven->importe = #"12.45";
}
void someOtherFunction () {
NSLog(#"Result: %#",resven->importe);
}
This code will also work nicely if you use ARC and sooner or later every project should migrate to ARC in the near future (as soon as it can drop support for OSX/iOS versions without ARC support).
Maybe your declaration should be struct ResultadoVentaPUP resven;. This works for me:
#import <Foundation/Foundation.h>
struct ResultadoVentaPUP{
NSString *autenticadoPorPin1;
NSString *autenticadoPorPin2;
NSString *tipoPago;
NSString *importe;
};
struct ResultadoVentaPUP resven;
void func1() {
resven.importe = #"12.45";
}
void func2() {
NSLog(#"Result: %#", resven.importe);
}
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
func1();
func2();
[p release];
}
And I would be remiss if I didn't include the caveat that any time you are using global variables you should seriously be reconsidering your design.
I'm steadily getting the hang of Objective-C, but am still very much a beginner and have a beginner-level question hopefully someone could shed some light on:
If I have a very simple project and want to set a constant that I'll use throughout—say, a NSDictionary with keys being month names and values being days in that month—how is this done? (I.e., what command form and where to put it?)
NOTE: If this example is already possible using built-in functions, perhaps we could just pretend it isn't for the purposes of this question ;)
The answer depends on the type of your constant. If all you need is an int or a double, you can use preprocessor and the #define CONST 123 syntax. For Objective C classes, however, you need to do a lot more work.
Specifically, you would need to hide the constant behind a class method or a free-standing function. You will also need to add a prototype of that method or function in the header file, provide a function-scoped static variable to store the constant, and add code to initialize it.
Here is an example using a simple NSDictionary:
Header: MyConstants.h
#interface MyConstants
+(NSDictionary*)getConstDictionary;
#end
Implementation: MyConstants.m
+(NSDictionary*)getConstDictionary {
static NSDictionary *inst = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
inst = #{
#"key1": #"value1",
#"key2": #"value2",
#"key3": #"value3"
};
});
return inst;
}
Usage:
NSString *val = [[MyConstants getConstDictionary] objectForKey:#"key2"];
The accepted answer is correct, but if you prefer operate with variable (not trough method). I can suggest this pattern:
#implementation MyClass
static NSSet *mySetOfObjects;
+ (void)initialize {
mySetOfObjects = [[NSSet alloc] initWithObjects:#"one", #"two", #"three", nil];
}
// Example usage:
+ (BOOL)isRecognizedString:(NSString *)searchItem {
return [mySetOfObjects containsObject:searchItem];
}
#end
As for me - it looks better.
For more details the source is here.
Let's assume you want to declare an NSString constant in your class that holds a url. In your header .h file you will need the following:
#import
extern NSString * const BaseURL;
#interface ClassName : NSObject {
You will then need to set it's value in your main .m file as follows:
#import "ClassName.h"
NSString * const BaseURL = #"http://some.url.com/path/";
#implementation ClassName
You can now access this constant throughout your class or subclasses. Here's an example of usage:
NSString *urlString = [NSString stringWithFormat:#"%#%#", BaseURL, #"filename.html"];
If your constants are strings then you can use this form:
MyObject.h:
extern NSString *const kJanuary;
....
extern NSString *const kDecember;
#interface MyObject : NSObject
{
...
}
#end
MyObject.m:
NSString *const kJanuary = #"January";
....
NSString *const kDecember = #"December";
#implementation MyObject
....
#end
You can then use the constant kJanuary, for example, from anywhere when using your class.
Is it possible to add properties to an Objective C object at runtime?
It’s possible to add formal properties to a class via class_addProperty():
BOOL class_addProperty(Class cls,
const char *name,
const objc_property_attribute_t *attributes,
unsigned int attributeCount)
The first two parameters are self-explanatory. The third parameter is an array of property attributes, and each property attribute is a name-value pair which follow Objective-C type encodings for declared properties. Note that the documentation still mentions the comma-separated string for the encoding of property attributes. Each segment in the comma-separated string is represented by one objc_property_attribute_t instance. Furthermore, objc_property_attribute_t accepts class names besides the generic # type encoding of id.
Here’s a first draft of a program that dynamically adds a property called name to a class that already has an instance variable called _privateName:
#include <objc/runtime.h>
#import <Foundation/Foundation.h>
#interface SomeClass : NSObject {
NSString *_privateName;
}
#end
#implementation SomeClass
- (id)init {
self = [super init];
if (self) _privateName = #"Steve";
return self;
}
#end
NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
return object_getIvar(self, ivar);
}
void nameSetter(id self, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}
int main(void) {
#autoreleasepool {
objc_property_attribute_t type = { "T", "#\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t backingivar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty([SomeClass class], "name", attrs, 3);
class_addMethod([SomeClass class], #selector(name), (IMP)nameGetter, "##:");
class_addMethod([SomeClass class], #selector(setName:), (IMP)nameSetter, "v#:#");
id o = [SomeClass new];
NSLog(#"%#", [o name]);
[o setName:#"Jobs"];
NSLog(#"%#", [o name]);
}
}
Its (trimmed) output:
Steve
Jobs
The getter and setter methods should be written more carefully but this should be enough as an example of how to dynamically add a formal property at runtime.
If you take a look at NSKeyValueCoding protocol, documented here, you can see that there is a message called:
- (id)valueForUndefinedKey:(NSString *)key
You should override that method to provide your custom result for the specified undefined property. Of course this assumes that your class uses the corresponding protocol.
This kind of approach is commonly uses to provide unknown behavior to classes (eg. a selector that doesn't exist).
#properties - no (i.e. using dot syntax etc). But you can add storage using using associated objects: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?.
I have a very simple java code like this. I don't have any idea how to do this in Objective C. Especially, the static part which calls the getLocalAddress() method and assign it into the static string variable. I know how to set a static variable and a static method in Objective but I dont know how to implement that static { } part in java.
Thanks in advance...
public class Address {
public static String localIpAddress;
static {
localIpAddress = getLocalIpAddress();
}
public Address() {
}
static String getLocalIpAddress() {
//do something to get local ip address
}
}
I added this in my .h file
#import <Foundation/Foundation.h>
extern NSString *localIpAddress;
#class WifiAddrss;
#interface Address : NSObject {
}
#end
And my .m file looks like
#import "Address.h"
#import "WifiAddress.h"
#implementation Address
+(void)initialize{
if(self == [Address class]){
localIpAddress = [self getLocalIpAddress];
}
}
+(NSString *)getLocalIpAddress{
return address here
}
-(id)init{
self = [super init];
if (self == nil){
NSLog(#"init error");
}
return self;
}
#end
And Now I am getting a linking error and it complains about "extern NSString *localIpAddress" part. If I change the extern to static, it works fine. But what I wanted to do is that I want make the scope of "localIpAddress" variable as grobal. Since if I put "static" in front of a variable in Objective-C then the variable is only visible in the class. But this time, I want to make that as a grobal variable. So my question is how to make "localIpAddress" variable as a grobal variable which is initialized once when the first time Address class is created.. Thanks in advance...
You have declared the variable in your .h file (told the compiler that it exists and of which type it is), now you need to define it in your .m file (actually make it exist).
Just add NSString *localIpAddress; to your .m file, or better yet:
NSString *localIpAddress = nil;
(That is, give it a sane default value)
The extern keyword means: there is a variable of the given name and type, but actually lives in an "external" file that needs to be linked. So for each extern declaration, you need to actually define the variable in one of your implementation files (.c, .cxx/.c++/.cpp, .m ; this mechanism is part of the C standard on which Objective-C is standing).
Unless you want other modules to access localIpAddress directly without using your class, declare it as static inside your implementation (.m) file.
extern should be used in the following scenario:
A module defines the variable as global. That particular translation unit must not use extern
Other modules need to access that variable directly. Those particular translation units must use extern
Since this is not your case, do the following in your implementation (.m) file:
static NSString *localIpAddress;
// …
+(NSString *)getLocalIpAddress{
return localIpAddress;
}
and remove
extern NSString *localIpAddress;
from your header (.h) file.
Whenever you need to get that address, use
NSString *addr = [Address getLocalIpAddress];
By the way, the convention is that getter methods do not start with get. For instance, you could’ve named that method localIpAddress.
A quick fix would be to move the localIpAddress variable into your implementation file. Then you wouldn't need to use the extern keyword. Really, if you think about it, you have a static accessor, so there's no reason to have the variable declaration itself in the header.
Let me clarify:
Interface:
#import <Foundation/Foundation.h>
#interface Address : NSObject {
}
+(void) initializeLocalIpAddress;
+(NSString *) localIpAddress;
#end
Implementation:
#import "Address.h"
#import "WifiAddress.h"
NSString *localIpAddress;
#implementation Address
+(void) initializeLocalIpAddress
{
//get the local ip address here
localIpAddress = ...;
}
+(NSString *) localIpAddress
{
return localIpAddress;
}
-(id)init {
if ((self = [super init])) {
NSLog(#"init error");
}
return self;
}
#end