Convert callback/closure to Swift from Objective C - objective-c

So I am trying to implement the Superpowered library in Swift, and am getting stuck around initialisation with a callback. How would I convert that line:
__unsafe_unretained Superpowered *self = (__bridge Superpowered *)clientdata;
into Swift?
Here is the simplified Objective C implementation:
#implementation Superpowered {
SuperpoweredIOSAudioIO *audioIO;
SuperpoweredBandpassFilterbank *filters;
unsigned int samplerate;
}
static bool audioProcessing(void *clientdata, float **buffers, unsigned int inputChannels, unsigned int outputChannels, unsigned int numberOfSamples, unsigned int samplerate, uint64_t hostTime) {
__unsafe_unretained Superpowered *self = (__bridge Superpowered *)clientdata;
if (samplerate != self->samplerate) {
self->samplerate = samplerate;
};
// Update position.
self->lastNumberOfSamples = numberOfSamples;
return false;
}
- (id)init {
self = [super init];
if (!self) return nil;
samplerate = 44100;
audioIO = [[SuperpoweredIOSAudioIO alloc] initWithDelegate:(id<SuperpoweredIOSAudioIODelegate>)self preferredBufferSize:12 preferredMinimumSamplerate:44100 audioSessionCategory:AVAudioSessionCategoryRecord channels:2 audioProcessingCallback:audioProcessing clientdata:(__bridge void *)self];
[audioIO start];
return self;
}
And here is the start of my Swift version:
func bridge<T : AnyObject>(obj : T) -> UnsafeMutableRawPointer {
return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque())
// return unsafeAddressOf(obj) // ***
}
func bridge<T : AnyObject>(ptr : UnsafeMutableRawPointer) -> T {
return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
// return unsafeBitCast(ptr, T.self) // ***
}
open class EchoesEngine: NSObject, CLLocationManagerDelegate, SuperpoweredIOSAudioIODelegate {
public var audioIO:SuperpoweredIOSAudioIO
static var lastNumberOfSamples:UInt32!
static var samplerate:UInt32!
override init() {
super.init()
audioIO = SuperpoweredIOSAudioIO.init(delegate: self, preferredBufferSize: 12, preferredMinimumSamplerate: 44100, audioSessionCategory: AVAudioSessionCategoryPlayAndRecord, channels: 2, audioProcessingCallback: EchoesEngine.audioProcessingCallback, clientdata: bridge(obj: self))
…
}
#objc static let audioProcessingCallback : #convention(c) (UnsafeMutableRawPointer?, UnsafeMutablePointer<UnsafeMutablePointer<Float>?>?, UInt32, UInt32, UInt32, UInt32, UInt64) -> Bool = {
(clientdata, buffers, inputChannels, outputChannels, numberOfSamples, _samplerate, hostTime) in
/*
let unsafePointer = Unmanaged<EchoesEngine>.fromOpaque(clientdata!).takeUnretainedValue()
let pointer = AutoreleasingUnsafeMutablePointer<EchoesEngine>(unsafePointer)
*/
self = bridge(ptr: clientdata!)
if samplerate != _samplerate {
samplerate = _samplerate
}
lastNumberOfSamples = numberOfSamples
return false
}

So I discovered that in this context the self I was trying to cast to is actually a new variable providing an __unsafe_unretained reference to self, as passed to the initialiser for SuperpoweredIOSAudioIO.
This answer provided me with almost everything I needed. Swift 3.3 changes a couple of things, and this is what I ended up with:
func bridge<T : AnyObject>(obj : T) -> UnsafeMutableRawPointer {
return UnsafeMutableRawPointer(Unmanaged.passUnretained(obj).toOpaque())
// return unsafeAddressOf(obj) // ***
}
func bridge<T : AnyObject>(ptr : UnsafeMutableRawPointer?) -> T {
return Unmanaged<T>.fromOpaque(ptr!).takeUnretainedValue()
// return unsafeBitCast(ptr, T.self) // ***
}

Related

Incompatible pointer types passing 'struct NSArray *' to parameter of type 'NSArray *

I was following a tutorial on calling objective-c code from golang. The tutorial is at this link
The code is as follows ( it is the same on the tutorial )
main.go
package main
import (
"fmt"
"net/url"
"strconv"
"unsafe"
)
//#cgo CFLAGS: -x objective-c
//#cgo LDFLAGS: -framework Foundation
//#include "foundation.h"
import "C"
// NSString -> C string
func cstring(s *C.NSString) *C.char { return C.nsstring2cstring(s) }
// NSString -> Go string
func gostring(s *C.NSString) string { return C.GoString(cstring(s)) }
// NSNumber -> Go int
func goint(i *C.NSNumber) int { return int(C.nsnumber2int(i)) }
// NSArray length
func nsarraylen(arr *C.NSArray) uint { return uint(C.nsarraylen(arr)) }
// NSArray item
func nsarrayitem(arr *C.NSArray, i uint) unsafe.Pointer {
return C.nsarrayitem(arr, C.ulong(i))
}
// NSURL -> Go url.URL
func gourl(nsurlptr *C.NSURL) *url.URL {
nsurl := *C.nsurldata(nsurlptr)
userInfo := url.UserPassword(
gostring(nsurl.user),
gostring(nsurl.password),
)
host := gostring(nsurl.host)
if nsurl.port != nil {
port := goint(nsurl.port)
host = host + ":" + strconv.FormatInt(int64(port), 10)
}
return &url.URL{
Scheme: gostring(nsurl.scheme),
User: userInfo, // username and password information
Host: host, // host or host:port
Path: gostring(nsurl.path),
RawQuery: gostring(nsurl.query), // encoded query values, without '?'
Fragment: gostring(nsurl.fragment), // fragment for references, without '#'
}
}
// NSArray<NSURL> -> Go []url.URL
func gourls(arr *C.NSArray) []url.URL {
var result []url.URL
length := nsarraylen(arr)
for i := uint(0); i < length; i++ {
nsurl := (*C.NSURL)(nsarrayitem(arr, i))
u := gourl(nsurl)
result = append(result, *u)
}
return result
}
func UserApplicationSupportDirectories() []url.URL {
return gourls(C.UserApplicationSupportDirectories())
}
func main() {
fmt.Printf("%#+v\n", UserApplicationSupportDirectories())
}
Foundation.h
#import <Foundation/Foundation.h>
typedef struct _NSURLdata {
NSString *scheme;
NSString *user;
NSString *password;
NSString *host;
NSNumber *port;
NSString *path;
NSString *query;
NSString *fragment;
} NSURLdata;
const char* nsstring2cstring(NSString*);
int nsnumber2int(NSNumber*);
unsigned long nsarraylen(NSArray*);
const void* nsarrayitem(NSArray*, unsigned long);
const NSURLdata* nsurldata(NSURL*);
const NSArray* UserApplicationSupportDirectories();
Foundation.m
#import "foundation.h"
const char*
nsstring2cstring(NSString *s) {
if (s == NULL) { return NULL; }
const char *cstr = [s UTF8String];
return cstr;
}
int
nsnumber2int(NSNumber *i) {
if (i == NULL) { return 0; }
return i.intValue;
}
unsigned long
nsarraylen(NSArray *arr) {
if (arr == NULL) { return 0; }
return arr.count;
}
const void*
nsarrayitem(NSArray *arr, unsigned long i) {
if (arr == NULL) { return NULL; }
return [arr objectAtIndex:i];
}
const NSURLdata*
nsurldata(NSURL *url) {
NSURLdata *urldata = malloc(sizeof(NSURLdata));
urldata->scheme = url.scheme;
urldata->user = url.user;
urldata->password = url.password;
urldata->host = url.host;
urldata->port = url.port;
urldata->path = url.path;
urldata->query = url.query;
urldata->fragment = url.fragment;
return urldata;
}
const NSArray*
UserApplicationSupportDirectories() {
NSFileManager *manager = [NSFileManager defaultManager];
return [manager URLsForDirectory: NSApplicationSupportDirectory
inDomains: NSUserDomainMask];
}
When I build this code, I get the following warnings from the compiler
cgo-gcc-prolog:70:47: warning: incompatible pointer types passing 'struct NSArray *' to parameter of type 'NSArray *' [-Wincompatible-pointer-types]
./foundation.h:17:33: note: passing argument to parameter here
cgo-gcc-prolog:88:22: warning: incompatible pointer types passing 'struct NSArray *' to parameter of type 'NSArray *' [-Wincompatible-pointer-types]
./foundation.h:16:34: note: passing argument to parameter here
cgo-gcc-prolog:107:24: warning: incompatible pointer types passing 'struct NSNumber *' to parameter of type 'NSNumber *' [-Wincompatible-pointer-types]
./foundation.h:15:27: note: passing argument to parameter here
cgo-gcc-prolog:125:52: warning: incompatible pointer types passing 'struct NSString *' to parameter of type 'NSString *' [-Wincompatible-pointer-types]
./foundation.h:14:39: note: passing argument to parameter here
cgo-gcc-prolog:143:45: warning: incompatible pointer types passing 'struct NSURL *' to parameter of type 'NSURL *' [-Wincompatible-pointer-types]
./foundation.h:18:34: note: passing argument to parameter here
Individually compiling the Objective-C code on XCode doesn't show any warnings, and I feel that the CGO-Calls are getting messed up for some reason. How do I avoid the warnings here?
C.NSString generated by CGO is incompatible to NSString by Objective-C. To avoid a warning message from compiler, e.g in const char* nsstring2cstring(), NSString should be pass as void* in parameter in your Objective-C code function (foundation.m foundation.h), and cast your void* to NSString and return as C const char*:
const char* nsstring2cstring(void* s) {
if (s == NULL) { return NULL; }
NSString *cs = *((__unsafe_unretained NSString **)(s));
const char *cstr = [cs UTF8String];
return cstr;
}
In main.go Go code function, It need to pass C.NSString as a unsafe.Pointer to nsstring2cstring(void*)
// NSString -> C string
func cstring(s *C.NSString) *C.char { return C.nsstring2cstring(unsafe.Pointer(s)) }

When are C++ objects destroyed in Objective-C++?

I am trying to expose a C++ object by creating an Objective-C++ class to wrap it.
Ultimately, in Swift, I'm trying to write this:
print(JSApplication.eval("'TKTK'")?.toString() ?? "")
print(JSApplication.eval("x = {a: 42}; x")?.toString() ?? "")
print(JSApplication.eval("x")?.get("a")?.toString() ?? "nope");
print(JSApplication.eval("1 + 2")?.toInt32() ?? 0)
However, when I try to call JSApplication.eval("x")?.get("a")?.toString(), Objective-C calls dealloc on my class after .get("a") but before .toString().
Normally in dealloc I would call .reset() on the shared pointer that the class contains. But since the dealloc is firing too early, this would clear out my V8 result before .toString() could be called on it.
This raises a general question: How does Swift / Objective-C decide when to call dealloc on a temporary object? For something like foo()?.bar()?.baz(), where foo and bar return temporary objects, is it correct that both temp objects are receiving a dealloc message before baz is called? That's what I'm seeing.
If that's the correct behavior, then what's the proper way to extend the lifetime of a temporary object to the scope where the function is being called, like C++? Is that possible?
Here's my Objective-C++ binding. (I notice that no "Destroy 0x..." messages are being printed out at all, so C++ destructors don't seem to be firing. Am I supposed to call those manually?)
// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}
#interface NJSValue (V8)
- (instancetype)init;
- (instancetype)initWithValue:(Local<Value>)value;
#end
struct NJSRef
{
std::shared_ptr<Nan::Persistent<Value>> _ref;
~NJSRef()
{
printf("Destroy 0x%08x\n", (unsigned int)(size_t)_ref.get());
}
};
#implementation NJSValue (V8)
NJSRef m;
- (instancetype)init
{
self = [super init];
return self;
}
- (instancetype)initWithValue:(Local<Value>)value
{
self = [super init];
Nan::HandleScope scope;
Nan::EscapableHandleScope escape;
m._ref.reset(new Nan::Persistent<Value>(escape.Escape(value)));
printf("Alloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
return self;
}
#end
#implementation NJSValue
- (void)dealloc
{
printf("Dealloc 0x%08x\n", (unsigned int)(size_t)m._ref.get());
//m_ref.reset();
}
- ( NSString * _Nonnull )toString
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
v8::String::Utf8Value str(JS_ISOLATE(), value);
const char* cstr = ToCString(str);
return NJSStringToNSString(JS_STR(cstr));
} else {
return #"undefined";
}
}
- (NSNumber *)toInt32
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
if (!value->IsInt32()) return nullptr;
return [NSNumber numberWithInt:TO_INT32(value)];
} else {
return nullptr;
}
}
- (NSNumber *)toNumber
{
if (m._ref != nullptr) {
Nan::HandleScope scope;
Local<Value> value(Nan::New(*m._ref));
if (!value->IsNumber()) return nullptr;
return [NSNumber numberWithDouble:TO_DOUBLE(value)];
} else {
return nullptr;
}
}
- (NJSValue * _Nullable __strong)get:(NSString * _Nonnull)key CF_RETURNS_RETAINED
{
if (m._ref == nullptr) return nullptr;
v8::HandleScope scope(JS_ISOLATE());
v8::EscapableHandleScope handle_scope(JS_ISOLATE());
Local<Value> value(Nan::New(*m._ref));
if (!value->IsObject()) return nullptr;
Local<Object> obj(JS_OBJ(value));
Local<Value> jsKey(JS_STR([key UTF8String]));
if (!obj->Has(JS_CONTEXT(), jsKey).FromJust()) return nullptr;
Local<Value> result(obj->Get(jsKey));
v8::String::Utf8Value str(JS_ISOLATE(), result);
const char* cstr = ToCString(str);
printf("got %s\n", cstr);
NJSValue* ret = [[NJSValue alloc] initWithValue:handle_scope.Escape(result)];
// [self associateValue:ret withKey:key];
return ret;
}
#end
#implementation JSApplication
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super init];
if (self) {
self.frame = frame;
}
return self;
}
// Executes a string within the current v8 context.
v8::Local<v8::Value>
ExecuteString(v8::Isolate* isolate, v8::Local<v8::String> source,
v8::Local<v8::Value> name, bool print_result,
bool report_exceptions) {
v8::EscapableHandleScope handle_scope(isolate);
v8::TryCatch try_catch(isolate);
v8::ScriptOrigin origin(name);
v8::Local<v8::Context> context(isolate->GetCurrentContext());
v8::Local<v8::Script> script;
if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
// Print errors that happened during compilation.
if (report_exceptions)
ReportException(isolate, &try_catch);
return handle_scope.Escape(v8::Undefined(isolate));
} else {
v8::Local<v8::Value> result;
if (!script->Run(context).ToLocal(&result)) {
assert(try_catch.HasCaught());
// Print errors that happened during execution.
if (report_exceptions)
ReportException(isolate, &try_catch);
return handle_scope.Escape(v8::Undefined(isolate));
} else {
assert(!try_catch.HasCaught());
if (print_result && !result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(isolate, result);
const char* cstr = ToCString(str);
printf("eval result: %s\n", cstr);
}
return handle_scope.Escape(result);
}
}
}
void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch) {
v8::HandleScope handle_scope(isolate);
v8::String::Utf8Value exception(isolate, try_catch->Exception());
const char* exception_string = ToCString(exception);
v8::Local<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
// V8 didn't provide any extra information about this error; just
// print the exception.
fprintf(stderr, "%s\n", exception_string);
} else {
// Print (filename):(line number): (message).
v8::String::Utf8Value filename(isolate,
message->GetScriptOrigin().ResourceName());
v8::Local<v8::Context> context(isolate->GetCurrentContext());
const char* filename_string = ToCString(filename);
int linenum = message->GetLineNumber(context).FromJust();
fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
// Print line of source code.
v8::String::Utf8Value sourceline(
isolate, message->GetSourceLine(context).ToLocalChecked());
const char* sourceline_string = ToCString(sourceline);
fprintf(stderr, "%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
int start = message->GetStartColumn(context).FromJust();
for (int i = 0; i < start; i++) {
fprintf(stderr, " ");
}
int end = message->GetEndColumn(context).FromJust();
for (int i = start; i < end; i++) {
fprintf(stderr, "^");
}
fprintf(stderr, "\n");
v8::Local<v8::Value> stack_trace_string;
if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
stack_trace_string->IsString() &&
v8::Local<v8::String>::Cast(stack_trace_string)->Length() > 0) {
v8::String::Utf8Value stack_trace(isolate, stack_trace_string);
const char* stack_trace_string = ToCString(stack_trace);
fprintf(stderr, "%s\n", stack_trace_string);
}
}
}
+ (NJSValue *)Eval:(NSString *)string __attribute((ns_returns_retained))
{
Isolate* isolate = Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
v8::Context::Scope context_scope(context);
const char* str = [string UTF8String];
Local<Value> result = ExecuteString(
context->GetIsolate(),
v8::String::NewFromUtf8(context->GetIsolate(), str,
v8::NewStringType::kNormal).ToLocalChecked(),
JS_STR("JSApplication.Eval"), false, true);
return [[NJSValue alloc] initWithValue:result];
}
#end
Here's the output I get for the Swift code at the top of this question:
TKTK
Alloc 0x81d34230
Dealloc 0x81d34230
Alloc 0x81d34270
Dealloc 0x81d34270
TKTK
Alloc 0x81d38230
Dealloc 0x81d38230
[object Object]
Alloc 0x81d38250
got 42
Alloc 0x81d38240
Dealloc 0x81d38240
Dealloc 0x81d38240
42
Alloc 0x81d38250
Dealloc 0x81d38250
3
A big part of your problem is that your declaration of NJSRef m; is not declaring an instance variable, even though it's inside an #implementation. It's just a file-scope global. There's just one, and it's being shared (and clobbered) by all of your instances of NJSValue. You would have to enclose it in curly braces {...} to make it an instance variable.
That explains why it's never destroyed, at least. Probably a lot of the other symptoms, too, but it's hard to tell given the external types you're using that I'm not familiar with.

What do non-ARC Objective-C property accessors look like?

I want to know how the getter and setter for an Objective-C property are implemented as part of learning the concept of memory management.
I have not been able to find an actual representation other than "nonatomic" and "atomic".
What does the actual code look like for getters and setters with the different property attributes, such as strong/weak, copy/assign, and __unsafe_unretained?
You can check the source code at the objc4 github repo
getter: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-accessors.mm#L48
id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
setter: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-accessors.mm#L70
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
load weak variable: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/NSObject.mm#L444
id
objc_loadWeakRetained(id *location)
{
id result;
SideTable *table;
retry:
result = *location;
if (!result) return nil;
table = &SideTables()[result];
table->lock();
if (*location != result) {
table->unlock();
goto retry;
}
result = weak_read_no_lock(&table->weak_table, location);
table->unlock();
return result;
}
I don't think there is any code for unsafe_retained. Compiler can just simply assign the pointer without anything else.

What is wrong with the implementation of NSCoding protocol in Swift

I thought I'd be cautious and try out Swift on an existing Obj-C project by converting one class. And a small, simple one at that. Oh dear.
Transliterating the original obj-c into Swift should be straightforward and so it seemed. Unfortunately, whilst the encoder to persistent store seems to work, it crashes with an EXC_BREAKPOINT error at the first line of the init coder.
IF (and the caps are intentional) NSCoding/Swift gives the same persistent content as NSCoding/ObjC, then my all obj-c version should be able to read what is encoded by Swift and vice versa. This proves not to be the case - and my perfectly-functioning obj-c version crashes out when it tries to read the persistent store from the Swift version. Surely, if NSCoding is implemented correctly, it ought to generate something in one that is readable in t'other? Otherwise, there ought to be separate NSCodingSwift and NSCodingObjC protocols?
So, to summarise, I can read/write in obj-c. I can't write/obj-c and read/swift and I can write/swift read/obj-c and I can't read/write in swift.
Here are the two versions:
let keyBeaconItemNameKey = "name"
let keyBeaconItemUUIDKey = "uuid"
let keyBeaconItemMajorValueKey = "major"
let keyBeaconItemMinorValueKey = "minor"
import UIKit
import CoreLocation
class SMBeaconItem : NSObject, NSCoding
{
var name : String!
var uuid : NSUUID!
var major : NSNumber!
var minor : NSNumber!
init(newName : String, newUUID : NSUUID, newMajor : NSNumber, newMinor : NSNumber )
{
name = newName
uuid = newUUID
major = newMajor
minor = newMinor
}
init( coder decoder : NSCoder!)
{
name = decoder.decodeObjectForKey(keyBeaconItemNameKey) as String
uuid = decoder.decodeObjectForKey(keyBeaconItemUUIDKey) as NSUUID
major = decoder.decodeObjectForKey(keyBeaconItemMajorValueKey) as NSNumber
minor = decoder.decodeObjectForKey(keyBeaconItemMinorValueKey) as NSNumber
}
func encodeWithCoder( encoder: NSCoder!)
{
encoder.encodeObject(name, forKey:keyBeaconItemNameKey)
encoder.encodeObject(uuid, forKey:keyBeaconItemUUIDKey)
encoder.encodeObject(major, forKey:keyBeaconItemMajorValueKey)
encoder.encodeObject(minor, forKey:keyBeaconItemMinorValueKey)
}
}
And the working original:
#implementation SMBeaconItem
- (instancetype)initWithName:(NSString *)name uuid:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor
{
self = [super init];
if (!self)
{
return nil;
}
_name = name;
_uuid = uuid;
_majorValue = major;
_minorValue = minor;
return self;
}
#pragma mark - Persistence
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self)
{
return nil;
}
_name = [aDecoder decodeObjectForKey:keyBeaconItemNameKey];
_uuid = [aDecoder decodeObjectForKey:keyBeaconItemUUIDKey];
_majorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMajorValueKey] unsignedIntegerValue];
_minorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMinorValueKey] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:keyBeaconItemNameKey];
[aCoder encodeObject:self.uuid forKey:keyBeaconItemUUIDKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.majorValue] forKey:keyBeaconItemMajorValueKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.minorValue] forKey:keyBeaconItemMinorValueKey];
}
#end
Thanks for any help you can give.
The only thing that stands out to me is that you're using String instead of NSString as the name's type. In the betas Apple has been putting out, String (strangely) is not a one-for-one replacement of NSString. Namely, some methods are missing and require calling .bridgeToObjectiveC() to get the NSString version. Using that type instead will probably conform to what NSCoder is expecting, though that difference should not be so.
I didn't actually test this claim since I'm not on my dev machine. But that's my gut instinct. Try it and see what happens! If nothing changes, try switching the order of how you're setting the vars and see if it's a problem related to the name field or simply the first line of the init function.
Below code working is swift for save retrieve NSCoding value in UserDefaults
import UIKit
import Foundation
class ViewController: UIViewController {
var employees: Employees?
let static_key = "nscdeing_data_saved"
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: URL(string: "http://dummy.restapiexample.com/api/v1/employees")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 60)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let status = (response as? HTTPURLResponse)?.statusCode, status == 200, let data = data{
do {
guard let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] else { return }
self.employees = Employees.init(fromDictionary: dic)
let archiveData = try NSKeyedArchiver.archivedData(withRootObject: self.employees as Any, requiringSecureCoding: true)
UserDefaults.standard.set(archiveData, forKey: self.static_key)
} catch let error {
fatalError(error.localizedDescription)
}
}
}.resume()
}
#IBAction func printAction(_ sender: Any) {
if let data = UserDefaults.standard.data(forKey: static_key){
do {
let value = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
print(value as Any)
} catch let error {
fatalError(error.localizedDescription)
}
}
}
}
class Employees : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var data : [Datum]!
var status : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
status = dictionary["status"] as? String
data = [Datum]()
if let dataArray = dictionary["data"] as? [[String:Any]]{
for dic in dataArray{
let value = Datum(fromDictionary: dic)
data.append(value)
}
}
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if status != nil{
dictionary["status"] = status
}
if data != nil{
var dictionaryElements = [[String:Any]]()
for dataElement in data {
dictionaryElements.append(dataElement.toDictionary())
}
dictionary["data"] = dictionaryElements
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
data = aDecoder.decodeObject(forKey: "data") as? [Datum]
status = aDecoder.decodeObject(forKey: "status") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if data != nil{
aCoder.encode(data, forKey: "data")
}
if status != nil{
aCoder.encode(status, forKey: "status")
}
}
}
class Datum : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var employeeAge : String!
var employeeName : String!
var employeeSalary : String!
var id : String!
var profileImage : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
employeeAge = dictionary["employee_age"] as? String
employeeName = dictionary["employee_name"] as? String
employeeSalary = dictionary["employee_salary"] as? String
id = dictionary["id"] as? String
profileImage = dictionary["profile_image"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if employeeAge != nil{
dictionary["employee_age"] = employeeAge
}
if employeeName != nil{
dictionary["employee_name"] = employeeName
}
if employeeSalary != nil{
dictionary["employee_salary"] = employeeSalary
}
if id != nil{
dictionary["id"] = id
}
if profileImage != nil{
dictionary["profile_image"] = profileImage
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
employeeAge = aDecoder.decodeObject(forKey: "employee_age") as? String
employeeName = aDecoder.decodeObject(forKey: "employee_name") as? String
employeeSalary = aDecoder.decodeObject(forKey: "employee_salary") as? String
id = aDecoder.decodeObject(forKey: "id") as? String
profileImage = aDecoder.decodeObject(forKey: "profile_image") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if employeeAge != nil{
aCoder.encode(employeeAge, forKey: "employee_age")
}
if employeeName != nil{
aCoder.encode(employeeName, forKey: "employee_name")
}
if employeeSalary != nil{
aCoder.encode(employeeSalary, forKey: "employee_salary")
}
if id != nil{
aCoder.encode(id, forKey: "id")
}
if profileImage != nil{
aCoder.encode(profileImage, forKey: "profile_image")
}
}
}

property type or class using reflection

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?