I have a class MyOperation that has a property localizedTitle that should return a localized title but the title is being set from outside:
#interface MyOperation
- (instancetype)initWithTitle:(NSString*)title;
#property (readOnly) NSString *localizedTitle;
#end
#interface PTImageOperationSession() {
NSString* _operationTitle;
}
#implementation MyOperation
- (instancetype)initWithTitle:(NSString*)title
{
[self retainSelf];
self = [super init];
if (self)
{
_operationTitle = title;
}
return self;
}
- (NSString *)localizedTitle {
return _operationTitle;
}
#end
- (void)foo {
MyOperation *operation = [[MyOperation alloc] initWithTitle:NSLocalizedString(#"MyLocalizedTitle", nil)]; // Good.
NSSting *titleString = [self titleString]; // Can be different strings at runtime.
MyOperation *operation1 = [[MyOperation alloc] initWithTitle: titleString]; // Bad because the title is not localized.
}
Question: Is it possible to insure that the class will always be initialised with localised title? My responsibility as a class owner is to return a localized title, but it is being set from outside so I have no control on it. Would you tag it as a "design issue"?
Passing variable to NSLocalizedString macro shouldn't be any problem, here is what I did for testing purpose:
Localizable.strings
"MyString"="My sample localized string";
And my TestClass
#interface TestClass : NSObject
#property (readonly) NSString *localizedTitle;
-(instancetype)initWithTitle:(NSString *)title;
#end
#implementation TestClass {
NSString *_title;
}
-(instancetype)initWithTitle:(NSString *)title {
self = [super init];
if (self) {
_title = NSLocalizedString(title, nil);
}
return self;
}
-(NSString *)localizedTitle {
return _title;
}
#end
Init my test class, then access its property
TestClass *myClass = [[TestClass alloc] initWithTitle:#"MyString"];
NSLog(#"%#", myClass.localizedTitle);
This is what I have in console:
2018-11-05 19:16:36.268393 TestApp[1471:352553] My sample localized string
Update
The macro:
#define NSLocalizedString(key, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:#"" table:nil]
Is the shortcut to localizedStringForKey:value:table: function & according to the document:
Return Value
A localized version of the string designated by key in
table tableName. This method returns the following when key is nil or
not found in table:
If key is nil and value is nil, returns an empty string.
If key is nil and value is non-nil, returns value.
If key is not found and value is nil or an empty string, returns key.
If key is not found and value is non-nil and not empty, return value.
If you pass a non-existence key to the macro, you will receive the key itself. That's the designated behavior.
By the way, in common sense, you can't ask for a localized string if it isn't defined, right?
Related
I have this class:
#interface XYZPerson : NSObject{
NSString *_myNonPropertyInstanceVariable;
}
#property NSString *firstName;
#property NSString *lastName;
#property NSDate *dateOfBirth;
#property (readonly) NSString* fullName;
#property (nonatomic)NSString* experimental;
-(void)sayHello;
- (void)saySomething:(NSString *)greeting;
+(id)person;
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDOB;
#end
Implementation:
#import "XYZPerson.h"
#implementation XYZPerson {
NSString *_myNonPropertyInstanceVariable2;
}
#synthesize experimental = _experimental;
-(id)init{
self = [self initWithFirst:#"John" andLastName:#"Doe"];
return self;
}
-(id) initWithFirst:(NSString*)first andLastName:(NSString*)last{
return [self initWithFirstName:first lastName:last dateOfBirth:nil];
}
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
dateOfBirth:(NSDate *)aDOB{
self = [super init];
if(self){
_firstName = aFirstName;
_lastName = aLastName;
_dateOfBirth = aDOB;
}
return self;
}
-(void)sayHello{
NSString* greet = [NSString stringWithFormat:#"Hello, World! %#",self.fullName ];
[self saySomething:greet];
}
-(void)saySomething:(NSString *)greeting {
NSLog(#"%#", greeting);
}
+(id)person{
return [[self alloc]init];
}
-(NSString*)fullName{
return [NSString stringWithFormat:#"%# %#",_firstName,_lastName];
}
-(void) setExperimental:(NSString *)anExperimental{
_experimental = anExperimental;
}
-(NSString*) experimental{
return #"Balls to you";
}
In my main method, I do:
NSMutableString* firstName = [NSMutableString stringWithString:#"IntheBeggining"];
NSString* secondName = #"MyLastName";
XYZPerson* newP = [[XYZPerson alloc]initWithFirstName:firstName lastName:secondName dateOfBirth:nil];
firstName = [NSMutableString stringWithFormat:#"Awesome"];
[newP sayHello];
Now I expected that since firstName is a mutable string, when I change it to "Awesome", it should be reflected in the sayHello invocation... but instead the output is:
Hello, World! IntheBeggining MyLastName
It should have said "Hello,World! Awesome MyLastName" ..Why didn't the new value of the NSMutableString* be reflected in the output?
You doesn't change the content of the mutable string of the first line in your last snippet. You just change the reference firstName is pointing to. To change the content use setString::
[firstName setString:#"Awesome"];
This will change the first name of the person, too.
BTW: it is not a good design if the content of properties can be changed from outside it's maintaining class. It's better to use copied properties, which will copy the content of mutable strings to immutable strings on assignments:
#property (copy) NSString *firstName;
You can change the first name of a XYZPerson by invoking its setter:
newP.firstName = #"Awesome";
This has the advantage that the maintaining can keep control over the value. Suppose if you want store the full name in a new (hidden) property. If code outside of the class are changing the value of firstName by setString:, the class has no chance to observe this change and update the full name. If you forbid the assignment of mutable string, you just need to overwrite the setter to keep the full name up-to-date.
I have a iPad application where I'm attempting to use a singleton. This is the code in the .h file:
//-------------------------------------------
//-- singleton: timeFormat
#interface SingletonTimeFormat : NSObject {
}
#property (nonatomic, retain) NSNumber *timeFormat;
+ (id)sharedTimeFormat;
#end
This is the code from the .m file:
//-------------------------------------------
//-- SingletonTimeFormat
#implementation SingletonTimeFormat {
}
#synthesize timeFormat;
//-- sharedColorScheme --
+ (id)sharedTimeFormat {
static dispatch_once_t dispatchOncePredicate = 0;
__strong static id _sharedObject = nil;
dispatch_once(&dispatchOncePredicate, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-id) init {
self = [super init];
if (self) {
timeFormat = [[NSNumber alloc] init];
}
return self;
}
#end
I load the value (either 12 or 24) in AppDelegate - didFinishLaunchingWithOptions, then when I want to get the value of timeFormat I use this:
SingletonTimeFormat *stf = [[SingletonTimeFormat alloc]init];
if([stf.timeFormat isEqualToNumber: [NSNumber numberWithInt:12]]) {
which returns 0 (it was set correctly in AppDelegate, but apparently when I do the alloc in another class, it loses it's value. So obviously it's not working! (I have several other singletons that have the same pattern, but so far they appear to be working.
What's wrong here and how do I fix it?
You don't want to call your singleton using alloc init. With this singleton, all references to it should be through its sharedTimeFormat method, which will init the object if necessary, and will return the singleton instance otherwise.
In other words, it doesn't appear that you're referencing the instance of the object stored in the static sharedObject variable, which means that it's stored value will not necessarily be the same.
I'm learning Objective C with the book "Programming with Objective C", but I'm stuck with the exercises 3 on page 66.
I have the following properties:
#property (copy, readonly) NSString *lastName;
#property (copy, readonly) NSString *firstName;
the following methods:
+ (id) personWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
return [[self alloc] initWithLastName:aLastName andFirstName:aFirstName;
}
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = aLastName;
_firstName = aFirstName;
}
return self;
}
- (void) sayHello {
NSString *greeting = [NSString stringWithFormat:#"Hello, %# %#!", [self firstName], [self lastName]];
[self saySomething:greeting];
}
and the following code in the main:
NSMutableString *firstName = [NSMutableString stringWithString:#"Steve"];
NSString *lastName = #"Jobs";
XYZPerson* firstPerson = [XYZPerson personWithLastName:lastName andFirstName:firstName];
[firstPerson sayHello];
which correctly print "Hello, Steve Jobs!".
Later, I added the following lines:
[firstName setString:#"John"];
[firstPerson sayHello];
which edit the property firstName in the object firstPerson, and print "Hello, John Jobs!".
However, firstName have the copy attribute, so why this happen?
Well let's break this down...
This property says that we want only an accessor to be generated (the readonly part) plus we take ownership of the object via copy upon assignment (which in this case is never gonna happen because of the readonly)
#property (copy, readonly) NSString *lastName;
So a better form would be (unless you're interested in redeclaration of the property as readwrite in a class extension, like in this answer):
#property (readonly) NSString *lastName;
and now we copy inside our init method before assignment to the backing ivars:
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = [aLastName copy];
_firstName = [aFirstName copy];
}
return self;
}
and of course the same applies to your other property also.
The firstName and Lastname you are using in your main method are not the properties you declared, but instead new instance variables. To access the properties you want you can use _firstName to use the ivar or self.firstName to use the property's setter and getter methods.
EDIT: Disregard the above. Because you are using the ivar directly instead of calling the accessor method, the copy is not being made -- you have to call copy yourself.
Does anyone know if there is a way to set a property like a string in the User Defined Runtime Atributes sections of Interface Builder without creating a subclass of said component? For example, I want to store a metadata value for each component in my interface that I use later. I just don't want to have to create a subclass or each component to add a metadata property.
This is one approach I came up with. Opinions?
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#interface UIControl(MetaData)
#property (nonatomic, retain) id entityProperty;
#end
#implementation UIControl(MetaData)
static char const * const EntityPropertyKey = "EntityProperty";
#dynamic entityProperty;
- (id)entityProperty {
return objc_getAssociatedObject(self, EntityPropertyKey);
}
- (void)setEntityProperty:(id)newEntityProperty {
objc_setAssociatedObject(self, EntityPropertyKey, newEntityProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
...
if (textField.entityProperty)
[managedObject setValue: textField.text forKey:textField.entityProperty];
You could keep an NSDictionary somewhere, perhaps in a singleton object that has methods for issuing unique ids for objects and storing metadata by the id keys in the dictionary. The UI objects have a tag property that you can use, if your ids are just incremented integers. Then the dictionary keys would just be NSNumbers for those unique integers.
Like this:
#import <Foundation/Foundation.h>
#interface ACLMetadataManager : NSArray
+(ACLMetadataManager*) sharedMetadataManager;
-(NSUInteger) getUniqueId;
-(void) setObject: (id) object forId:(NSUInteger) theId;
-(id) objectForId:(NSUInteger) theId;
#end
And:
#import "ACLMetadataManager.h"
#implementation ACLMetadataManager { // Private variables
NSMutableDictionary *_metadata;
NSUInteger _ids;
}
- (id)init {
self = [super init];
if (self) {
_metadata = [[NSMutableDictionary alloc] init];
}
return self;
}
+(ACLMetadataManager*) sharedMetadataManager { // Singleton getter
static ACLMetadataManager *instance;
if (instance != nil) {
return instance;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
static dispatch_once_t oneTimeThread;
dispatch_once(&oneTimeThread, ^(void) {
instance = [[ACLMetadataManager alloc] init];
});
#else
#synchronized(self) {
instance = [[ACLMetadataManager alloc] init];
}
#endif
return instance;
}
-(NSUInteger) getUniqueId { // Increment unique id when getter is called.
return ++_ids; // Start from 1 because tag is 0 by default.
}
-(void) setObject: (id) object forId:(NSUInteger) theId {
[_metadata setObject:object forKey:[NSNumber numberWithInteger:theId]];
}
-(id) objectForId:(NSUInteger) theId {
return [_metadata objectForKey:[NSNumber numberWithInteger:theId]];
}
// Override some methods to ensure singleton stays instantiated.
- (id) retain {
return self;
}
- (oneway void) release {
// Does nothing here.
}
- (id) autorelease {
return self;
}
- (NSUInteger) retainCount {
return INT32_MAX;
}
#end
Usage:
ACLMetadataManager *metadataManager = [ACLMetadataManager sharedMetadataManager];
myControl.tag = [metadataManager getUniqueId];
[metadataManager setObject:myMetadata forId:myControl.tag];
I have a class called "CardSet", containing an NSMutableArray* cardSet to hold "cards", which I extend to make "DeckCards". I'd like "CardSet" to have a method called "(void)addCard:(Card*)" (and similarly a method "removeCard"). I'd like "addCard" to some how have access to and set cardSet. Even better I'd like to use the "addCard" method to initialise cardSet. The class file "CardSet.h" reads:
#import < Cocoa/Cocoa.h >
#import < Card.h >
#interface CardSet : NSObject {
NSMutableArray* cardSet;
}
-(id)init;
-(NSMutableArray*)getCardSet;
-(void)setCardSet:(NSMutableArray *)new_cardset;
-(Card*)getCard:(NSInteger) index;
**-(void)addCard:(Card*) new_card;**
-(void)removeCard:(Card*) old_card;
-(void)dealloc;
#property (readwrite, retain, getter=getCardSet, setter=setCardSet) NSMutableArray* cardSet;
#end
and the method file reads:
#import "CardSet.h"
#implementation CardSet
-(id)init{
if( self = [super init] ){} //will add initialisations here later
return self;
}
-(NSMutableArray*)getCardSet{
return cardSet;
}
-(void)setCardSet:(NSMutableArray *)new_cardSet{
cardSet = new_cardSet;
}
-(Card*)getCard:(NSInteger)index{
return [cardSet objectAtIndex:index];
}
**-(void)addCard:(Card *)new_card{
[cardSet addObject:new_card];
}**
-(void)removeCard:(Card *)old_card{
[cardSet removeObject:old_card];
}
-(void)dealloc{
[cardSet release];
[super dealloc];
}
#synthesize cardSet;
#end
This compiles just fine. I'd like to initialise a "DeckCards" instance using its "addCard" method 52 times. When I call addCard 52 times in a DeckCards setter method, and ask for the size of its "cardSet", I'm returned 0.
This appears to be a scope or privileges problem? Can the "addCard" method have any setter privileges? Must a setter argument be the same as the return and respective member type?
[I can work around the above by creating an NSMutableArray object "deck_cards_temp" outside of "DeckCard", add 52 cards to this, and pass it to set the member of my "DeckCards" instance via the setter inherited from "CardSet". This is not very satisfactory!]
What do you advise? Many thanks in advance for your help and patience.
You are never actually creating the cardSet object. You should be creating it in your -init method:
-(id)init
{
if( self = [super init] )
{
cardSet = [[NSMutableArray alloc] init];
}
return self;
}
Because you never actually create the array, all the calls to -addCard: are being sent to a nil object.
When you pass in an array to -setCardSet:, you are passing in an initialized array so the array is no longer nil and the -addCard: calls work fine.
CardSet.h
#import <Cocoa/Cocoa.h>
// For know we just need to know there is a class named "Card" being used but implemented later
#class Card;
#interface CardSet : NSObject {
NSMutableArray *cardSet;
}
// Here are the methods according to "correct" naming conventions
- (Card *)cardAtIndex:(NSInteger)index;
- (void)addCard:(Card *)card;
- (void)removeCard:(Card *)card;
// This will help us and forget about writing the setter/getter
#property (nonatomic, retain) NSMutableArray *cardSet;
#end
CardSet.m
#import "CardSet.h"
// Now we tell the compiler what "Card" is and what methods etc. it has
#import "Card.h"
#implementation CardSet
#synthesize cardSet;
- (id)init {
if (self = [super init]) {
// If we don't create the cardSet, how are we able to work with it!?
NSMutableArray *anArray = [[NSMutableArray alloc] init];
self.cardSet = anArray;
[anArray release];
}
return self;
}
- (Card *)cardAtIndex:(NSInteger)index {
return [cardSet objectAtIndex:index];
}
- (void)addCard:(Card *)card {
[cardSet addObject:card];
}
- (void)removeCard:(Card *)card {
[cardSet removeObject:card];
}
- (void)dealloc {
[cardSet release];
[super dealloc];
}
#end
As Abizern already noted: Naming the array the same as your class is a bad thing.
I would shorten that init method:
- (id)init {
if (self = [super init]) {
// If we don't create the cardSet, how are we able to work with it!?
self.cardSet = [NSMutableArray array];
}
return self;
}