XCode Unit Test vs app function call performance - xcode

I see quite a huge discrepancy between the time it takes to process (read, parse) csv file inside a unit test vs exactly the same code reading and parsing the same file from the same location in either simulator or running on a device.
I see around x8 time difference. Running on the main thread.
Edit: The unit test is much faster. Logging times around method call, in both places
More findings:
what I actually found and perhaps this is important is that the function being called actually creates a number of threads and then waits for all of them to complete It splits an array of 180000 rows into chunks and processes each chunk asynchronously. (Using DispatchQueue.global().async and DispatchGroup).
Depending on the number of threads the performance while calling in the app degrades, while in a unit test is fairly similar.
func processCSV(fileName: String, columnToGet:[String]?, block: (([String:String]) ->())? = nil) throws -> ([[String:String]]) {
var userReport = [[String:String]?]()
var wmsEntries = 0
do {
let data = try String(contentsOfFile: fileName, encoding: .utf8)
var myStrings = data.components(separatedBy: .newlines)
let headerRow = myStrings.first?.components(separatedBy: ",")
let headerCount = headerRow?.count ?? 0
var headerColumnMap = [String:Int]()
try columnToGet?.forEach({ column in
guard let index = headerRow?.firstIndex(where: {$0.compare(column, options: .caseInsensitive) == .orderedSame}) else {
throw NSError(domain: "Unexpected or invalid header in csv file.", code: NSFileReadCorruptFileError, userInfo:nil )
}
headerColumnMap[column] = index
})
myStrings = Array(myStrings.dropFirst()).filter({!$0.isEmpty})
wmsEntries = myStrings.count
userReport = [[String:String]?](repeating: nil, count: wmsEntries)
let dispatchGroup = DispatchGroup()
func insert(_ record:[Substring], at:Int) {
var entry = [String:String]()
headerColumnMap.forEach({ key, value in
entry[key] = record[value].trimmingCharacters(in: .whitespacesAndNewlines)
})
DispatchQueue.global().async {
block?(entry)
}
userReport[at] = entry
}
let chunkSize = max(1000, myStrings.count / 9)
for (chunkIndex, chunk) in myStrings.chunked(into: chunkSize).enumerated() {
dispatchGroup.enter()
DispatchQueue.global().async {
for (counter, str) in chunk.enumerated() {
let data = self.parse(line: str)
let insertIndex = chunkIndex * chunkSize + counter
guard data.count == headerCount, data.count > columnToGet?.count ?? 0 else {
DDLogError("Error in file, mismatched number of values, on line \(myStrings[chunkIndex * chunkSize + counter])")
continue
}
insert(data, at: insertIndex)
}
dispatchGroup.leave()
}
}
dispatchGroup.wait()
} catch {
print(error)
}
let filtered = userReport.filter({$0 != nil}) as! [[String:String]]
self.numberLinesWithError = wmsEntries - filtered.count
return filtered
}

Related

Not Executing Function at The Right Time, But Executed After Completion Block

Need help in figuring out why my function is not executing when I thought it should but it executed after the completion block in the code. I am fairly new to Xcode so please excuse me if things sound confusing here. Below is my code.
class ImageDownloader{
typealias completionHandler = (Result<Set<ARReferenceImage>, Error>) -> ()
typealias ImageData = (image: UIImage, orientation: CGImagePropertyOrientation, physicalWidth: CGFloat, name: String)
static let URLDic = [ReferenceImagePayload]()
class func getDocumentData(completion:#escaping ([ReferenceImagePayload]) -> ()) {
var documentCollection: [ReferenceImagePayload] = []
db.collection("Users").getDocuments {(snapshot, error) in
if error == nil && snapshot != nil {
var index = 0
for document in snapshot!.documents {
let loadData = document.data()
index += 1
if loadData["Target URL"] != nil {
let url = loadData["Target URL"]
let urlString = URL(string: "\(String(describing: url ?? ""))")
let urlName = loadData["Target Image"]
documentCollection.append(ReferenceImagePayload(name: urlName as! String, url: urlString!))
if snapshot!.documents.count == index {
// After finished, send back the loaded data
completion(documentCollection)
}
}
}
}
}
}
static var receivedImageData = [ImageData]()
class func downloadImagesFromPaths(_ completion: #escaping completionHandler) {
// THE LINE BELOW WHERE I CALL THE FUNCTION IS NOT EXECUTED WHEN THIS CLASS IS INITIALLY CALLED. BUT AS THE CODE RUNS, THIS LINE BELOW IS EXECUTED AFTER THE COMPLETIONOPERATION = BLOCKOPERATION IS COMPLETED.
let loadedDataDic: () = getDocumentData { (URLDic) in
print(URLDic.self, "Got it")
}
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 6
let completionOperation = BlockOperation {
OperationQueue.main.addOperation({
completion(.success(referenceImageFrom(receivedImageData)))
// LINE "let loadedDataDic: () = getDocumentData" ONLY GOT EXECUTED AT THIS POINT
})
}
URLDic.forEach { (loadData) in
let urlstring = loadData.url
let operation = BlockOperation(block: {
do{
let imageData = try Data(contentsOf: loadData.url)
print(imageData, "Image Data")
if let image = UIImage(data: imageData){
receivedImageData.append(ImageData(image, .up, 0.1, loadData.name))
}
}catch{
completion(.failure(error))
}
})
completionOperation.addDependency(operation)
}
operationQueue.addOperations(completionOperation.dependencies, waitUntilFinished: false)
operationQueue.addOperation(completionOperation)
}
}

How do we read the data continuously from either a text file or server without any user input

I have a variable 'a' created, whose values for example are from a text file on my desktop. I want to read the data continuously and display the value of 'a' in my application.
Even if I change the value from the text file, it should reflect automatically on my application.
Any suggestions?
I'm using Swift 2.2
You can use GCD's Dispatch Source to monitor a file. What follows is simple example where we monitor a file and update an NSTextView with the file's content after every update.
class ViewController: NSViewController {
#IBOutlet var textView: NSTextView!
// Create a dispatch_source_t to monitor the file
func dispatchSoureForFile(at path: String) -> dispatch_source_t? {
let fileHandle = open(path.cStringUsingEncoding(NSUTF8StringEncoding)!, O_RDONLY)
// Cannot open file
guard fileHandle != -1 else {
return nil
}
// The queue where the event handler will execute. Don't set to the main queue
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// The events we are interested in
let mask = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND
return dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, UInt(fileHandle), mask, queue)
}
// The function to use for monitoring a file
func startMonitoringFile(at path: String) {
guard let dispatchSource = dispatchSoureForFile(at: path) else {
print("Cannot create dispatch source to monitor file at '\(path)'")
return
}
let eventHandler = {
let data = dispatch_source_get_data(dispatchSource)
// Tell what change happened to the file. Delete it if you want
if data & DISPATCH_VNODE_WRITE != 0 {
print("File is written to")
}
if data & DISPATCH_VNODE_EXTEND != 0 {
print("File is extended to")
}
if data & DISPATCH_VNODE_DELETE != 0 {
print("File is deleted")
}
// Sometimes the old version of the file is deleted before the new version is written
// to disk. This happens when you call `writeToFile(_, atomically: true)` for example.
// In that case, we want to stop monitoring at the old node and start at the new node
if data & DISPATCH_VNODE_DELETE == 1 {
dispatch_source_cancel(dispatchSource)
self.startMonitoringFile(at: path)
return
}
// Always update the GUI from the main queue
let fileContent = try! String(contentsOfFile: path)
dispatch_async(dispatch_get_main_queue()) {
self.textView.string = fileContent
}
}
// When we stop monitoring a vnode, close the file handle
let cancelHandler = {
let fileHandle = dispatch_source_get_handle(dispatchSource)
close(Int32(fileHandle))
}
dispatch_source_set_registration_handler(dispatchSource, eventHandler)
dispatch_source_set_event_handler(dispatchSource, eventHandler)
dispatch_source_set_cancel_handler(dispatchSource, cancelHandler)
dispatch_resume(dispatchSource)
}
override func viewDidLoad() {
super.viewDidLoad()
self.startMonitoringFile(at: "/path/to/file.txt")
}
}
To trigger a DISPATCH_VNODE_EXTEND event, you can try this at the Terminal:
echo "Hello world" >> /path/to/file.txt
I managed to get the following code to work (in Swift 3.x) for a very similar use case. Hope it helps.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.commandOutputNotification(_:)),
name: NSNotification.Name.NSFileHandleDataAvailable,
object: nil)
self.startTasks()
}
func startTasks() {
self.task = Process()
self.task!.terminationHandler = self.commandTerminationHandler
let argumentsString = "tail -n 1 -f /path/to/file/file.log"
self.task!.launchPath = "/bin/sh"
self.task!.arguments = ["-c", argumentsString]
let pipe = Pipe()
task!.standardOutput = pipe
task!.standardError = pipe
pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
task!.launch()
}
func commandTerminationHandler(_ task: Process) -> Void {
// TODO: finish this
}
func commandOutputNotification(_ notification: Notification) {
let fileHandle = notification.object as! FileHandle
let data = fileHandle.availableData
if data.count > 0 {
processLogData(data:(String.init(data: data, encoding: String.Encoding.utf8)))
fileHandle.waitForDataInBackgroundAndNotify()
}
}
func processLogData(data:String?) {
// Handle data String
}

Swift 2 to swift 3 conversion Midi Input

I'm hoping someone may be able to help i'm using Xcode 8 and swift 3
I have a playground file Xcode 7 swift 2 that involves a Midi callback for Midi Input everything works fine in 7
I tried a conversion to 8 and it brought up errors regarding memory and a few name changes mostly of what i believe to be non serious i also redefined the infinite loop using PlaygroundSupport
However the error i cannot get over involves MyMIDIReadProc at
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
The error says
Cannot convert value of type '(pktList: UnsafePointer, readProcRefCon: UnsafeMutablePointer, srcConnRefCon: UnsafeMutablePointer) -> Void' to expected argument type 'MIDIReadProc' (aka '#convention(c) (UnsafePointer, Optional>, Optional>) -> ()')
My understanding is that it needs a #convention(c) wrapper of some description inserted. I think i'm on the right track because you can wrap a function but my knowledge of where to put it has run out. Again i was hoping some one might be able to advise
Thanks for reading
apologies for any bad language as i'm self taught
Here is the original Xcode 7 code
import Cocoa
import CoreMIDI
import XCPlayground
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.memory;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(COpaquePointer(srcConnRefCon)).memory;
print("MIDI Received From Source: \(getDisplayName(srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
// bytes mirror contains all the zero values in the ridiulous packet data tuple
// so use the packet length to iterate.
var i = packet.length;
for (_, attr) in bytes.enumerate()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
--i;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).memory;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
// Keep playground running
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true;
And here is the Xcode 8 code converted
var str = "Hello, playground"
import Cocoa
import CoreMIDI
import XCPlayground
import PlaygroundSupport
func getDisplayName(obj: MIDIObjectRef) -> String
{
var param: Unmanaged<CFString>?
var name: String = "Error";
let err: OSStatus = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
if err == OSStatus(noErr)
{
name = param!.takeRetainedValue() as String
}
return name;
}
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
let packetList:MIDIPacketList = pktList.pointee;
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
print("MIDI Received From Source: \(getDisplayName(obj: srcRef))");
var packet:MIDIPacket = packetList.packet;
for _ in 1...packetList.numPackets
{
let bytes = Mirror(reflecting: packet.data).children;
var dumpStr = "";
var i = packet.length;
for (_, attr) in bytes.enumerated()
{
dumpStr += String(format:"$%02X ", attr.value as! UInt8);
i -= 1;
if (i <= 0)
{
break;
}
}
print(dumpStr)
packet = MIDIPacketNext(&packet).pointee;
}
}
var midiClient: MIDIClientRef = 0;
var inPort:MIDIPortRef = 0;
var src:MIDIEndpointRef = MIDIGetSource(0);
MIDIClientCreate("MidiTestClient", nil, nil, &midiClient);
MIDIInputPortCreate(midiClient, "MidiTest_InPort", MyMIDIReadProc, nil, &inPort);
MIDIPortConnectSource(inPort, src, &src);
PlaygroundPage.current.needsIndefiniteExecution = true
Pointer types are drastically changed in Swift 3. Many C-based APIs' signatures are changed accordingly.
Following those changes manually would be painful. You can make Swift work for you, with a little modification.
Try changing the function header:
func MyMIDIReadProc(pktList: UnsafePointer<MIDIPacketList>,
readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void
{
to a closure declaration:
let MyMIDIReadProc: MIDIReadProc = {pktList, readProcRefCon, srcConnRefCon in
Swift infers argument types perfectly in this style.
You may need to fix pointer type conversion:
let srcRef:MIDIEndpointRef = UnsafeMutablePointer<MIDIEndpointRef>(OpaquePointer(srcConnRefCon)).pointee;
to something like this:
//I'm not sure using `!` is safe here...
let srcRef: MIDIEndpointRef = UnsafeMutablePointer(srcConnRefCon!).pointee
(By the way, the equivalent part in your Xcode 7 code is a little bit redundant. You have no need to use intermediate COpaquePointer there.)
In Swift 3, pointers cannot be nil, and nullable pointers are represented with Optionals. You may need many other fixes to work with C-based APIs in Swift 3.
OOPer is pointing (ahem) you in the right direction. Here is a blog post on using Swift 3 Core MIDI along with a working github repo.
Assuming that you're working with CoreMIDI 1.3 or later, you may have more luck using MIDIInputPortCreateWithBlock instead of MIDIInputPortCreate.
This method takes a Swift block as a parameter instead of requiring an #convention(c) function reference, making it more amenable to use within methods belonging to Swift classes, e.g.:
public func midiReadBlock(ptr: UnsafePointer<MIDIPacketList>, _: UnsafeMutableRawPointer?) -> Void {
let list: MIDIPacketList = ptr.pointee
...
}
You may also find these two extensions useful.
This one (derived from here) allows you to iterate directly over a MIDIPacketList using for pkt in list:
extension MIDIPacketList: Sequence {
public func makeIterator() -> AnyIterator<MIDIPacket> {
var iterator: MIDIPacket?
var nextIndex: UInt32 = 0
return AnyIterator {
nextIndex += 1
if nextIndex > self.numPackets { return nil }
if iterator != nil {
iterator = withUnsafePointer(to: &iterator!) { MIDIPacketNext($0).pointee }
} else {
iterator = self.packet;
}
return iterator
}
}
}
and this one adds a method to a MIDIPacket to extract the contents as a [UInt8] instead of having to use the really broken tuple syntax:
extension MIDIPacket {
public var asArray: [UInt8] {
let mirror = Mirror(reflecting: self.data)
let length = Int(self.length)
var result = [UInt8]()
result.reserveCapacity(length)
for (n, child) in mirror.children.enumerated() {
if n == length {
break
}
result.append(child.value as! UInt8)
}
return result
}
}

Continuously update a UILabel in Swift

I have a function, shown below, that I would like to continuously update. It is taking data from a webpage, and every so often that webpage is updated to reflect current information. Is there a way that I can catch this update and reflect that in my application? I'm pretty new to Swift and iOS programming. Some of the code made seem very bizarre, but it currently works for whatever song is playing when you first open the app (that is, it updates the text to show that song playing but doesn't update later).
let url = NSURL(string: "http://api.vicradio.org/songs/current")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) in
if error != nil {
return
}
let name = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
var songName = ""
var artistName = "by "
var quoteNumber = 0
for character in name.characters {
if character == "\"" {
quoteNumber++
}
if quoteNumber == 3 && character != "\"" {
songName += String(character)
} else if quoteNumber == 7 && character != "\"" {
artistName += String(character)
}
}
if (songName != "no song metadata provided") {
self.SongNowText.text = songName
self.ArtistNowText.text = artistName
self.SongNowText.setNeedsDisplay()
self.ArtistNowText.setNeedsDisplay()
} else if (songName == "no song metadata provided") {
self.SongNowText.text = "The Best of What's Next!"
self.ArtistNowText.text = "only on VIC Radio"
}
}
task!.resume()
It looks like the URL you're accessing there is an API endpoint putting out JSON. I highly recommend using NSJSONSerialization.JSONObjectWithData to parse the response body into a dictionary and use that instead of rolling your own solution by counting quote marks.
The callback to dataTaskWithURL is executed on a background thread. Avoid updating the UI on anything besides the main thread because it can cause problems. Use dispatch_async to execute your UI update function on the main thread as in the example.
All you can do with this API is send it requests and read the responses. You can poll the endpoint at a regular interval while the app is open and get decent results from that. NSTimer is one way to do that, and it requires you put the method you want to execute repeatedly in a class inheriting from NSObject because it depends on Objective-C style message sending.
Throw this in a playground and try it:
import Cocoa
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely()
class RadioDataAccessor : NSObject {
private let callback: [String : AnyObject] -> Void
init(callback: [String : AnyObject] -> Void) {
self.callback = callback
super.init()
NSTimer.scheduledTimerWithTimeInterval(5.0, target: self,
selector: "updateData", userInfo: nil, repeats: true)
// just so it happens quickly the first time
updateData()
}
func updateData() {
let session = NSURLSession.sharedSession()
let url = NSURL(string: "http://api.vicradio.org/songs/current")!
session.dataTaskWithURL(url) { data, response, error in
if error != nil {
return
}
var jsonError = NSErrorPointer()
let json = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.allZeros,
error: jsonError) as? [String : AnyObject]
if jsonError != nil {
return
}
dispatch_async(dispatch_get_main_queue()) { self.callback(json!) }
}.resume()
}
}
RadioDataAccessor() { data in
println(data)
}
You may want to save the timer to a variable and expose a function that lets you invalidate it.

Read a file/URL line-by-line in Swift

I am trying to read a file given in an NSURL and load it into an array, with items separated by a newline character \n.
Here is the way I've done it so far:
var possList: NSString? = NSString.stringWithContentsOfURL(filePath.URL) as? NSString
if var list = possList {
list = list.componentsSeparatedByString("\n") as NSString[]
return list
}
else {
//return empty list
}
I'm not very happy with this for a couple of reasons. One, I'm working with files that range from a few kilobytes to hundreds of MB in size. As you can imagine, working with strings this large is slow and unwieldy. Secondly, this freezes up the UI when it's executing--again, not good.
I've looked into running this code in a separate thread, but I've been having trouble with that, and besides, it still doesn't solve the problem of dealing with huge strings.
What I'd like to do is something along the lines of the following pseudocode:
var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
currentline = aStreamReader.nextLine()
list.addItem(currentline)
}
How would I accomplish this in Swift?
A few notes about the files I'm reading from: All files consist of short (<255 chars) strings separated by either \n or \r\n. The length of the files range from ~100 lines to over 50 million lines. They may contain European characters, and/or characters with accents.
(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it. An updated version for Swift 3 is provided at the end.)
The following Swift code is heavily inspired by the various answers to
How to read data from NSFileHandle line by line?. It reads from the file in chunks, and converts complete lines to strings.
The default line delimiter (\n), string encoding (UTF-8) and chunk size (4096)
can be set with optional parameters.
class StreamReader {
let encoding : UInt
let chunkSize : Int
var fileHandle : NSFileHandle!
let buffer : NSMutableData!
let delimData : NSData!
var atEof : Bool = false
init?(path: String, delimiter: String = "\n", encoding : UInt = NSUTF8StringEncoding, chunkSize : Int = 4096) {
self.chunkSize = chunkSize
self.encoding = encoding
if let fileHandle = NSFileHandle(forReadingAtPath: path),
delimData = delimiter.dataUsingEncoding(encoding),
buffer = NSMutableData(capacity: chunkSize)
{
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = buffer
} else {
self.fileHandle = nil
self.delimData = nil
self.buffer = nil
return nil
}
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
if atEof {
return nil
}
// Read data chunks from file until a line delimiter is found:
var range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
while range.location == NSNotFound {
let tmpData = fileHandle.readDataOfLength(chunkSize)
if tmpData.length == 0 {
// EOF or read error.
atEof = true
if buffer.length > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = NSString(data: buffer, encoding: encoding)
buffer.length = 0
return line as String?
}
// No more lines.
return nil
}
buffer.appendData(tmpData)
range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
}
// Convert complete line (excluding the delimiter) to a string:
let line = NSString(data: buffer.subdataWithRange(NSMakeRange(0, range.location)),
encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.replaceBytesInRange(NSMakeRange(0, range.location + range.length), withBytes: nil, length: 0)
return line as String?
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seekToFileOffset(0)
buffer.length = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
Usage:
if let aStreamReader = StreamReader(path: "/path/to/file") {
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
print(line)
}
}
You can even use the reader with a for-in loop
for line in aStreamReader {
print(line)
}
by implementing the SequenceType protocol (compare http://robots.thoughtbot.com/swift-sequences):
extension StreamReader : SequenceType {
func generate() -> AnyGenerator<String> {
return AnyGenerator {
return self.nextLine()
}
}
}
Update for Swift 3/Xcode 8 beta 6: Also "modernized" to
use guard and the new Data value type:
class StreamReader {
let encoding : String.Encoding
let chunkSize : Int
var fileHandle : FileHandle!
let delimData : Data
var buffer : Data
var atEof : Bool
init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
chunkSize: Int = 4096) {
guard let fileHandle = FileHandle(forReadingAtPath: path),
let delimData = delimiter.data(using: encoding) else {
return nil
}
self.encoding = encoding
self.chunkSize = chunkSize
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = Data(capacity: chunkSize)
self.atEof = false
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
// Read data chunks from file until a line delimiter is found:
while !atEof {
if let range = buffer.range(of: delimData) {
// Convert complete line (excluding the delimiter) to a string:
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.removeSubrange(0..<range.upperBound)
return line
}
let tmpData = fileHandle.readData(ofLength: chunkSize)
if tmpData.count > 0 {
buffer.append(tmpData)
} else {
// EOF or read error.
atEof = true
if buffer.count > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = String(data: buffer as Data, encoding: encoding)
buffer.count = 0
return line
}
}
}
return nil
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seek(toFileOffset: 0)
buffer.count = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
extension StreamReader : Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator {
return self.nextLine()
}
}
}
Efficient and convenient class for reading text file line by line (Swift 4, Swift 5)
Note: This code is platform independent (macOS, iOS, ubuntu)
import Foundation
/// Read text file line by line in efficient way
public class LineReader {
public let path: String
fileprivate let file: UnsafeMutablePointer<FILE>!
init?(path: String) {
self.path = path
file = fopen(path, "r")
guard file != nil else { return nil }
}
public var nextLine: String? {
var line:UnsafeMutablePointer<CChar>? = nil
var linecap:Int = 0
defer { free(line) }
return getline(&line, &linecap, file) > 0 ? String(cString: line!) : nil
}
deinit {
fclose(file)
}
}
extension LineReader: Sequence {
public func makeIterator() -> AnyIterator<String> {
return AnyIterator<String> {
return self.nextLine
}
}
}
Usage:
guard let reader = LineReader(path: "/Path/to/file.txt") else {
return; // cannot open file
}
for line in reader {
print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines))
}
Repository on github
Swift 4.2 Safe syntax
class LineReader {
let path: String
init?(path: String) {
self.path = path
guard let file = fopen(path, "r") else {
return nil
}
self.file = file
}
deinit {
fclose(file)
}
var nextLine: String? {
var line: UnsafeMutablePointer<CChar>?
var linecap = 0
defer {
free(line)
}
let status = getline(&line, &linecap, file)
guard status > 0, let unwrappedLine = line else {
return nil
}
return String(cString: unwrappedLine)
}
private let file: UnsafeMutablePointer<FILE>
}
extension LineReader: Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator<String> {
return self.nextLine
}
}
}
Usage:
guard let reader = LineReader(path: "/Path/to/file.txt") else {
return
}
reader.forEach { line in
print(line.trimmingCharacters(in: .whitespacesAndNewlines))
}
I'm late to the game, but here's small class I wrote for that purpose. After some different attempts (try to subclass NSInputStream) I found this to be a reasonable and simple approach.
Remember to #import <stdio.h> in your bridging header.
// Use is like this:
let readLine = ReadLine(somePath)
while let line = readLine.readLine() {
// do something...
}
class ReadLine {
private var buf = UnsafeMutablePointer<Int8>.alloc(1024)
private var n: Int = 1024
let path: String
let mode: String = "r"
private lazy var filepointer: UnsafeMutablePointer<FILE> = {
let csmode = self.mode.withCString { cs in return cs }
let cspath = self.path.withCString { cs in return cs }
return fopen(cspath, csmode)
}()
init(path: String) {
self.path = path
}
func readline() -> String? {
// unsafe for unknown input
if getline(&buf, &n, filepointer) > 0 {
return String.fromCString(UnsafePointer<CChar>(buf))
}
return nil
}
deinit {
buf.dealloc(n)
fclose(filepointer)
}
}
This function takes a file URL and returns a sequence which will return every line of the file, reading them lazily. It works with Swift 5. It relies on the underlying getline:
typealias LineState = (
// pointer to a C string representing a line
linePtr:UnsafeMutablePointer<CChar>?,
linecap:Int,
filePtr:UnsafeMutablePointer<FILE>?
)
/// Returns a sequence which iterates through all lines of the the file at the URL.
///
/// - Parameter url: file URL of a file to read
/// - Returns: a Sequence which lazily iterates through lines of the file
///
/// - warning: the caller of this function **must** iterate through all lines of the file, since aborting iteration midway will leak memory and a file pointer
/// - precondition: the file must be UTF8-encoded (which includes, ASCII-encoded)
func lines(ofFile url:URL) -> UnfoldSequence<String,LineState>
{
let initialState:LineState = (linePtr:nil, linecap:0, filePtr:fopen(url.path,"r"))
return sequence(state: initialState, next: { (state) -> String? in
if getline(&state.linePtr, &state.linecap, state.filePtr) > 0,
let theLine = state.linePtr {
return String.init(cString:theLine)
}
else {
if let actualLine = state.linePtr { free(actualLine) }
fclose(state.filePtr)
return nil
}
})
}
So for instance, here's how you would use it to print every line of a file named "foo" in your app bundle:
let url = NSBundle.mainBundle().urlForResource("foo", ofType: nil)!
for line in lines(ofFile:url) {
// suppress print's automatically inserted line ending, since
// lineGenerator captures each line's own new line character.
print(line, separator: "", terminator: "")
}
I developed this answer by modifying Alex Brown's answer to remove a memory leak mentioned by Martin R's comment, and by updating it to for Swift 5.
Try this answer, or read the Mac OS Stream Programming Guide.
You may find that performance will actually be better using the stringWithContentsOfURL, though, as it will be quicker to work with memory-based (or memory-mapped) data than disc-based data.
Executing it on another thread is well documented, also, for example here.
Update
If you don't want to read it all at once, and you don't want to use NSStreams, then you'll probably have to use C-level file I/O. There are many reasons not to do this - blocking, character encoding, handling I/O errors, speed to name but a few - this is what the Foundation libraries are for. I've sketched a simple answer below that just deals with ACSII data:
class StreamReader {
var eofReached = false
let fileHandle: UnsafePointer<FILE>
init (path: String) {
self.fileHandle = fopen(path.bridgeToObjectiveC().UTF8String, "rb".bridgeToObjectiveC().UTF8String)
}
deinit {
fclose(self.fileHandle)
}
func nextLine() -> String {
var nextChar: UInt8 = 0
var stringSoFar = ""
var eolReached = false
while (self.eofReached == false) && (eolReached == false) {
if fread(&nextChar, 1, 1, self.fileHandle) == 1 {
switch nextChar & 0xFF {
case 13, 10 : // CR, LF
eolReached = true
case 0...127 : // Keep it in ASCII
stringSoFar += NSString(bytes:&nextChar, length:1, encoding: NSASCIIStringEncoding)
default :
stringSoFar += "<\(nextChar)>"
}
} else { // EOF or error
self.eofReached = true
}
}
return stringSoFar
}
}
// OP's original request follows:
var aStreamReader = StreamReader(path: "~/Desktop/Test.text".stringByStandardizingPath)
while aStreamReader.eofReached == false { // Changed property name for more accurate meaning
let currentline = aStreamReader.nextLine()
//list.addItem(currentline)
println(currentline)
}
Or you could simply use a Generator:
let stdinByLine = GeneratorOf({ () -> String? in
var input = UnsafeMutablePointer<Int8>(), lim = 0
return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil
})
Let's try it out
for line in stdinByLine {
println(">>> \(line)")
}
It's simple, lazy, and easy to chain with other swift things like enumerators and functors such as map, reduce, filter; using the lazy() wrapper.
It generalises to all FILE as:
let byLine = { (file:UnsafeMutablePointer<FILE>) in
GeneratorOf({ () -> String? in
var input = UnsafeMutablePointer<Int8>(), lim = 0
return getline(&input, &lim, file) > 0 ? String.fromCString(input) : nil
})
}
called like
for line in byLine(stdin) { ... }
Following up on #dankogai's answer, I made a few modifications for Swift 4+,
let bufsize = 4096
let fp = fopen(jsonURL.path, "r");
var buf = UnsafeMutablePointer<Int8>.allocate(capacity: bufsize)
while (fgets(buf, Int32(bufsize-1), fp) != nil) {
print( String(cString: buf) )
}
buf.deallocate()
This worked for me.
Thanks
Swift 5.5: use url.lines
ADC Docs are here
Example usage:
guard let url = URL(string: "https://www.example.com") else {
return
}
// Manipulating an `Array` in memory seems to be a requirement.
// This will balloon in size as lines of data get added.
var myHugeArray = [String]()
do {
// This should keep the inbound data memory usage low
for try await line in url.lines {
myHugeArray.append(line)
}
} catch {
debugPrint(error)
}
You can use this in a SwiftUI .task { } modifier or wrap this in a Task return type to get its work off the main thread.
It turns out good old-fasioned C API is pretty comfortable in Swift once you grok UnsafePointer. Here is a simple cat that reads from stdin and prints to stdout line-by-line. You don't even need Foundation. Darwin suffices:
import Darwin
let bufsize = 4096
// let stdin = fdopen(STDIN_FILENO, "r") it is now predefined in Darwin
var buf = UnsafePointer<Int8>.alloc(bufsize)
while fgets(buf, Int32(bufsize-1), stdin) {
print(String.fromCString(CString(buf)))
}
buf.destroy()
(Note: I'm using Swift 3.0.1 on Xcode 8.2.1 with macOS Sierra 10.12.3)
All of the answers I've seen here missed that he could be looking for LF or CRLF. If everything goes well, s/he could just match on LF and check the returned string for an extra CR at the end. But the general query involves multiple search strings. In other words, the delimiter needs to be a Set<String>, where the set is neither empty nor contains the empty string, instead of a single string.
On my first try at this last year, I tried to do the "right thing" and search for a general set of strings. It was too hard; you need a full blown parser and state machines and such. I gave up on it and the project it was part of.
Now I'm doing the project again, and facing the same challenge again. Now I'm going to hard-code searching on CR and LF. I don't think anyone would need to search on two semi-independent and semi-dependent characters like this outside of CR/LF parsing.
I'm using the search methods provided by Data, so I'm not doing string encodings and stuff here. Just raw binary processing. Just assume I got an ASCII superset, like ISO Latin-1 or UTF-8, here. You can handle string encoding at the next-higher layer, and you punt on whether a CR/LF with secondary code-points attached still counts as a CR or LF.
The algorithm: just keep searching for the next CR and the next LF from your current byte offset.
If neither is found, then consider the next data string to be from the current offset to the end-of-data. Note that the terminator length is 0. Mark this as the end of your reading loop.
If a LF is found first, or only a LF is found, consider the next data string to be from the current offset to the LF. Note that the terminator length is 1. Move the offset to after the LF.
If only a CR is found, do like the LF case (just with a different byte value).
Otherwise, we got a CR followed by a LF.
If the two are adjacent, then handle like the LF case, except the terminator length will be 2.
If there is one byte between them, and said byte is also CR, then we got the "Windows developer wrote a binary \r\n while in text mode, giving a \r\r\n" problem. Also handle it like the LF case, except the terminator length will be 3.
Otherwise the CR and LF aren't connected, and handle like the just-CR case.
Here's some code for that:
struct DataInternetLineIterator: IteratorProtocol {
/// Descriptor of the location of a line
typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)
/// Carriage return.
static let cr: UInt8 = 13
/// Carriage return as data.
static let crData = Data(repeating: cr, count: 1)
/// Line feed.
static let lf: UInt8 = 10
/// Line feed as data.
static let lfData = Data(repeating: lf, count: 1)
/// The data to traverse.
let data: Data
/// The byte offset to search from for the next line.
private var lineStartOffset: Int = 0
/// Initialize with the data to read over.
init(data: Data) {
self.data = data
}
mutating func next() -> LineLocation? {
guard self.data.count - self.lineStartOffset > 0 else { return nil }
let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
let lineEndOffset: Int
switch (nextCR, nextLF) {
case (nil, nil):
lineEndOffset = self.data.count
case (nil, let offsetLf):
lineEndOffset = offsetLf!
location.terminatorLength = 1
case (let offsetCr, nil):
lineEndOffset = offsetCr!
location.terminatorLength = 1
default:
lineEndOffset = min(nextLF!, nextCR!)
if nextLF! < nextCR! {
location.terminatorLength = 1
} else {
switch nextLF! - nextCR! {
case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
location.terminatorLength += 1 // CR-CRLF
fallthrough
case 1:
location.terminatorLength += 1 // CRLF
fallthrough
default:
location.terminatorLength += 1 // CR-only
}
}
}
self.lineStartOffset = lineEndOffset + location.terminatorLength
location.length += self.lineStartOffset
return location
}
}
Of course, if you have a Data block of a length that's at least a significant fraction of a gigabyte, you'll take a hit whenever no more CR or LF exist from the current byte offset; always fruitlessly searching until the end during every iteration. Reading the data in chunks would help:
struct DataBlockIterator: IteratorProtocol {
/// The data to traverse.
let data: Data
/// The offset into the data to read the next block from.
private(set) var blockOffset = 0
/// The number of bytes remaining. Kept so the last block is the right size if it's short.
private(set) var bytesRemaining: Int
/// The size of each block (except possibly the last).
let blockSize: Int
/// Initialize with the data to read over and the chunk size.
init(data: Data, blockSize: Int) {
precondition(blockSize > 0)
self.data = data
self.bytesRemaining = data.count
self.blockSize = blockSize
}
mutating func next() -> Data? {
guard bytesRemaining > 0 else { return nil }
defer { blockOffset += blockSize ; bytesRemaining -= blockSize }
return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
}
}
You have to mix these ideas together yourself, since I haven't done it yet. Consider:
Of course, you have to consider lines completely contained in a chunk.
But you have to handle when the ends of a line are in adjacent chunks.
Or when the endpoints have at least one chunk between them
The big complication is when the line ends with a multi-byte sequence, but said sequence straddles two chunks! (A line ending in just CR that's also the last byte in the chunk is an equivalent case, since you need to read the next chunk to see if your just-CR is actually a CRLF or CR-CRLF. There are similar shenanigans when the chunk ends with CR-CR.)
And you need to handle when there are no more terminators from your current offset, but the end-of-data is in a later chunk.
Good luck!
I wanted a version that did not continually modify the buffer or duplicate code, as both are inefficient, and would allow for any size buffer (including 1 byte) and any delimiter. It has one public method: readline(). Calling this method will return the String value of the next line or nil at EOF.
import Foundation
// LineStream(): path: String, [buffSize: Int], [delim: String] -> nil | String
// ============= --------------------------------------------------------------
// path: the path to a text file to be parsed
// buffSize: an optional buffer size, (1...); default is 4096
// delim: an optional delimiter String; default is "\n"
// ***************************************************************************
class LineStream {
let path: String
let handle: NSFileHandle!
let delim: NSData!
let encoding: NSStringEncoding
var buffer = NSData()
var buffSize: Int
var buffIndex = 0
var buffEndIndex = 0
init?(path: String,
buffSize: Int = 4096,
delim: String = "\n",
encoding: NSStringEncoding = NSUTF8StringEncoding)
{
self.handle = NSFileHandle(forReadingAtPath: path)
self.path = path
self.buffSize = buffSize < 1 ? 1 : buffSize
self.encoding = encoding
self.delim = delim.dataUsingEncoding(encoding)
if handle == nil || self.delim == nil {
print("ERROR initializing LineStream") /* TODO use STDERR */
return nil
}
}
// PRIVATE
// fillBuffer(): _ -> Int [0...buffSize]
// ============= -------- ..............
// Fill the buffer with new data; return with the buffer size, or zero
// upon reaching end-of-file
// *********************************************************************
private func fillBuffer() -> Int {
buffer = handle.readDataOfLength(buffSize)
buffIndex = 0
buffEndIndex = buffer.length
return buffEndIndex
}
// PRIVATE
// delimLocation(): _ -> Int? nil | [1...buffSize]
// ================ --------- ....................
// Search the remaining buffer for a delimiter; return with the location
// of a delimiter in the buffer, or nil if one is not found.
// ***********************************************************************
private func delimLocation() -> Int? {
let searchRange = NSMakeRange(buffIndex, buffEndIndex - buffIndex)
let rangeToDelim = buffer.rangeOfData(delim,
options: [], range: searchRange)
return rangeToDelim.location == NSNotFound
? nil
: rangeToDelim.location
}
// PRIVATE
// dataStrValue(): NSData -> String ("" | String)
// =============== ---------------- .............
// Attempt to convert data into a String value using the supplied encoding;
// return the String value or empty string if the conversion fails.
// ***********************************************************************
private func dataStrValue(data: NSData) -> String? {
if let strVal = NSString(data: data, encoding: encoding) as? String {
return strVal
} else { return "" }
}
// PUBLIC
// readLine(): _ -> String? nil | String
// =========== ____________ ............
// Read the next line of the file, i.e., up to the next delimiter or end-of-
// file, whichever occurs first; return the String value of the data found,
// or nil upon reaching end-of-file.
// *************************************************************************
func readLine() -> String? {
guard let line = NSMutableData(capacity: buffSize) else {
print("ERROR setting line")
exit(EXIT_FAILURE)
}
// Loop until a delimiter is found, or end-of-file is reached
var delimFound = false
while !delimFound {
// buffIndex will equal buffEndIndex in three situations, resulting
// in a (re)filling of the buffer:
// 1. Upon the initial call;
// 2. If a search for a delimiter has failed
// 3. If a delimiter is found at the end of the buffer
if buffIndex == buffEndIndex {
if fillBuffer() == 0 {
return nil
}
}
var lengthToDelim: Int
let startIndex = buffIndex
// Find a length of data to place into the line buffer to be
// returned; reset buffIndex
if let delim = delimLocation() {
// SOME VALUE when a delimiter is found; append that amount of
// data onto the line buffer,and then return the line buffer
delimFound = true
lengthToDelim = delim - buffIndex
buffIndex = delim + 1 // will trigger a refill if at the end
// of the buffer on the next call, but
// first the line will be returned
} else {
// NIL if no delimiter left in the buffer; append the rest of
// the buffer onto the line buffer, refill the buffer, and
// continue looking
lengthToDelim = buffEndIndex - buffIndex
buffIndex = buffEndIndex // will trigger a refill of buffer
// on the next loop
}
line.appendData(buffer.subdataWithRange(
NSMakeRange(startIndex, lengthToDelim)))
}
return dataStrValue(line)
}
}
It is called as follows:
guard let myStream = LineStream(path: "/path/to/file.txt")
else { exit(EXIT_FAILURE) }
while let s = myStream.readLine() {
print(s)
}

Resources