I have the following function to pull images from a Firebase Storage database.
For some reason, the print(imageRef) line is working well but the imageRef.getData() code block is being skipped completely. It's not executing either of the print statements "error updating..." or "Got the image".
What could be causing this?
func updateCurrentUser() {
var downloadedImages : [UIImage?] = []
for i in 0...8 {
let storageRef = Storage.storage().reference()
let imageRef = storageRef.child(self.currentUser.userid + "/img" + String(i) + ".jpg")
print(imageRef)
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
imageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print("error updating returning user: \(error.localizedDescription)")
} else {
// Data for the image is returned
let image = UIImage(data: data!)
downloadedImages[i] = image
print("Got the image")
}
}
}
self.currentUser.images = downloadedImages
}
Firebase is asynchronous and firebase data is only valid within the closure following the firebase function. In a nutshell, code is faster than the internet and it takes time for data to download.
Here's an abbreviated version of your code... note the comments
func updateCurrentUser() {
var downloadedImages : [UIImage?] = []
for i in 0...8 {
imageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
//firebase data is ONLY valid here so append each image to an array
self.currentUser.images.append(downloadedImage)
}
}
//the following line will execute WAY before the images are downloaded
// so self.currentUser.images will always be empty
self.currentUser.images = downloadedImages
}
You're probably going to want to use a completion handler as well. See my answer this question for further reading
I'm trying to import a large data set of around 80k objects. I'm trying to follow Apple example
I have two issues:
In the code example there is a comment:
// taskContext.performAndWait runs on the URLSession's delegate queue
// so it won’t block the main thread.
But in my case I'm not using URLSession to fetch the JSON. The file is bundled with the app. In this case how to make sure the import won’t block the main thread. Should I create a custom queue ? Any example ?
In the example it's just importing an array of entities. But in my case I need to import just one entity that has 70k object in a relation to many.
So what I want to achieve is:
If there is a ContactBook don't import anything because we have already imported the JSON.
If there is no ContactBook create one and import all the 70k Contact object to the contacts relation of the ContactBook. This should happen in batches like in the example and should not block the UI.
What I have tried:
private func insertContactbookIfNeeded() {
let fetch: NSFetchRequest<Contactbook> = ContactBook.fetchRequest()
let contactBookCount = (try? context.count(for: fetch)) ?? 0
if contactBookCount > 0 {
return
}
let contacts = Bundle.main.decode([ContactJSON].self, from: "contacts.json")
// Process records in batches to avoid a high memory footprint.
let batchSize = 256
let count = contacts.count
// Determine the total number of batches.
var numBatches = count / batchSize
numBatches += count % batchSize > 0 ? 1 : 0
for batchNumber in 0 ..< numBatches {
// Determine the range for this batch.
let batchStart = batchNumber * batchSize
let batchEnd = batchStart + min(batchSize, count - batchNumber * batchSize)
let range = batchStart..<batchEnd
// Create a batch for this range from the decoded JSON.
let contactsBatch = Array(contacts[range])
// Stop the entire import if any batch is unsuccessful.
if !importOneBatch(contactsBatch) {
assertionFailure("Could not import batch number \(batchNumber) range \(range)")
return
}
}
}
private func importOneBatch(_ contactsBatch: [ContactJSON]) -> Bool {
var success = false
// Create a private queue context.
let taskContext = container.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// NOT TRUE IN MY CASE: (Any suggestion ??)
// taskContext.performAndWait runs on the URLSession's delegate queue
// so it won’t block the main thread.
print("isMainThread: \(Thread.isMainThread)") // prints true
taskContext.performAndWait {
let fetchRequest: NSFetchRequest<ContactBook> = ContactBook.fetchRequest()
fetchRequest.returnsObjectsAsFaults = true
fetchRequest.includesSubentities = false
let contactBookCount = (try? taskContext.count(for: fetchRequest)) ?? 0
var contactBook: ContactBook?
if contactBookCount > 0 {
do {
contactBook = try taskContext.fetch(fetchRequest).first
} catch let error as NSError {
assertionFailure("can't fetch the contactBook \(error)")
}
} else {
contactBook = ContactBook(context: taskContext)
}
guard let book = contactBook else {
assertionFailure("Could not fetch the contactBook")
return
}
// Create a new record for each contact in the batch.
for contactJSON in contactsBatch {
// Create a Contact managed object on the private queue context.
let contact = Contact(context: taskContext)
// Populate the Contact's properties using the raw data.
contact.name = contactJSON.name
contact.subContacts = NSSet(array: contactJSON.subContacts { subC -> Contact in
let contact = Contact(context: taskContext)
contact.name = subC.name
})
book.addToContacts(contact)
}
// Save all insertions and deletions from the context to the store.
if taskContext.hasChanges {
do {
try taskContext.save()
} catch {
print("Error: \(error)\nCould not save Core Data context.")
return
}
// Reset the taskContext to free the cache and lower the memory footprint.
taskContext.reset()
}
success = true
}
return success
}
The problem is that this is very slow because in each batch I fetch the workbook (which is getting bigger in each iteration) to be able to insert new batch of contacts in the contact book. Is there a efficient way to avoid fetching the workbook in each batch ? also any suggestion to make this is faster ? increase the batch size ? create a background queue ?
Update:
I have tried to create a ContactBook once in insertWordbookIfNeeded and pass it to importOneBatch with each iteration but I get:
Thread 1: Exception: "Illegal attempt to establish a relationship
'contactBook' between objects in different contexts
I am trying to process a large text file in my app. I know that I want to be careful with the amount of memory being consumed while I read the data. Once a piece of data is read the app does not need to keep the data around.
Thanks to “Martin R” and the post Read a file/URL line-by-line for helping me jump start my effort.
I am trying to monitor the memory consumption of my app as it reads in the large data file so that I can be sure it is behaving as expected. Here’s where I am running into a problem.
When I run Instruments using Command-I from within Xcode and I monitor allocations I see that during the read of the file the app peeks at ~15MB and then drops back down. This is fairly repeatable +/- 0.5MB.
When I run the app using Command-R from within Xcode and then let it finish reading through the file, and then press record within Instruments, the memory consumption now swells to ~360MB.
So to clarify, the two ways I have done measurement of memory allocations are:
Profile:
1. Xcode Command-I.
2. Instruments Record Allocations. Observe ~15MB
Simulate and Profile:
1. Xcode Command-R.
2. Let app run to “IDLE”.
3. Instruments Record. Observe ~360MB.
I have been trying to figure out a few things here.
Q1. Why the difference? (This may answer all my questions)
Q2. Do I have a real problem or is this only a problem because of how debug code is annotated on to the simulator?
Q3. Similar to Q2, if I run a debug build on a real device, will it have the same issue?
Q4. For my app, ~15MB is acceptable when parsing the file, but ~360MB will not be. Is there another way I can continue to debug on my device without taking this 360MB hit?
Version 8.1 (8B62)
Sierra
2.7Ghz i5
MacBook Pro Circa 2015
Sample Code attached. The first part of the file is merely a copy of the code from the referenced post for reader convenience. One can take this code as is and run it in Xcode. At the bottom is the ViewController ViewDidLoad() method where things "run". The memory “swell” is after “File opened”.
//
//
import UIKit
/* Originally from
* stackoverflow:
* https://stackoverflow.com/questions/24581517/read-a-file-url-line-by-line-in-swift
* posted by Martin R.
* Much thanks!
*/
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()
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let path2WordList = Bundle.main.path(forResource: "large_text_file", ofType: "txt")
var wordCnt: Int = 0
if nil != path2WordList {
if let aStreamReader = StreamReader(path: path2WordList!) {
defer { aStreamReader.close() }
print("File openned")
/* Read and discard */
while aStreamReader.nextLine() != nil {
wordCnt += 1
}
} // if let ...
} // if nil ...
print ("Final wordCnt := \(wordCnt)")
} // viewDidLoad
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've encountered problems like this when using long running while loops. The problem is that anything that is allocated into the current autorelease pool won't get deallocated until the loop exits.
To guard against this, you can wrap the contents of your while loop in autoreleasepool(invoking:). This will cause each iteration of your loop to have its own autorelease pool that is drained each time.
It would look something like this:
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
var result: String? = nil
// Read data chunks from file until a line delimiter is found:
while !atEof, result == nil {
result = autoreleasepool {
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
}
}
return result
}
As to whether your memory growth is a side effect of the debug environment, it's hard to say. But it would probably be wise to guard against this kind of growth regardless.
I'm still getting started with ReactiveCocoa and functional reactive programming concepts, so maybe this is a dumb question.
ReactiveCocoa seem naturally designed to react to streams of live data, touch events or accelerometer sensor input etc.
Is it possible to apply finite impulse response filters in ReactiveCocoa in an easy, reactive fashion? Or if not, what would be the least-ugly hacky way of doing this? How would one go about implementing something like a simple moving average?
Ideally looking for an Swift 2 + RA4 solution but also interested in if this is possible at all in Objective C and RA2/RA3.
What you actually need is a some sort of period buffer, which will keep a period of values buffered and only start sending out when the buffer has reached capacity (the code below is heavenly inspired on takeLast operator)
extension SignalType {
func periodBuffer(period:Int) -> Signal<[Value], Error> {
return Signal { observer in
var buffer: [Value] = []
buffer.reserveCapacity(period)
return self.observe { event in
switch event {
case let .Next(value):
// To avoid exceeding the reserved capacity of the buffer, we remove then add.
// Remove elements until we have room to add one more.
while (buffer.count + 1) > period {
buffer.removeAtIndex(0)
}
buffer.append(value)
if buffer.count == period {
observer.sendNext(buffer)
}
case let .Failed(error):
observer.sendFailed(error)
case .Completed:
observer.sendCompleted()
case .Interrupted:
observer.sendInterrupted()
}
}
}
}
}
based on that you can map it to any algorithm you want
let pipe = Signal<Int,NoError>.pipe()
pipe.0
.periodBuffer(3)
.map { Double($0.reduce(0, combine: +))/Double($0.count) } // simple moving average
.observeNext { print($0) }
pipe.1.sendNext(10) // does nothing
pipe.1.sendNext(11) // does nothing
pipe.1.sendNext(15) // prints 12
pipe.1.sendNext(7) // prints 11
pipe.1.sendNext(9) // prints 10.3333
pipe.1.sendNext(6) // prints 7.3333
Probably the scan signal operator is what you're looking for. Inspired by Andy Jacobs' answer, I came up with something like this (a simple moving average implementation):
let (signal, observer) = Signal<Int,NoError>.pipe()
let maxSamples = 3
let movingAverage = signal.scan( [Int]() ) { (previousSamples, nextValue) in
let samples : [Int] = previousSamples.count < maxSamples ? previousSamples : Array(previousSamples.dropFirst())
return samples + [nextValue]
}
.filter { $0.count >= maxSamples }
.map { $0.average }
movingAverage.observeNext { (next) -> () in
print("Next: \(next)")
}
observer.sendNext(1)
observer.sendNext(2)
observer.sendNext(3)
observer.sendNext(4)
observer.sendNext(42)
Note: I had to move average method into a protocol extension, otherwise the compiler would complain that the expression was too complex. I used a nice solution from this answer:
extension Array where Element: IntegerType {
var total: Element {
guard !isEmpty else { return 0 }
return reduce(0){$0 + $1}
}
var average: Double {
guard let total = total as? Int where !isEmpty else { return 0 }
return Double(total)/Double(count)
}
}
I just started learning Swift. I have got my code to read from the text file, and the App displays the content of the entire text file. How can I display line by line and call upon that line multiple times?
TextFile.txt contains the following:
1. Banana
2. Apple
3. pear
4. strawberry
5. blueberry
6. blackcurrant
The following is what currently have..
if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){
var data = String(contentsOfFile:path, encoding: NSUTF8StringEncoding, error: nil)
if let content = (data){
TextView.text = content
}
If there is another way of doing this please let me know. It would be much appreciated.
Swift 3.0
if let path = Bundle.main.path(forResource: "TextFile", ofType: "txt") {
do {
let data = try String(contentsOfFile: path, encoding: .utf8)
let myStrings = data.components(separatedBy: .newlines)
TextView.text = myStrings.joined(separator: ", ")
} catch {
print(error)
}
}
The variable myStrings should be each line of the data.
The code used is from:
Reading file line by line in iOS SDK written in Obj-C and using NSString
Check edit history for previous versions of Swift.
Swift 5.5
The solution below shows how to read one line at a time. This is quite different from reading the entire contents into memory. Reading line-by-line scales well if you have a large file to read. Putting an entire file into memory does not scale well for large files.
The example below uses a while loop that quits when there are no more lines, but you can choose a different number of lines to read if you wish.
The code works as follows:
create a URL that tells where the file is located
make sure the file exists
open the file for reading
set up some initial variables for reading
read each line using getLine()
close the file and free the buffer when done
You could make the code less verbose if you wish; I have included comments to explain what the variables' purposes are.
Swift 5.5
import Cocoa
// get URL to the the documents directory in the sandbox
let home = FileManager.default.homeDirectoryForCurrentUser
// add a filename
let fileUrl = home
.appendingPathComponent("Documents")
.appendingPathComponent("my_file")
.appendingPathExtension("txt")
// make sure the file exists
guard FileManager.default.fileExists(atPath: fileUrl.path) else {
preconditionFailure("file expected at \(fileUrl.absoluteString) is missing")
}
// open the file for reading
// note: user should be prompted the first time to allow reading from this location
guard let filePointer:UnsafeMutablePointer<FILE> = fopen(fileUrl.path,"r") else {
preconditionFailure("Could not open file at \(fileUrl.absoluteString)")
}
// a pointer to a null-terminated, UTF-8 encoded sequence of bytes
var lineByteArrayPointer: UnsafeMutablePointer<CChar>? = nil
// see the official Swift documentation for more information on the `defer` statement
// https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_defer-statement
defer {
// remember to close the file when done
fclose(filePointer)
// The buffer should be freed by even if getline() failed.
lineByteArrayPointer?.deallocate()
}
// the smallest multiple of 16 that will fit the byte array for this line
var lineCap: Int = 0
// initial iteration
var bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)
while (bytesRead > 0) {
// note: this translates the sequence of bytes to a string using UTF-8 interpretation
let lineAsString = String.init(cString:lineByteArrayPointer!)
// do whatever you need to do with this single line of text
// for debugging, can print it
print(lineAsString)
// updates number of bytes read, for the next iteration
bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)
}
If you have a huge file and don't want to load all data to memory with String, Data etc. you can use function readLine() which reads content from standard input line by line until EOF is reached.
let path = "path/file.txt"
guard let file = freopen(path, "r", stdin) else {
return
}
defer {
fclose(file)
}
while let line = readLine() {
print(line)
}
This is not pretty, but I believe it works (on Swift 5). This uses the underlying POSIX getline command for iteration and file reading.
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(fileURL.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
}
})
}
Here is how you might use it:
for line in lines(ofFile:myFileURL) {
print(line)
}
Probably the simplest, and easiest way to do this in Swift 5.0, would be the following:
import Foundation
// Determine the file name
let filename = "main.swift"
// Read the contents of the specified file
let contents = try! String(contentsOfFile: filename)
// Split the file into separate lines
let lines = contents.split(separator:"\n")
// Iterate over each line and print the line
for line in lines {
print("\(line)")
}
Note: This reads the entire file into memory, and then just iterates over the file in memory to produce lines....
Credit goes to: https://wiki.codermerlin.com/mediawiki/index.php/Code_Snippet:_Print_a_File_Line-by-Line
Update for Swift 2.0 / Xcode 7.2
do {
if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){
let data = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
let myStrings = data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
print(myStrings)
}
} catch let err as NSError {
//do sth with Error
print(err)
}
Also worth to mention is that this code reads a file which is in the project folder (since pathForResource is used), and not in e.g. the documents folder of the device
You probably do want to read the entire file in at once. I bet it's very small.
But then you want to split the resulting string into an array, and then distribute the array's contents among various UI elements, such as table cells.
A simple example:
var x: String = "abc\ndef"
var y = x.componentsSeparatedByString("\n")
// y is now a [String]: ["abc", "def"]
One more getline solution:
Easy to use. Just copy past.
Tested on real project.
extension URL
{
func foreachRow(_ mode:String, _ rowParcer:((String, Int)->Bool) )
{
//Here we should use path not the absoluteString (wich contains file://)
let path = self.path
guard let cfilePath = (path as NSString).utf8String,
let m = (mode as NSString).utf8String
else {return}
//Open file with specific mode (just use "r")
guard let file = fopen(cfilePath, m)
else {
print("fopen can't open file: \"\(path)\", mode: \"\(mode)\"")
return
}
//Row capacity for getline()
var cap = 0
var row_index = 0
//Row container for getline()
var cline:UnsafeMutablePointer<CChar>? = nil
//Free memory and close file at the end
defer{free(cline); fclose(file)}
while getline(&cline, &cap, file) > 0
{
if let crow = cline,
// the output line may contain '\n' that's why we filtered it
let s = String(utf8String: crow)?.filter({($0.asciiValue ?? 0) >= 32})
{
if rowParcer(s, row_index)
{
break
}
}
row_index += 1
}
}
}
Usage:
let token = "mtllib "
var mtlRow = ""
largeObjFileURL.foreachRow("r"){ (row, i) -> Bool in
if row.hasPrefix(token)
{
mtlRow = row
return true // end of file reading
}
return false // continue file reading
}
Here is an example of writeing and reading a text file one line at a time
in Swift version 5. Reads one line in at a time and includes EOF detection
//
// main.swift
// IO
//
// Created by Michael LeVine on 8/30/22.
//
import Foundation
let file = "file.txt" //this is the file. we will write to and read from it
let text = "some text\n" //just a text
// test file will be placed on deasktop
let dir = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first
let fileURL = dir!.appendingPathComponent(file).path
let fileURL2 = dir!.appendingPathComponent(file)
let fileManager = FileManager.default
// the following variable used by eof detection which also use var fileManager internally
var eofOffset: UInt64 = 0
if fileManager.fileExists(atPath: fileURL) {
do { try fileManager.removeItem(atPath: fileURL)}
catch {
print("Error removeing old \(fileURL)")
exit(1)
}
}
// create the new file
fileManager.createFile(atPath: fileURL, contents:Data(" ".utf8), attributes: nil)
var fileHandle = FileHandle(forWritingAtPath: fileURL)
//writing
for _ in 1...10 {
fileHandle!.write(text.data(using: .utf8)!)
}
do {
try fileHandle!.close()
}
catch { print("write close error \(error)")
exit(1)
}
// now to read text file by 2 methods
// first use String to read whole file in one gulp
let contents = try! String(contentsOfFile: fileURL)
let lines = contents.split(separator: "\n")
var i: Int = 0
// print out one way
for line in lines {
print("\(i) \(line)")
i=i+1
}
// printout another way
for j in 0...9 {
print("\(i) \(j) \(lines[j])")
i = i + 1
}
//Open up to see about reading line at a time
fileHandle = FileHandle(forReadingAtPath: fileURL)
eofInit() // must be called immediately after fileHandle init
var outputLine: String = ""
i = 0
// read a line and print it out as recieved
while true {
outputLine = getLine()
if eofTest(){
if outputLine.count > 0 {
print("\(i) \(outputLine)")
}
exit(1)
}
print("\(i) \(outputLine)")
i = i + 1
}
// function reads one character at each call and returns it as a 1 character string
// is called only by "getLine"
func getChar() -> String {
var ch: Data
if eofTest() {
return ""
}
do {
try ch = fileHandle!.read(upToCount: 1)! // read 1 character from text file
} catch { print("read 1 char \(error)")
exit(1)
}
let ch2: UnicodeScalar = UnicodeScalar(ch[0]) // convert to unicode scaler as intermediate value
let ch3: String = String(ch2) // Now create string containing that one returned character
return ch3 // and pass to calling function
}
// read in whole line one character at a time -- assumes line terminated by linefeed
func getLine() -> String {
var outputLine : String = ""
var char : String = ""
// keep fetching characters till line feed/eof found
lineLoop:
while true { // its an infinite loop
if eofTest() {
break lineLoop
}
char = getChar() // get next character
if char == "\n" { // test for linefeed
break lineLoop // if found exit loop
}
outputLine.append(char) // lf not found -- append char to output line
}
return outputLine // got line -- return it to calling routine
}
//eof handleing
//init routine must be called immediately after fileHandle inited to get current position
// at start of file
func eofInit()
{ var beginningOffset: UInt64 = 0
do {
try beginningOffset = fileHandle!.offset()
try eofOffset = fileHandle!.seekToEnd()
try fileHandle!.seek(toOffset: beginningOffset)
} catch {
print("Init eof detection error \(error)")
}
}
func eofTest() -> Bool{
var current: UInt64 = 0
do {
current = try fileHandle!.offset()
} catch {
print("eof test get current \(error)")
exit(1)
}
if current < eofOffset {
return false
} else {
return true
}
}
Based on Jason Cross answer simplified version line by line reader(gist).
import Darwin
class FileLineReader {
init?(path: String, removeNewLineOnEnd: Bool = true) {
file = fopen(path, "r")
self.removeNewLineOnEnd = removeNewLineOnEnd
if file == nil {
return nil
}
}
deinit {
fclose(file)
}
var iterator: AnyIterator<String> {
return AnyIterator(self.getNextLine)
}
func getNextLine() -> String? {
var line: UnsafeMutablePointer<CChar>!
var linecap: Int = 0
defer { free(line) }
if getline(&line, &linecap, file) > 0 {
if removeNewLineOnEnd {
var i = 0
while line[i] != 0 { i += 1 }
if i > 0 && line[i-1] == 10 { // new line symbol
line[i-1] = 0
}
}
return String(cString: line)
} else {
return nil
}
}
private let file: UnsafeMutablePointer<FILE>!
private let removeNewLineOnEnd: Bool
}
iUrii approach may not work if you need to open several files.