NSDecimalNumber.adding from NSManagedObjects hitting unrecognized selector issue - swift

First, let me thank everyone in the SO community in advance for your help in getting me out of this jam.
My app hits a runtime error, and I've isolated the line causing the error.
When I attempt to add two NSDecimalNumber variables use the .adding method, I receive this "unrecognized selector sent to instance" error:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFNumber
decimalNumberByAdding:]: unrecognized selector sent to instance
0x608002c361a0'
I've created dummy NSDecimalNumber variables to try and debug this issue, and they seem to work fine when adding. However, when working with my NSManagedObject variables (result and newTransaction) which have NSDecimalNumber variables, I run into this error.
Below is the code that is causing these issues:
//Testing with dummy variables
let a1 = NSDecimalNumber(decimal: 5.2)
let a2 = NSDecimalNumber(decimal: 10.8)
print ("a1: \(a1), a2: \(a2)") //a1: 5.2, a2: 10.8
let a3 = a1.adding(a2)
print ("a3: \(a3)") //a3: 16
//Great, everything above works fine.
//Now let's try using my NSManagedObjects, which were defined in another section
let a = result.netChange //result.netChange is of class NSDecimalNumber
let b = newTransaction.amount //newTransaction.amount is of class NSDecimalNumber
print ("a: \(a), b: \(b)") //a: 444.12, b: 22.23
let c = a.adding(b) //<---This is where the app crashes
print ("c: \(c)") //Does not print, as the app has stopped
My question: Why are my dummy variables able to add with each other, while my NSManagedObject variables cannot?
Thanks again!

A core data property of type "Double" is stored as NSNumber in the
managed object context. It is not sufficient to change the type in the
NSManagedObject subclass because the accessor methods are created
dynamically at runtime. Your code compiles, but crashes at runtime
because the variable is a NSNumber and not a NSDecimalNumber.
The solution is to set the type to "Decimal" in the Core Data model
inspector.

Related

Swift UnsafeMutableRawPointer returns class instead of instance

I’d expect this code to return an NSString with the ID of the currently selected input source. Instead, it seems to return one of the NSString classes itself.
import Foundation
import Carbon
let current = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
let id = TISGetInputSourceProperty(current, kTISPropertyInputSourceID).load(as: NSString.self)
id.length
When I run this in my macOS app, I get this error message in the logs: +[__NSCFConstantString _fastCStringContents:]: unrecognized selector sent to class 0x7fff92cf79e8. How can I fix this issue so I get the correct returned value?
TISGetInputSourceProperty() returns an (unmanaged) raw pointer which must be converted to a CFStringRef, not dereferenced with load(). The CFString can then be bridged to a Swift String.
let current = TISCopyCurrentKeyboardInputSource().takeRetainedValue()
if let ptr = TISGetInputSourceProperty(current, kTISPropertyInputSourceID) {
let id = Unmanaged<CFString>.fromOpaque(ptr).takeUnretainedValue() as String
print(id) // com.apple.keylayout.German
}
Note also that takeRetainedValue() must be used on the return value of TISCopyCurrentKeyboardInputSource() because that function returns a (+1) retained reference, otherwise you'll have a memory leak.

Prevent NSKeyedArchiver throwing exception without Objective-C

On iOS version lower than 11 the throwing archivedData(withRootObject:requiringSecureCoding:) is unavailable, so I have tried to do the equivalent on versions less than iOS 11:
let archiveData: NSData
if #available(iOS 11.0, *) {
archiveData = try NSKeyedArchiver.archivedData(
withRootObject: rootObject,
requiringSecureCoding: true
) as NSData
} else {
NSKeyedArchiver.archivedData(withRootObject: userActivity)
let mutableData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: mutableData)
archiver.requiresSecureCoding = true
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
if let error = archiver.error {
throw error
}
archiver.finishEncoding()
archiveData = mutableData
}
However, when the rootObject calls NSCoder.failWithError(_:) in the encode(with:) function an NSInvalidUnarchiveOperationException exception is raised.
If I subclass NSKeyedArchiver as such:
final class KeyedArchiver: NSKeyedArchiver {
override var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {
return .setErrorAndReturn
}
}
It instead raises an NSInternalInconsistencyException exception with the message Attempting to set decode error on throwing NSCoder.
Is there a way to do this kind of archiving without throwing an exception, short of writing an Objective-C function to catch the exception and throwing it as an error?
The reason you're still getting the exception at encode time is that the .decodingFailurePolicy is effective only when decoding (i.e., unarchiving via NSKeyedUnarchiver), not encoding. Any object that calls .failWithError(_:) on encode will still produce the exception.
Calling .failWithError(_:) at encode-time is relatively rare: usually, once you have a fully constructed object at runtime, it's not terribly likely that it should be in a state that's not encodable. There are of course cases where this is possible, so you really have two options:
If you're working with objects you know and can check ahead of time whether they're in a valid state to encode, you should do that and avoid encoding invalid objects altogether
If you're working with arbitrary objects which you can't validate up-front, you're going to have to wrap your callout to NSKeyedArchiver via an Objective-C function which can catch the exception (and ideally throw an Error containing that exception, like the newer NSKeyedArchiver API does on your behalf)
Based on your comment above, option 2 is your best bet.
As an aside, you can shorten up your fallback code to avoid having to construct an intermediate NSMutableData instance:
let archiver = NSKeyedArchiver()
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
archiveData = archiver.encodedData
The default initializer on NSKeyedArchiver constructs a new archiver with an internal mutable data instance to use, and NSKeyedArchiver.encodedData property automatically calls -finishEncoding on your behalf.

This class is not key value coding-compliant for the key openKey. How to fix errors

The source of the error.
#objc func selectHeader(sender: UIButton) -> Void {
var testSection = NSDictionary()
var openValue = String()
testSection = self.arrHeader.object(at: sender.tag) as! NSDictionary
openValue = testSection.value(forKey: self.isOpenKey) as! String
// value change
if openValue == "Y" {
testSection.setValue("N", forKey: self.isOpenKey) <—— App crash self.isOpenKey == “openKey”
} else {
testSection.setValue("Y", forKey: self.isOpenKey) <—— App crash self.isOpenKey == “openKey”
}
let section: NSIndexSet = NSIndexSet(index: (sender.tag))
self.collectionView.reloadSections(section as IndexSet)
}
TestSection.setValue crashes with the following error.
2018-02-09 16:43:38.141[10730:2091077] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_TtGCs26_SwiftDeferredNSDictionarySSSS_ 0x1665dea0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key openKey.'
* First throw call stack:
libc++abi.dylib: terminating with uncaught exception of type NSException
What is the problem?
You are using the NSDictionary wrong.
First - in response to the comments of #Willeke, you cannot modify an NSDictionary, you need a NSMutableDictionary. You even better might want to use Dictionary.
Then, you should call setObject:forKey:, not setValue:forKey:
I must admit that this API might be a little confusing, because it is so look-alike:
What you want is to map a Dictionary key entry to an object, which is done by setObject:forKey:, in your example (with a mutable dictionary): testSection.setObject("N", forKey: "openKey"). Using the Swift way with a Swift Dictionary, you would write testSection["openKey"] = "N"
When using setValue:forKey:, you are Key-Value-Coding, e.g. you are calling a method ("sending a message") to the NSDictionary instance, and the name of the Message is what you provided in the key value. This would result in something like calling textSection.openKey = "N", thus the exeption
this class is not key value coding-compliant for the key openKey
In the special case of NSMutableDictionary, as #matt mentioned, both setValue:forKey and setObject:forKey behave the same as long as you do not provide an NSString as a key parameter - in that latter case, KVC will be used.
What is the problem?
The "problem" is that you are using NSDictionary in a Swift program, plus you are using key-value coding (value(forKey:), setValue(_:forKey:)) all over the place. Stop it. This is Swift, not Objective-C. Use Swift types and Swift means of talking to them.

NSUserDefault give EXC_BAD_ACCESS trying to get values

I'm using NSUserDefaults in my app to store some flag values. My function increases the value each time that a condition has occurred. Occasionally it works, but most of the time it crashes and get an EXC_BAD_ACCESS message.
var sample1: Int = countWord.integer(forKey: "countWord1")
var sample2: Int = countWord.integer(forKey: "countWord2")
var sample3: Int = countWord.integer(forKey: "countWord3")
Those are the lines where I get the error.
EDIT
This is how I initialize and setcountword
let countWord = UserDefaults.standard
countWord.set(sample1+1, forKey: "countWord1")
Application crashes after repeatedly iterated function
I don't think these are the lines your app is crashing. For exc-bad-access you usually have to trace way back. You can use Instrument to trace it.
The variables defined in the controller and methods are well handled by ARC. This usually happens when you have two controller, for example, ViewController A is trying to access ViewController B's strong variable after ViewController B is deallocated.

How to pass arguments to a custom NSNumber function for a NSExpression in Swift?

Alright guys, I'm struggling with this: I already managed to make custom functions without arguments work like this:
NSExpression(format: "function(1+1, 'myCustomFunction'").expressionValueWithObject(nil, context: nil) as! Double
But I have no idea of how to implement a custom function with arguments, I already tried passing the following String to NSExpression without success:
"function(1+1, 'myCustomFunctionWithArgument(42)')" // 42 as the argument
where the custom function looks like:
public extension NSNumber {
func myCustomFunctionWithArgument(x: Double) -> NSNumber
//...
}
However I get the following error:
-[__NSCFNumber myCustomFunctionWithArgument(42)]: unrecognized selector sent to instance 0xb000000000000045
2016-03-14 18:23:00.755 MyApp[3517:263390] *** Terminating app due to uncaught exception
I already searched everywhere, and I only find Objective-C answers, like on this tutorial:
https://spin.atomicobject.com/2015/03/24/evaluate-string-expressions-ios-objective-c-swift/#comment-549856
In the end he explains how to do it on Objective-C, but not in Swift. Is there any way to do it? Btw this is for my Calculator app.
I got the code below to work in an Xcode 7.2.1 playground. The error you received had to do with the selector, and the main thing to keep in mind for selectors is that you need a colon (:) after the name of the function in this case to signify that the function has a single parameter.
import Foundation
public extension NSNumber {
func myCustomFunctionWithArgument(x: NSNumber) -> NSNumber {
return self.doubleValue + x.doubleValue
}
}
let stringExpression = "function(1+1, 'myCustomFunctionWithArgument:', 42)"
let expression = NSExpression(format: stringExpression)
let result = expression.expressionValueWithObject(nil, context: nil) as! NSNumber
print(result.doubleValue)