NSFastEnumeration in Swift - ios

I am trying to convert an Objective-C project to swift, but I am unable to find how to use NSFastEnumeration for an object of a class that conforms to NSFastEnumeration.
Here is the code in ObjC:
// get the decode results
id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results)
// just grab the first barcode
break;
so far I tried to find how to do this, but this doe not seems work, here is the swift code:
var results: ZBarSymbolSet = infoDictionary?.objectForKey(ZBarReaderControllerResults) as ZBarSymbolSet
var symbol : ZBarSymbol? = nil;
for symbol in results
{ //just grab first barcode
break;
}
the error comes in for condition - "ZBarSymbolSet" does not have a member named "Generator"
What am I doing wrong?
Here is the screen shot

After a while poking around the swift framework files, I finally found this nice class called NSFastGenerator. NSSet and friends seem to be using the same Generator.
For ZBarSymbolSet, here's how you'd extend it to support for-in loops:
extension ZBarSymbolSet: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
Update: Looks like Swift 2.0's protocol extensions fixed this for us!

Your defined class ZBarSymbolSet needs to implement the Swift SequenceType interface in order to be usable in for <identifier> in <sequence> syntax. The SequenceType interface is
protocol SequenceType : _Sequence_Type {
typealias Generator : GeneratorType
func generate() -> Generator
}
and thus you see the mention of Generator as reported in your error message.
Also in the syntax:
for <identifier> in <sequence> {
<statements>
}
the <identifer> is only in scope for <statements>. Thus your second use of symbol in the if will be out of scope and an error. One proper idiom would be:
var symbolFound : ZBarSymbol?
for symbol in result {
symbolFound = symbol
break
}
if symbolFound ...
If course, but the time ZBarSymbolSet implements SequenceType it would also implement CollectionType with subscript and thus the whole 'find the first element' code would be var symbol = result[0]

Here is John Estropia's answer for Swift 3:
extension ZBarSymbolSet: Sequence {
public typealias Iterator = NSFastEnumerationIterator
public func makeIterator() -> NSFastEnumerationIterator {
return NSFastEnumerationIterator(self)
}
}
Then you for-in loop would look like this:
for element in results {
let symbol = element as! ZBarSymbol
// ...
}
This answer could be improved by also adopting the IteratorProtocol so you can specify the element associated type as ZBarSymbol. I have not figured out how to do this yet.

Step1:
extension ZBarSymbolSet: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
Step2:
var results: NSFastEnumeration = info.objectForKey(ZBarReaderControllerResults) as NSFastEnumeration
var symbolFound : ZBarSymbol?
for symbol in results as ZBarSymbolSet {
symbolFound = symbol as? ZBarSymbol
break
}
resultString = NSString(string: symbolFound!.data)

Here's a way to do it without writing an extension
var iterator = NSFastEnumerationIterator(collection)
while let element = iterator.next() {
// use element
}

Also, if you know that all objects in your ZBarSymbolSet are ZBarSymbol objects (since ObjC doesn't enforce all objects being ZBarSymbol objects), you can do the following:
extension ZBarSymbolSet {
public struct ZBarSymbolSetIterator {
public typealias Element = ZBarSymbol
private let enumerator: NSFastEnumerationIterator
init(_ symbols: ZBarSymbolSet) {
self.enumerator = NSFastEnumerationIterator(symbols)
}
public mutating func next() -> ZBarSymbol {
if let object = self.enumerator.next() {
return object as? ZBarSymbol
}
else { return nil }
}
}
public func makeIterator() -> ZBarSymbolSetIterator {
return ZBarSymbolSetIterator(self)
}
}
Now your for-loop will look like this:
for element in results {
// element is a ZBarSymbol
}

Related

Error message when defining struct

I am writing a struct in Swift:
struct LevelDictionary {
let kNumberOfSegments: Int = 10
static func loadLevelData() -> NSDictionary {
for var segmentNumber = 0; segmentNumber < kNumberOfSegments; ++segmentNumber {
//My code here
}
return dictionary
}
}
For some reason I get an error on compiling: Instance member 'kNumberOfSegments' cannot be used on type 'LevelDictionary'. What am I missing? I get the same error when I set up LevelDictionary as a Class.
loadLevelData() is a static function which is called on "class" level
LevelDictionary.loadLevelData()
To use kNumberOfSegments in the static function it must be static as well
static let kNumberOfSegments: Int = 10
The direct answer to your question is that you can't use a property in class scope.
A different answer is that you seem to want a static function that returns a dictionary after doing something a certain number of times; which is why you have kNumberOfSegments in the first place. But do you really need to have a variable for something that you aren't going to use again. Another way to do this is to have a default variable in your class method:
struct LevelDictionary {
static func loadLevelData(numberOfSegments: Int = 10) -> NSDictionary {
for segment in 0 ..< numberOfSegments {
// your code here
}
return dictionary
}
}
Now you can call the method without an argument to use the default
let dictionary = LevelDictionary.loadLevelData() // Will use 10 segments
Or you can use a parameter to override the default
let dictianary = LevelDictionary.loadLevelData(20) // Will use 20 segments
You can't use instance member variables/constants inside the static function. (In terms of Objective C you can't use instance member objects inside class function)
Either you should declare the kNumberOfSegments as static or make that function as non-static. I prefer the first option,
struct LevelDictionary
{
static let kNumberOfSegments: Int = 10
static func loadLevelData() -> NSDictionary
{
for var segmentNumber = 0; segmentNumber < kNumberOfSegments; ++segmentNumber
{
//My code here
}
return dictionary
}
}

How to iterate over CustomCollection generic type in Swift

I have a custom collection class with an embedded array written in Obj-c. The class implements NSFastEnumerator protocol in order to be iterable in Obj-c.
For my Swift classes I had to add the following code based on apporaches on SOF.
extension CustomCollection: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
Which again makes it iterable in Swift classes.
All is good until I need to use this class as a Generic type in one of my Swift base classes.
class SomeBaseClass<T: CustomCollection> {
typealias Collection = T
var model: Collection?
// Implementation goes here
}
When I try to iterate over my 'model' property, I get Command Signal Failure Error during build.
Any idea how this needs to be done and whether it's even possible to be done?
Running XCode 7 beta 6 and Swift 2.0
Thanks.
Here is what I came up with Xcode 7.0.1:
First the CustomCollection class. I've keep it simple since I don't know what yours is doing.
public class CustomCollection: NSFastEnumeration
{
var array: NSMutableArray = []
#objc public func countByEnumeratingWithState(state: UnsafeMutablePointer<NSFastEnumerationState>, objects buffer: AutoreleasingUnsafeMutablePointer<AnyObject?>, count len: Int) -> Int {
var index = 0
if state.memory.state != 0 {
index = Int(state.memory.state)
}
if index >= self.array.count {
return 0
}
var array = Array<AnyObject?>()
while (index < self.array.count && array.count < len)
{
array.append(self.array[index++])
}
let cArray: UnsafeMutablePointer<AnyObject?> = UnsafeMutablePointer<AnyObject?>.alloc(array.count)
cArray.initializeFrom(array)
state.memory.state = UInt(index)
state.memory.itemsPtr = AutoreleasingUnsafeMutablePointer<AnyObject?>.init(cArray)
return array.count
}
}
Then there's the code you provided.
extension CustomCollection: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
class SomeBaseClass<T: CustomCollection>
{
typealias Collection = T
var model: Collection?
}
With all this, I'm able to run the following
var myModel = CustomCollection()
myModel.array.addObject("This")
myModel.array.addObject("is")
myModel.array.addObject("a")
myModel.array.addObject(["complex", "test"])
var myVar = SomeBaseClass()
myVar.model = myModel
for myObject in myVar.model!
{
print(myObject)
}
And the console prints
This
is
a
(
complex,
test
)
Hope it helps!

Is RawOptionSetType compatible Objective-C?

I recently discovered the way to create NS_OPTIONS equivalent in Swift, however I cannot use them from Objective-C code in a Objective-C/Swift project.
Here is a sample project I did:
ObjcObject.h & ObjcObject.m
typedef NS_OPTIONS(NSUInteger, MyObjcOption)
{
MyOptionNone = 0,
MyObjcOptionCase01 = 1 << 0,
MyObjcOptionCase02 = 1 << 1,
MyObjcOptionCaseAll = MyObjcOptionCase01 | MyObjcOptionCase02
};
#interface ObjcObject : NSObject
+ (void)printMyObjcOption:(MyObjcOption)option;
#end
#implementation ObjcObject
+ (void)printMyObjcOption:(MyObjcOption)option
{
if (option == 0)
NSLog(#"None");
if (option & MyObjcOptionCase01)
NSLog(#"MyObjcOptionCase01");
if (option & MyObjcOptionCase02)
NSLog(#"MyObjcOptionCase02");
}
#end
SwiftObject.swift
struct MySwiftOption: RawOptionSetType, BooleanType {
private var value: UInt = 0
var rawValue: UInt { return self.value }
init(_ value: UInt) { self.value = value }
init(rawValue value: UInt) { self.value = value }
init(nilLiteral: ()) { self.value = 0 }
var boolValue: Bool { return value != 0 }
static var allZeros: MySwiftOption { return self(0) }
static var None: MySwiftOption { return self(0) }
static var All: MySwiftOption { return self.Case01 | self.Case02 }
static var Case01: MySwiftOption { return self(1 << 0) }
static var Case02: MySwiftOption { return self(1 << 1) }
}
public class SwiftObject: NSObject {
class func printMySwiftOption(option: MySwiftOption) {
if option.rawValue == 0 {
println("None")
}
if option & MySwiftOption.Case01 {
println(".Case01")
}
if option & MySwiftOption.Case02 {
println(".Case02")
}
}
class func sayHello() {
println("Hello")
}
}
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObjcOption objcOption = MyObjcOptionCase02;
[ObjcObject printMyObjcOption:objcOption];
[SwiftObject sayHello];
// MySwiftOption swiftOption = MySwiftOptionCase02; // Error: Use of undeclared identifier 'MySwiftOption'
// [SwiftObject printMySwiftOption:swiftOption];
return YES;
}
In the Objective-C code, I always get an error Use of undeclared identifier 'MySwiftOption'.
Is it a known issue? Is there a workaround?
You can't expose structs from Swift to ObjectiveC. Actually, have a look at the -Swift.h file to see what you can access from Objective. You won't find MySwiftOptions in there.
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics Tuples,
Enumerations defined in Swift,
Structures defined in Swift,
Top-level functions defined in Swift,
Global variables defined in Swift,
Typealiases defined in Swift,
Swift-style variadics Nested types,
Curried functions
I, for one, am living with these limitations on a daily bases.
Using Swift with Cocoa and Objective-C at Apple.

set associated objects for literal value in Swift

Well, this possibly a duplicated question. I've found some questions like this one:
Is there a way to set associated objects in Swift?
However, I want to add an Int property into swift's extension and these answers in the link above doesn't work.
Here's my code:
import ObjectiveC
var xoAssociationKey: UInt8 = 0
extension NSData {
var position: Int {
get {
return objc_getAssociatedObject(self, &xoAssociationKey) as Int
}
set {
objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
override convenience init() {
self.init()
position = 0
}
}
And I get fatal error: unexpectedly found nil while unwrapping an Optional value everytime I access position
FYI, I did find a solution for this error in Objective C and I'm looking for a swift solution. Here's my code in objective C if you interested:
static char PROPERTY_KEY;
#implementation NSData (Extension)
#dynamic position;
- (NSInteger)position {
return [objc_getAssociatedObject(self, &PROPERTY_KEY) integerValue];
}
- (void)setPosition:(NSInteger)position {
// Must convert to an object for this trick to work
objc_setAssociatedObject(self, &PROPERTY_KEY, #(position), OBJC_ASSOCIATION_COPY);
}
- (instancetype)init {
self = [super init];
if (self) {
self.position = 0;
}
return self;
}
NSData is part of a class cluster, so your custom init method is not necessarily called,
e.g.
let d = NSMutableData()
does not use your init method. The next problem is that your init method calls
itself recursively, therefore
let d = NSData()
crashes with a stack overflow. Note also that the Objective-C code relies on
undefined behaviour, because it replaces a method in a class extension.
So better remove your custom initialization, and change the getter to
return a default value if the associated object has not been set.
This can easily be achieved with an optional cast (as? Int) and the
nil-coalescing operator (??):
extension NSData {
var position: Int {
get {
return objc_getAssociatedObject(self, &xoAssociationKey) as? Int ?? 0
}
set {
objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}

Terribly Slow migrated Objc to swift code

In an application I've written I have a process that parses a large amount of data from Core-Data and displays it to a graph. While doing this processing I also end up writing the data out to a CSV File. I created a separate class called CSVLine which assists with the creation of the CSV file.
For my test case of 140k recorded my Objective-C code takes aprox 12 seconds to run. After "migrating" the class over to swift It now takes somewhere between 280-360 seconds to run. Obviously I've done something terrible.
Using Instruments I was able to identify the "slow" method and I was wondering if I've done something clear in SWIFT to cause the issue.
Objc
- (void)newLine {
// NSLog(#"Appending %#", self.csvString);
[outData appendData:[self csvData] ];
[self clear];
}
- (void)clear {
// Erase every single value
for (NSUInteger i = 0; i < [values count]; i ++) {
values[i] = #"";
}
}
Swift
func newLine() {
outData.appendData(csvData())
clear()
}
// Clear out the Array
func clear() {
for (var i = 0; i < values.count; i++) {
values[i] = ""
}
}
I'm working with various types of data that are all being written to a single CSV file so there are many blank lines. To accommodate for this I designed this class so that it has an array of keys and an array of values. The keys store the "column" names for the CSV file and values will store either a blank or a value for the index of the key for that data element.
Example:
Keys = [speed,heading,lat,lon]
values might be [200,300,"",""]
or ["","","38.553","25.2256"]
Once I'm done with a line i will write a comma joined list of the values into an internal data structure and clear out the line (erase all the items in the values array). This seems to be where the slowdown is with the swift class. Is there something blatantly "slow" i'm doing when i zero out my array?
Full Swift Class
#objc class CSVLineSwift : NSObject {
// Define Arrays
var keys: [String] = [String]()
var values: [String] = [String]()
var outData : NSMutableData = NSMutableData()
override init() {
}
// Singelton Operator - Thread Safe :: http://code.martinrue.com/posts/the-singleton-pattern-in-swift
class var instance : CSVLineSwift {
// Computed Property
struct Static {
static var instance : CSVLineSwift?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = CSVLineSwift();
}
return Static.instance!
}
// Erase existing Data
func newFile() {
outData = NSMutableData();
outData.appendData(headerData())
}
func csvString() -> String {
return ",".join(values)
}
func csvData() -> NSData {
let string = csvString()
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
return data!
}
func addField(field : String) {
keys.append(field)
values.append("")
}
func setValueForKey(value:String, key:String) {
if let index = find(keys, key) {
values[index] = value
} else {
print( "ERROR -- There was no key: \(key) in the header Array")
}
}
func headerString() -> String {
return ",".join(keys)
}
func headerData() -> NSData {
return headerString().dataUsingEncoding(NSUTF8StringEncoding)!
}
func newLine() {
outData.appendData(csvData())
clear()
}
// Clear out the Array
func clear() {
for (var i = 0; i < values.count; i++) {
values[i] = ""
}
}
func writeToFile(fileName : String) {
outData.writeToFile(fileName, atomically: true)
}
}
Be sure that Swift's optimization level in Build Settings is not -Onone. In my experience, it is orders of magnitude slower than -O. (-O is also the default for 'release', so alternatively you could simply build for release, as already suggested.) As for 'zeroing out' the array, it might be faster (although I do not know) to simply re-initialize the array with a repeated value:
values = [String](count: values.count, repeatedValue: "")
Or if you know you will be appending the new values as you go along, and are not bound to using the indices, you could call:
values.removeAll(keepCapacity: true)
And add the new values with values.append() rather than at indices.
It appears my issues were due to a debug build. In debug build there is no optimization. Once you do a run build the optimization kicks in and things run MUCH MUCH faster....
Similar to the answer posted here: Is Swift really slow at dealing with numbers?

Resources