Is there a way to define a custom path of where the strings for NSLocalizedString are read from? I am aware you can define what filename to read from, but I am trying to make a user-editable system that will be stored in the Application Support folder. If there is no way to change the path it reads from, then is there a low-level class built into cocoa to read localized string files?
One thing you could do is re-define NSLocalizedString in your pre-compiled header (.pch) file to point to a class of your own like MyLocalizationHandler, as such:
#undef NSLocalizedString
#define NSLocalizedString(key,comment) [[MyLocalizationHandler sharedLocalizationHandler] localizedString:key]
and then in MyLocalizationHandler.m do something like:
- (NSString *)localizedString:(NSString *)key {
// lookup the key however we want...
NSString *value = [self lookupLocalizedKey:key];
if (value)
return value;
// and maybe fall-back to the default localized string loading
return [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];
}
Related
I need to have multiple languages facility in my app. I am able to change language with localization strings, but storyboard text are not changing without restarting app. I added Arabic language also for users that needs to change alignments of text as well which is also working fine if I restart app.
I am using AppleLanguages user default key to change language and localized strings for dynamic texts.
For Objective C :
+(void)setLanguage:(NSString*)language{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
object_setClass([NSBundle mainBundle],[BundleEx class]);
});
objc_setAssociatedObject([NSBundle mainBundle], &_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:#"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
For Swift :
class func setLanguage(_ language: String) {
var onceToken: Int
if (onceToken == 0) {
/* TODO: move below code to a static variable initializer (dispatch_once is deprecated) */
object_setClass(Bundle.main, BundleEx.self)
}
onceToken = 1
objc_setAssociatedObject(Bundle.main, bundle, language ? Bundle(path: Bundle.main.path(forResource: language, ofType: "lproj")) : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
Add the above method and just pass the language which you want to convert.
If you have added ja.lproj then send "ja" as a param to the above method.
This will convert Storyboard language without restart.
Hope it helps.
I am working on a static library that handles sensitive data. It is imperative that the developer using the library can not use reflection on the library.
On Android, we solve the problem by developing an aar file with services and run the service into separate process;(When the service is running into another process then the developer can not use reflection) but I am wondering if something similar exists in iOS ?
Can we execute a static library into a separate process? if not, how we can avoid reflection on our static libraries?
For example:
MyTestObject *obj = [[[myTestView alloc] init ];
//===========================================
Class clazz = [obj class];
u_int count;
Ivar* ivars = class_copyIvarList(clazz, &count);
NSMutableArray* ivarArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
const char* ivarName = ivar_getName(ivars[i]);
[ivarArray addObject:[NSString stringWithCString:ivarName encoding:NSUTF8StringEncoding]];
}
free(ivars);
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
Method* methods = class_copyMethodList(clazz, &count);
NSMutableArray* methodArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
SEL selector = method_getName(methods[i]);
const char* methodName = sel_getName(selector);
[methodArray addObject:[NSString stringWithCString:methodName encoding:NSUTF8StringEncoding]];
}
free(methods);
NSDictionary* classDump = [NSDictionary dictionaryWithObjectsAndKeys:
ivarArray, #"ivars",
propertyArray, #"properties",
methodArray, #"methods",
nil];
NSLog(#"%#", classDump);
//======================================================
int v2 = [[obj valueForKey:#"testValue"] intValue];
SEL s = NSSelectorFromString(#"wannatTestIt");
[obj performSelector:s];
MyTestObject is a class from my library. In the first line, I initialize an object from this class.
In the next line, I read the variables, methods and property list of the class and log it. Here is the result:
{
ivars = (
testValue
);
methods = (
printTestValue,
wannatTestIt,
"initWithFrame:"
);
properties = (
);
}
wannaTestIt is a private method and testValue is a private variable. So I expect that the developer that uses the library can not access them. However, because the user of the library could get the name, the user can ultimately call the method to read the value of the iVar.
How can I prevent this?
If you want to completely "prevent" reflection then, well, you have to use a different language. Reflection is a key thing in Objective C and it's not possible to "block" or "disable" it.
However, you can make this run-time information much less useful for the researcher by obfuscating it. For example, take a look at this tool: https://github.com/Polidea/ios-class-guard. This is just an example. I'm not related to this particular project and you can freely chose a different obfuscator or write your own.
If what you need is to limit reflection to public API only and even disclosing a number of private methods and ivars (without their actual names) is not okay for you then you have no other choice than writing you sensitive code in a different language. You can use Pimpl design pattern to achieve what you want. This way, your classes would only have public methods and a single private ivar _impl (or something like that). Where _impl is an instance of the implementation class that is written in C++ (or Objective C++ if you need access to ObjC APIs) and all public methods act like a proxy. Something like this:
- (NSInteger)computeSuperSensitiveStuffWithFoo:(Foo *)foo Bar:(Bar *)bar {
return _impl->computeStuff(foo, bar);
}
This way all your private data and methods would be encapsulated in the MyClassImpl class. If you keep declaration and implementation of such class in private (i.e. do not distribute MyClassImpl.h file with your library) and use language like C++ for implementing it, then you will achieve what you want.
Also note that if you chose Objective C++ then MyClassImpl should be a C++ class (declared with class keyword) and not Objective C class (declared with #interface/#end block and implemented inside #implementation/#end block). Otherwise all you private data will be available for reflection anyway, but would require a couple of additional steps from the researcher.
I am trying to use an array of strings dynamically access methods at runtime within my class. For now the methods are already there, eventually I want to create them.
Is this possible?
For example:
bool nextLevel=NO;
for(NSString * match in gameLevels)
{
if([match isEqualToString:self.level])
{
nextLevel=YES;
}
else if(nextLevel==YES)
{
self.level=match;
nextLevel=NO;
}
}
//access method named self.level
Thank you in advance!
I use:
NSSelectorFromString(selectorString)
In your case, the selectorString would be:
NSString * selectorString = #"setLevel:";
This is 'setLevel' instead of 'level' because the Objective-C runtime will automatically expand dot properties to these selector names when assignment occurs.
To access a method based on a string, check the other answer.
To add a method in the runtime you need to create a IMP function or block.
If using a function, could be something like:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
You could also use a block like this:
IMP blockImplementation=imp_implementationWithBlock(^(id _self, ...){
//Your Code here
}
Then you need to add the method, like this:
class_addMethod(yourClass, #selector(selectorName), (IMP) blockImplementation, encoding);
The encoding part is a special runtime encoding to describe the type of parameters your method receives. You can find that on the Objective-C runtime reference.
If you receive dynamic arguments on your generated methods, you need to use the va_list to read the values.
I use NSLoger in iOS app, it great, it can show function name, file line, just I also want show the instance of the class information.
eg: class A methods is 'doAction', a instance : A* a = [A alloc];
in method 'doAction':
{
NSLog(#"out this instance information, do not need repeat set the argument [%#]",self );
}
I meaning is that I do not need define the format and the 'self' argument in every NSLog statement....
how to do it ? may I explain clearly and need your help thanks.
so... then I try to add some code as follows:
void LogMessageMe( const char *filename, int lineNumber, const char *functionName,id target, int level, NSString *format, ...)
{
NSString* strTarget = [target description];
va_list args;
va_start(args, format);
LogMessageTo_internal(NULL, filename, lineNumber, functionName, strTarget, level, format, args);
va_end(args);
}
and
#define NSLogMe(...) LogMessageMe( __FILE__, __LINE__, __FUNCTION__, self, 0, __VA_ARGS__)
the call code as follows:
NSLogMe(#"didFinishLaunchingWithOptions");
then output log:
<AppDelegate: 0x906e100> | Main thread | didFinishLaunchingWithOptions
that is really what I want to see... since in a app, many instance was create but that is the same class... if no the identify number, it is hard to trace the target instance ....
but still a issue...
IDE report warning message about the statement:
NSLogMe(#"didFinishLaunchingWithOptions");
"Implicit declaration of function ‘LogMessageMe’ is invalid in C99"
it seem small issue... anyone help ? thanks.
Use the %p specifier in NSLog statements to output the value of a pointer:
NSLogMe(#"Called from %# instance %p", [self class], self);
These are standard [NSString stringWithFormat:...] specifiers. For a complete list, see the documentation for string format specifiers.
The warning has nothing to do with NSLogger.
You need to define the macro NSLogMe in a place where the function signature LogMessageMe is visible.
And if you declare LogMessageMe outside of LoggerClient then you need to make LogMessageTo_internal public (now it isn't, as its name indicates).
Following the explanation here:
https://github.com/nst/iOS-Runtime-Headers
I am trying to obtain the class of the TUPhoneLogger in the bundle TelephonyUtilities.framework. However, the debugger always show "error: unknown class".
I've 2 different methods:
First method:
NSBundle* b = [NSBundle bundleWithPath:#"/System/Library/PrivateFrameworks/TelephonyUtilities.framework"];
BOOL success = [b load];
NSLog(#"%#", [b definedClasses_dd]);
Note: I've created a #interface NSBundle (DDAdditions)
extension:
- (NSArray *)definedClasses_dd {
NSMutableArray *array = [NSMutableArray array];
int numberOfClasses = objc_getClassList(NULL, 0);
Class *classes = calloc(sizeof(Class), numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for (int i = 0; i < numberOfClasses; ++i) {
Class c = classes[i];
if ([NSBundle bundleForClass:c] == self) {
[array addObject:c];
const char* nameOfClass = class_getName(c);
NSString* classString = [NSString stringWithUTF8String:nameOfClass];
if([classString isEqualToString:#"TUPhoneLogger"]) {
NSLog(#"Found it! TUPhoneLogger");
id test= [[c alloc] init];
NSLog(#"test: %#", test);
}
}
}
free(classes);
Second method:
NSBundle* b = [NSBundle bundleWithPath:#"/System/Library/PrivateFrameworks/TelephonyUtilities.framework"];
Class telephonyClass = [b classNamed:#"TUPhoneLogger"];
id test= [[telephonyClass alloc] init];
NSLog(#"%#", test);
In the debugger:
+1 to Victor, as I think it's simpler to just include the Framework as a Build Phase library in your project. Private frameworks are found under the PrivateFrameworks SDK subdirectory, but otherwise, it works similarly as with Public frameworks (with differences described in Victor's answer).
I will just offer one other technique that works, if you do want dynamic loading:
#include <dlfcn.h>
#import <objc/runtime.h>
and then
void* handle = dlopen("/System/Library/PrivateFrameworks/TelephonyUtilities.framework/TelephonyUtilities", RTLD_NOW);
Class c = NSClassFromString(#"TUPhoneLogger");
id instance = [[c alloc] init];
dlclose(handle);
I suppose a benefit of dlopen() is that you don't have to remember to call load, which got you in your second example. A downside is that you should call dlclose() afterwards.
Note: the slight difference in path for dlopen() vs NSBundle bundleWithPath: (file vs. dir)
Note++: this code won't work in the simulator, as the simulator probably is missing that framework (no real phone functionality)
Update
In iOS 9.3, Apple removed Private Frameworks from the SDK. So, since then, it's actually typically the case that you'll need to use this dynamic technique, if the Framework is not one of the public iOS frameworks. See this answer for more
There is a third method (which I prefer)
a) You link to this framework statically (meaning, you add it to your target)
b) You define necessary class (TUPhoneLogger ) in .h class. You can get it by using class-dump(-z)
c) You include this .h file
d) You just use private class the same way as you use public class.
Small additional explanation
There is no "magic" about private frameworks and private API. The only different that they aren't documented and included in .h files.
Step b) and c) creates .h classes with them and as result they can be used exactly the same way as usual public API.