Calling Core-Foundation methods from Swift - swift

I'm familiar with Obj-c and getting familiar with Swift, but I'm having a horrendous time struggling with Swift's very strict type-checking. Case in point, all I wanted to do was convert the routine in Obj-c below:
- (void)receiveIncomingConnectionNotification:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
NSFileHandle *incomingFileHandle = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
if (incomingFileHandle) {
CFDictionaryAddValue(
incomingRequests,
(__bridge const void *)(incomingFileHandle),
(__bridge const void *)((__bridge id)CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE)));
}
}
To this in Swift:
func receiveIncomingConnectionNotification(notification: NSNotification) {
let userInfo: NSDictionary = notification.userInfo!
let incomingFileHandle: NSFileHandle? = userInfo.objectForKey(NSFileHandleNotificationFileHandleItem) as? NSFileHandle
if (incomingFileHandle != nil) {
CFDictionaryAddValue(self.incomingRequests as CFMutableDictionaryRef, incomingFileHandle, CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true))
}
}
But the CFDictionaryAddValue call keeps generating errors pointing to the 'true' parameter that says "Cannot convert the expression's type '(CFMutableDictionaryRef, NSFileHandle?, Unmanaged<CFHTTPMessage>!' to type BooleanLiteralConvertible!.
Anyone know the correct way to do this, or what a BooleanLiteralConvertible is?

It will be clearer if you consider the arguments one at a time:
let m = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, 1).takeRetainedValue()
let f = incomingFileHandle!
let d = self.incomingRequests as CFMutableDictionaryRef
CFDictionaryAddValue(d, unsafeAddressOf(f), unsafeAddressOf(m))
The C Boolean must be expressed as a number.
CFHTTPMessageCreateEmpty needs memory management, as it comes back as an Unmanaged.
Your incomingFileHandle is an Optional; it needs unwrapping.
CFDictionaryAddValue expects unsafe addresses (UnsafePointer).

Related

Swift 3 NSArray compare to nil

I'm trying to migrate an objc project to swift3. I'm not sure how can I compare an array to nil. I have found this topic, but that was 2 years ago and the swift's syntax has changed a lot.
If I have a code like this in swift:
let variable = something as? NSArray
if variable == nil {
// do something
}
It won't let me to compare this variable with nil, causing an error "comparing this variable, always returns false". I have tried comparing variable.description with " ", but does it do the same thing?
By "something" i meant:
var variable = dict.object(forKey: someString) as! NSArray
The main thing I wanted to do with this was:
var variable = dict.object(forKey: someString) as! NSArray
if variable == nil {
//create
}
else {
// append
}
That's what the optional unwrapping syntax is for. You can combine the unwrapping and cast into one if statement:
if let variable = something as? NSArray {
// variable is not nil and is an NSArray
// Now you can do something with it.
} else {
// Either something is nil or it is not able to be cast as an NSArray
// Handle this case.
}
I should also mention that if you don't need to use something in Objective-C, then you should use the Swift-native array type. This can be declared like this:
let someArray = ["string1", "string2"]
This line indicates that variable is and must be an NSArray. If dict.object(forKey: someString) is not an NSArray, this will cause a crash
var variable = dict.object(forKey: someString) as! NSArray
// ^
// This exclamation mark means you are certain this is an NSArray
// Also, because there is no question mark after NSArray, this variable
// is not optional. It cannot be nil
However, you then use
if variable == nil {
And this is where the warning comes from. The variable can never be nil, because the variable is not optional
What you probably want is:
if let variable = dict.object(forKey:someString) as? NSArray
This will return false if:
dict.object(forKey:someString) returns a nil object
the object returned is not an NSArray
After this variable is now a non-optional NSArray. It is guaranteed to be an NSArray and is guaranteed to not be nil. You can use it without unwrapping it. e.g.
if let variable = dict.object(forKey:someString) as? NSArray {
for element in variable {
}
}
else {
//The dict doesn't contain the object yet. `variable` is nil
//Create a new array and add it to dict
let newArray = ["First Value"]
dict[someString] = newArray
}
let variable = something as? NSArray
With this declaration, variable will be an optional type (NSArray?) and never nil. This is because casting with as? returns an optional value that either contains the successfully casted object or nothing. You can see this by alt-clicking the variable name in Xcode.
If you want to know whether it contains a value, you need to use the if let syntax:
if let variable = variable {
// variable is guaranteed to be an NSArray here.
}
You can also use this format with guard-else:
guard let variable = something as? NSArray else {
// your variable is nil. Do something if needed
}
// your variable is available in this scope. Do something when variable contains Array

App Delegate weird error when trying to add element to NSUserDefaults

I've got a really weird error while running my app on Xcode 7 (Swift 2) that shows a "Thread 1: signal SIGABRT" running error message in the App Delegate class of my app. However I've actually already got this "Thread 1: signal SIGABRT" running error message in the App Delegate class lots of times, mainly when deleting an outlet reference in my code and forgetting to also delete it from storyboard. But that's certainly the first time I've got this same error when trying to make the command:
let wasteGain = WastesGainsClass(value: enteredMoney, originOrCat: segControlArray[segControl.selectedSegmentIndex], specification: plusEspecTField.text!, date: dateArray, mode: "gain")
gains.append(wasteGain)
NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains")
What happens is that if I just comment the line NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains") the app doesn't crash! So the error might just be in that line.
If anyone could help me, I`d thank you so much.
PS: WastesGainsClass format is like this:
class WastesGainsClass {
var value:Int = 0
var origin:String
var specification:String
var date:[String]
var mode:String
var rowMode:Int = 0
init(value:Int, originOrCat:String, specification:String, date:[String], mode:String) {
self.value = value
self.origin = originOrCat
self.specification = specification
self.date = date
self.mode = mode
}
}
From documentation:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
In Swift you can also use:
Int, UInt, Double, Float and Bool types because they are automatically bridged to NSNumber;
String bridged to NSString
[AnyObject] because it is bridged to NSArray;
[NSObject: AnyObject] because it is bridged to NSDictionary.
Of course type of array elements and dictionary values must be one of above types. Dictionary key type must be NSString (or bridged String).
To store instances of any other class you have two options:
Your custom class must be subclass of NSObject and conform to
NSCoding protocol and then you can archive object of this class to NSData with NSKeyedArchiver.archivedDataWithRootObject() and save it to NSUserDefaults and later retrieve it from NSUserDefaults and unarchive with NSKeyedUnarchiver.unarchiveObjectWithData():
import Foundation
class WastesGainsClass: NSObject, NSCoding {
var value: Int
init(value: Int) {
self.value = value
}
required init(coder aDecoder: NSCoder) {
value = aDecoder.decodeObjectForKey("value") as! Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(value, forKey: "value")
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { NSKeyedArchiver.archivedDataWithRootObject($0) }, forKey: "gains")
if let gainsData = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [NSData] {
gains = gainsData.map { NSKeyedUnarchiver.unarchiveObjectWithData($0) as! WastesGainsClass }
}
You can save your custom object properties to dictionary and store that
dictionary in NSUserDefaults:
import Foundation
class WastesGainsClass {
var value: Int
init(value: Int) {
self.value = value
}
}
extension WastesGainsClass {
convenience init(dict: [NSObject: AnyObject]) {
self.init(value: dict["value"] as? Int ?? 0)
}
func toDict() -> [NSObject: AnyObject] {
var d = [NSObject: AnyObject]()
d["value"] = value
return d
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { $0.toDict() }, forKey: "gains")
if let dicts = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [[NSObject: AnyObject]] {
gains = dicts.map { WastesGainsClass(dict: $0) }
}
NSUserDefaults unfortunately can't accept arbitrary objects, only objects that can be encoded in a Property List. See Apple's reference guide for Property Lists to learn which objects can be stored.
If you need to save several WastesGainsClass objects, you may wish to write a method that returns a Dictionary encoding their Property List-representable properties, and an initializer that accepts such a Dictionary to restore the object.
However, if you truly need to save multiple custom objects like this, you probably don't want to use NSUserDefaults at all. Consider a document-based app, and look into NSCoding.
The code you posted tries to save an array of custom objects to NSUserDefaults. You can't do that. Implementing the NSCoding methods doesn't help. You can only store things like NSArray, NSDictionary, NSString, NSData, NSNumber, and NSDate in NSUserDefaults.
You need to convert the object to NSData (like you have in some of the code) and store that NSData in NSUserDefaults. You can even store an NSArray of NSData if you need to.
see this post : Attempt to set a non-property-list object as an NSUserDefaults

Custom NSError weird behaviour Swift

In Swift, I have a custom NSError, I need to get the error userInfo dictionary and add things later, but it is nil in the assign line, but then error.userInfo have an object...
With error.userInfo as nil:
class MyError: NSError {
init(error: NSError) {
var newUserInfo = error.userInfo
...newUserInfo is nil...
super.init(...)
}
}
If I assign it 2 times it works ( I know there's something missing but what?)
init(error: NSError) {
var newUserInfo = error.userInfo
newUserInfo = error.userInfo
...newUserInfo now contains a dictionary...
}
Why?
This looks maybe compiler bug-ey to me, but it's hard to tell without seeing more of your code. At any rate, this sort of thing is easier to debug if you use conditional cast. userInfo in swift is a Dictionary<NSObject: AnyObject>?; if you're getting this from a Cocoa API you can do something like:
if let userInfo = error.userInfo as? [NSObject: NSObject] {
// modify and assign values as necessary
}
this will at least make it clearer where things are breaking.

Address Book External Change Callback in Swift (with C Function Pointers?)

Original Question (see solution below):
I am trying to use the AddressBook.framework in my Swift App, but can't figure out how to implement the ABAddressBookRegisterExternalChangeCallback function.
In Objective-C, I just implement the callback as a C function and pass its pointer:
// somewhere in the initializer of the MyAddressBook class:
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(nil, nil);
ABAddressBookRegisterExternalChangeCallback(addressBook, externalChangeCallback, (__bridge void *)(self));
// somewhere else in the MyAddressBook class:
void externalChangeCallback(ABAddressBookRef reference, CFDictionaryRef info, void *context)
{
[(__bridge MyAddressBook *)context addressBookDidChangeExternally];
}
- (void)addressBookDidChangeExternally
{
// good old Obj-C from here on!
}
In Swift, it is proving very difficult for me to handle C functions. I found that Apple added the ability to pass C function pointers around in beta 3, but how do I declare such a function? It would be good to use Swift's closure syntax, but is that even possible here?
This is where I create the ABAddressBookRef:
var addressBookRef: ABAddressBookRef = {
let addressBookRef: ABAddressBookRef = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
// TODO: how do I make this work?
let externalChangeCallback: ABExternalChangeCallback = {
println("Address book changed externally!")
}
ABAddressBookRegisterExternalChangeCallback(addressBookRef, externalChangeCallback, nil)
return addressBookRef
}()
So how can I implement this in Swift?
Solution (with flaws):
As suggested by pNre, this is how I implemented it now:
In Objective-C:
AddressBookExternalChangeCallback.h:
#import <AddressBook/AddressBook.h>
void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef);
AddressBookExternalChangeCallback.m:
#import "AddressBookExternalChangeCallback.h"
void addressBookExternalChangeCallback(ABAddressBookRef addressBookRef, CFDictionaryRef info, void *context)
{
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddressBookDidChangeExternallyNotification" object:nil];
});
}
void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef)
{
ABAddressBookRegisterExternalChangeCallback(addressBookRef, addressBookExternalChangeCallback, nil);
}
In Swift:
after importing bridging header:
registerExternalChangeCallbackForAddressBook(addressBookRef)
A notification is posted whenever the address book changes. Only #objc classes can register for notifications, though, so is there a way to call a Swift function or method instead?
ABExternalChangeCallback is defined as
typealias ABExternalChangeCallback = CFunctionPointer<((ABAddressBookRef!, CFDictionary!, UnsafeMutablePointer<()>) -> Void)>
From the Xcode release notes:
However, you cannot call a C function pointer (CFunctionPointer) or
convert a closure to C function pointer type.
This means you can't assign a block the way you're doing.
However you can bypass this limitation calling ABAddressBookRegisterExternalChangeCallback in an objc function and calling it from your swift code.
Swift 2.0
if let addressBook = ABAddressBookCreateWithOptions(nil, nil) {
let ref = addressBook.takeRetainedValue()
let callback: #convention(c) (addressBookRef: ABAddressBookRef!, info: CFDictionaryRef!, context: UnsafeMutablePointer<Void>) -> Void = {
(addressBookRef, info, context) in
// do the things you want
}
let addressBookChangeCallback = unsafeBitCast(callback, ABExternalChangeCallback.self)
ABAddressBookRegisterExternalChangeCallback(ref, addressBookChangeCallback, nil)
}
in swift 4.0
class ABAddressBookManager: NSObject {
static let shared = ABAddressBookManager()
private var addressBook: ABAddressBook?
override init() {
super.init()
let addressBookForObserving = ABAddressBookCreate().takeRetainedValue()
let context = Unmanaged.passUnretained(self).toOpaque()
DispatchQueue.main.async {
ABAddressBookRegisterExternalChangeCallback(addressBookForObserving, { (addressBook, info, context) in
guard let context = context else {
return
}
let manager = Unmanaged<ABAddressBookManager>.fromOpaque(context).takeUnretainedValue()
//call manager's method
}
}
self.addressBook = addressBookForObserving
}
}
I got the same issue. But I have cleared the cache by calling ABAddressBookRevert() in addressbook object.

MKLocalSearch on Swift init() doesn't work

i am trying to use MKLocalSearch Api in Swift. But I can't get it to work. The error is coming from
var search:MKLocalSearch = MKLocalSearch.init(request)
I read the documentation, and it state the method name is init(request:) I am not sure what i did wrong. please advice. :)
var request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchTextFiled.text
println(searchTextFiled.text)
request.region = self.mapView.region //need to define region later
var search:MKLocalSearch = MKLocalSearch.init(request)
search.startWithCompletionHandler {
(response:MKLocalSearchResponse!, error:NSError!) in
if !error {
var placemarks:NSMutableArray = NSMutableArray()
for item in response.mapItems {
placemarks.addObject(placemarks)
}
self.mapView.removeAnnotations(self.mapView.annotations)
self.mapView.showAnnotations(placemarks, animated: true)
} else {
}
}
Change this line:
var search:MKLocalSearch = MKLocalSearch.init(request)
To:
var search:MKLocalSearch = MKLocalSearch.init(request: request)
It's made a little confusing by the naming involved in this case - the first part, request: is a label that tells Swift which init function to call - in this case, it's mapping back to the Objective-C initWithRequest: method.
The second part, request is your MKLocalSearchRequest variable that you're passing to the init function. So the Objective-C equivalent is this:
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
There's a whole chapter titled "Initialization" in Apple's The Swift Programming Language book, available on the iBooks store and as a PDF here, albeit with worse (bordering on terrible) formatting in the PDF.
That's incorrect syntax for init. They're implied by Swift, so you'd use
var x = MKLocalSearch(request: request)
not
var x = MKLocalSearch.init(request)