I have a framework in Objective-C with this struct:
VehicleClass.h
struct VehicleStruct
{
__unsafe_unretained NSString *const CAR;
__unsafe_unretained NSString *const MOTORCYCLE;
__unsafe_unretained NSString *const TRUCK;
};
VehicleClass.m
#import "VehicleClass.h"
const struct VehicleStruct Vehicle = {
.CAR = #"CAR",
.MOTORCYCLE = #"MOTORCYCLE",
.TRUCK = #"TRUCK"
};
And in my Contansts.h
extern const struct VehicleStruct Vehicle;
Well, now I want get car value in my proyect using swift:
let carValueSwift = String(Vehicle.CAR)
In console:
"Unmanaged NSString(_value: CAR)"
How can I solve this? I need get this value like NSString, because if then I want to compare in my framework:
["CAR" isEqual: carValueSwift] Doesn´t work.
Thank you very much
With takeUnretainedValue() the Unmanaged<NSString> is converted
to NSString (without taking ownership), and this can simply be
cast to a Swift String:
let carValueSwift = Vehicle.CAR.takeUnretainedValue() as String
print(carValueSwift)
Well, I found solution:
let car: Unmanaged<NSString> = Vehicle.CAR
let carString = convertCfTypeToString(car)
Function:
func convertCfTypeToString(cfValue: Unmanaged<NSString>!) -> String?
{
let value = Unmanaged.fromOpaque(
cfValue.toOpaque()).takeUnretainedValue() as CFStringRef
if CFGetTypeID(value) == CFStringGetTypeID(){
return value as String
} else {
return nil
}
}
If anyone knows other way to avoid use convertCfTypeToString: method, please, let me know...
Related
I have some constant strings defined in my #implementation file like:
static NSString * const contentDisplayDateKeyPath = #"content.display_date";
static NSString * const contentIDKeyPath = #"content.id";
Could I get the content of contentDisplayDateKeyPath use a string which holding the variable's name in runtime?
ex:
NSString *constantName = #"contentDisplayDateKeyPath"
[self valueForKey:constantName]
then I'll get content.display_date
Can this be achieved?
I am trying to achieve this by using CFBundleGetDataPointer
CFBundleRef mainBundle = CFBundleGetBundleWithIdentifier(CFBridgingRetain([[NSBundle mainBundle] bundleIdentifier]));
void *stringPointer = CFBundleGetDataPointerForName(mainBundle, CFBridgingRetain(obj));
NSString *string = (__bridge NSString *)stringPointer;
But the stringPointer is always null.
Thanks for help
This should do it for you.
NSString *__autoreleasing *string = (NSString*__autoreleasing*)dlsym(RTLD_DEFAULT, "<name of constant like PKPaymentNetworkVisa>");
NSLog(#"%#", *string);
Use a map with the key as the constant name and the value as the constant value:
static NSDictionary *_constants = #{
#"contentDisplayDateKeyPath" : #"content.display_date",
#"contentIDKeyPath" : #"content.id",
// etc.
};
...
NSString *constantName = #"contentDisplayDateKeyPath";
NSString *constantValue = _constants[constantName];
Another option is to encapsulate this into a singleton object and access your constants through read only properties. Check out What should my Objective-C singleton look like? to see the singleton design pattern.
This question was answered here:
How do I lookup a string constant at runtime in Objective-C?
The solution worked perfectly for me.
You can use CFBundleGetDataPointerForName to lookup a constant's value at runtime
-(NSString *)lookupStringConstant:(NSString *)constantName
{
void ** dataPtr = CFBundleGetDataPointerForName(CFBundleGetMainBundle(), (__bridge CFStringRef)constantName);
return (__bridge NSString *)(dataPtr ? *dataPtr : nil);
}
I was wondering if it's possible to determine the class or primitive type of an Objects properties. Getting all properties names and values is pretty easy. SO answer
So is there any way to get the properties class type while the property hast no value or nil value?
Example Code
#interface MyObject : NSObject
#property (nonatomic, copy) NSString *aString;
#property (nonatomic, copy) NSDate *aDate;
#property NSInteger aPrimitive;
#end
#implementation MyObject
#synthesize aString;
#synthesize aDate;
#synthesize aPrimitive;
- (void)getTheTypesOfMyProperties {
unsigned int count;
objc_property_t* props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
// Here I can easy get the name or value
const char * name = property_getName(property);
// But is there any magic function that can tell me the type?
// the property can be nil at this time
Class cls = magicFunction(property);
}
free(props);
}
#end
After searching through Apples Documentation about objc Runtime and according to this SO answer I finally got it working. I just want to share my results.
unsigned int count;
objc_property_t* props = class_copyPropertyList([MyObject class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
const char * name = property_getName(property);
NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
const char * type = property_getAttributes(property);
NSString *attr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString componentsSeparatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else {
// According to Apples Documentation you can determine the corresponding encoding values
}
if ([typeAttribute hasPrefix:#"T#"]) {
NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns #"NSDate" into NSDate
Class typeClass = NSClassFromString(typeClassName);
if (typeClass != nil) {
// Here is the corresponding class even for nil values
}
}
}
free(props);
Inspired by the ObjC answer by #arndt-bieberstein I have written a solution in Swift 3 (probably very similar - if not same - in earlier versions of Swift). You can find it on Github I am trying to make a pod of it but I am having issues getting pob lib lintto work with the Swift 3 code (CLI xcodebuild or Xcode 8 related problem probably.) Anyhow, the class method func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? can extract the name and types of any Swift class that inherits from NSObject.
The work horse of the project are these methods, but checkout the full code on Github:
func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
func getTypeOf(property: objc_property_t) -> Any {
guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self }
let attributes = attributesAsNSString as String
let slices = attributes.components(separatedBy: "\"")
guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) }
let objectClassName = slices[1]
let objectClass = NSClassFromString(objectClassName) as! NSObject.Type
return objectClass
}
func getPrimitiveDataType(withAttributes attributes: String) -> Any {
guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self }
return type
}
func getNameOf(property: objc_property_t) -> String? {
guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil }
return name as String
}
It can extract the NSObject.Type of all properties which class type inherits from NSObject such as NSDate (Swift3: Date), NSString(Swift3: String?) and NSNumber, however it is store in the type Any (as you can see as the type of the value of the Dictionary returned by the method). This is due to the limitations of value types such as Int, Int32, Bool. Since those types do not inherit from NSObject, calling .self on e.g. an Int - Int.self does not return NSObject.Type, but rather the type Any. Thus the method returns Dictionary<String, Any>? and not Dictionary<String, NSObject.Type>?.
You can use this method like this:
class Book: NSObject {
let title: String
let author: String?
let numberOfPages: Int
let released: Date
let isPocket: Bool
init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) {
self.title = title
self.author = author
self.numberOfPages = numberOfPages
self.released = released
self.isPocket = isPocket
}
}
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
print("'\(name)' has type '\(type)'")
}
// Prints:
// 'title' has type 'NSString'
// 'numberOfPages' has type 'Int'
// 'author' has type 'NSString'
// 'released' has type 'NSDate'
// 'isPocket' has type 'Bool'
You can also try to cast the Any to NSObject.Type, which will succeed for all properties inheriting from NSObject, then you can check the type using standard == operator:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if let objectType = type as? NSObject.Type {
if objectType == NSDate.self {
print("Property named '\(name)' has type 'NSDate'")
} else if objectType == NSString.self {
print("Property named '\(name)' has type 'NSString'")
}
}
}
}
If you declare this custom == operator:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
You can then even check the type of value types like this:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if type == Int.self {
print("Property named '\(name)' has type 'Int'")
} else if type == Bool.self {
print("Property named '\(name)' has type 'Bool'")
}
}
}
LIMITATIONS
I have not yet been able to give this project support for when the value types are optionals. If you have declared a property in you NSObject subclass like this: var myOptionalInt: Int? my solution will not work, because the method class_copyPropertyList can't find those properties.
Does anyone have a solution for this?
Objective-C offers runtime reflections feature. I'm trying to find getter/setter selector name of a declared property. I know the basic rule like field/setField:. Anyway I think runtime reflection should offer a feature to resolve the name for complete abstraction, but I couldn't find the function.
How can I resolve the getter/setter method selector (not implementation) of a declared property with runtime reflection in Objective-C (actually Apple's Cocoa)
Or reverse query. (method selector → declared property)
I think you can get the selector names only if the property is declared with explicit (setter = XXX and/or getter = XXX)
So to get the getter and setter selector names for some property 'furType' of the class 'Cat':
objc_property_t prop = class_getProperty([Cat class], "furType");
char *setterName = property_copyAttributeValue(prop, "S");
if (setterName == NULL) { /*Assume standard setter*/ }
char *getterName = property_copyAttributeValue(prop, "G");
if (getterName == NULL) { /*Assume standard getter */ }
I don't know of a reverse query, other than iterating through all the properties and looking for matches. Hope that helps.
A little update from my NSObject category. Hope this'll help some one:
+(SEL)getterForPropertyWithName:(NSString*)name {
const char* propertyName = [name cStringUsingEncoding:NSASCIIStringEncoding];
objc_property_t prop = class_getProperty(self, propertyName);
const char *selectorName = property_copyAttributeValue(prop, "G");
if (selectorName == NULL) {
selectorName = [name cStringUsingEncoding:NSASCIIStringEncoding];
}
NSString* selectorString = [NSString stringWithCString:selectorName encoding:NSASCIIStringEncoding];
return NSSelectorFromString(selectorString);
}
+(SEL)setterForPropertyWithName:(NSString*)name {
const char* propertyName = [name cStringUsingEncoding:NSASCIIStringEncoding];
objc_property_t prop = class_getProperty(self, propertyName);
char *selectorName = property_copyAttributeValue(prop, "S");
NSString* selectorString;
if (selectorName == NULL) {
char firstChar = (char)toupper(propertyName[0]);
NSString* capitalLetter = [NSString stringWithFormat:#"%c", firstChar];
NSString* reminder = [NSString stringWithCString: propertyName+1
encoding: NSASCIIStringEncoding];
selectorString = [#[#"set", capitalLetter, reminder, #":"] componentsJoinedByString:#""];
} else {
selectorString = [NSString stringWithCString:selectorName encoding:NSASCIIStringEncoding];
}
return NSSelectorFromString(selectorString);
}
We have some code today that takes an NSArray and passes it as a argument list to -[NSString initWithFormat:arguments] and we're trying to get this to work with ARC. Here's the code were using
NSString* format = #"Item %s and Item %s"; // Retrieved elsewhere
NSArray* args = [NSArray arrayWithObjects:#"1", #"2", nil]; // Retrieved elsewhere
// http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
char* argsList = (char*) malloc(sizeof(NSString*) * args.count);
[args getObjects:(id*) argsList];
NSString* message = [[[NSString alloc] initWithFormat:format arguments:argsList] autorelease];
free(argsList);
Any recommendations on how to make this ARC compliant? Or we're even open to a better way of doing it.
This only works for arrays with a single element
The answer by chrisco was working well, until I went to compile with 64-bit architecture. This caused an error:
EXC_BAD_ADDRESS type EXC_I386_GPFLT
The solution was to use a slightly different approach for passing the argument list to the method:
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
__unsafe_unretained id * argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * arguments.count);
for (NSInteger i = 0; i < arguments.count; i++) {
argList[i] = arguments[i];
}
NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;// arguments:(void *) argList];
free (argList);
return result;
}
Cannot find a way to do this obj-c but a swift helper class finally got this working (my whole project is obj-c except this class)
#objc class StringFormat: NSObject {
class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArgType] = args.map({ (arg: AnyObject) -> CVarArgType in
if let iArg = (arg is NSNumber ? arg.intValue : nil) {
return iArg
}
return arg as! CVarArgType
});
return String(format: key, arguments: locArgs)
}
}
There is some magic going on, to do with how [CVarArgType] doesn't behave like a normal array - but this works in the flexible cross architecture way you expect it to.
Expanding on #mcfedr's answer, this Swift 3 helper does the job:
import Foundation
#objc (FTStringFormat) public class StringFormat: NSObject {
#objc public class func format(key: String, args: [AnyObject]) -> String {
let locArgs: [CVarArg] = args.flatMap({ (arg: AnyObject) -> CVarArg? in
if let arg = arg as? NSNumber {
return arg.intValue
}
if let arg = arg as? CustomStringConvertible {
return arg.description
}
return nil
});
return String(format: key, arguments: locArgs)
}
}
Calling from Objective-C:
[FTStringFormat formatWithKey:#"name: %# age: %d" args:#[#"John", #(42)]]
For the %# format specifier we're using Swift's CustomStringConvertible protocol in order to call description on all of the array members.
Supporting all number format specifiers like %d and %f is not really possible because the NSNumber object doesn't reveal if it's an integer or float. So we could only support one or the other. Here we use intValue, so %d is supported but %f and %g are not.
The only thing you need to do is remove the autorelease.
You're malloc'ing and free'ing yourself - ARC doesn't care about that.
I write solution use NSInvocation and signatures.
Answer create in this.
Also I write detailed description how it work but only on Russian ((
Maybe it help for someone.
I tried mcfedr's code. Somehow, my Xcode 11 treated CVarArgType as undeclared type, so I investigated into this for a while.
I didn't not understand the closure part of his/her code. And, I just simplified to hard casted each element to CVarArg using as! operator.
func format(key: String, args: [Any]) -> String {
return String(format: key, arguments: args.map { ($0 as! CVarArg) })
}
let doubleValue: Double = 1.25
let floatValue: Float = 2.75
let intValue: Int = 3
let numberValue: NSNumber = 4.5 as NSNumber
let hello: String = "Hello"
let world: NSString = "World" as NSString
print(format(key: "double: %f, float: %f, int: %d, number: %#, %#, %#", args: [doubleValue, floatValue, intValue, numberValue, hello, world]))
// double: 1.250000, float: 2.750000, int: 3, number: 4.5, Hello, World
It seems it's working fine under swift 5.1, but there may be some pitfalls.
Is there a method that returns all the keys for an object conforming to the NSKeyValueCoding protocol?
Something along the lines of [object getPropertyKeys] that would return an NSArray of NSString objects. It would work for any KVC-compliant object. Does such a method exist? I haven't found anything in searching the Apple docs so far.
Thanks,
G.
#import "objc/runtime.h"
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for(i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName) {
const char *propType = getPropertyType(property);
NSString *propertyName = [NSString stringWithUTF8String:propName];
NSString *propertyType = [NSString stringWithUTF8String:propType];
}
}
free(properties);
Use class_getPropertyList. That will tell you all the #properties of the object.
It won't necessarily list every KVC-compliant property, because any method that takes no arguments and returns a value is a valid KVC-compliant getter. There's no 100%-reliable way for the runtime to know which ones behave as properties (e.g., -[NSString length]) and which ones behave as commands (e.g., -[NSFileHandle readDataToEndOfFile]).
You should be declaring your KVC-compliant properties as #properties anyway, so this shouldn't be too big of a problem.
There is no such method as the KVO system does not require objects/classes to register with it which properties they support KVO for. Any key could potentially support KVO, the only way to know is from the author's documentation.
And of course, there is no guarantee that an #property will support KVO; it's quite possible to write a property that doesn't (and may be necessary sometimes). So, getting a list of a class's #propertys and then assuming they're KVO-compliant would be a dangerous choice in my opinion.
You need a getPropertyType function. See this post: Get an object attributes list in Objective-C
For Swift onlookers, you can get this functionality by utilising the Encodable functionality. I will explain how:
Conform your object to Encodable protocol
class ExampleObj: NSObject, Encodable {
var prop1: String = ""
var prop2: String = ""
}
Create extension for Encodable to provide toDictionary functionality
public func toDictionary() -> [String: AnyObject]? {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(self),
let json = try? JSONSerialization.jsonObject(with: data, options: .init(rawValue: 0)), let jsonDict = json as? [String: AnyObject] else {
return nil
}
return jsonDict
}
Call toDictionary on your object instance and access keys property.
let exampleObj = ExampleObj()
exampleObj.toDictionary()?.keys
Voila! Access your properties like so:
for k in exampleObj!.keys {
print(k)
}
// Prints "prop1"
// Prints "prop2"