AVAudioConverter Compressed/PCM conversion throwing OSStatus -50 (kAudio_ParamError) - ios

I'm trying to use AVAudioConverter to convert compressed audio data (in this case mp3, 96kbps, CBR, stereo) into PCM data (the standard output format, obtained by calling audioEngine.outputNode.outputFormatForBus(0)). I use an AudioFileStream to get the packets. I have previously used an AudioQueue to play these packets, so they are valid.
This is where I call convertToBuffer:
let outputBuffer = AVAudioPCMBuffer(PCMFormat: outputFormat, frameCapacity: outputBufferFrameLength)
outputBuffer.frameLength = outputBufferFrameLength
var error:NSError?
audioConverter.convertToBuffer(outputBuffer, error: &error, withInputFromBlock: { (packetCount:AVAudioPacketCount, inputStatus:UnsafeMutablePointer<AVAudioConverterInputStatus>) -> AVAudioBuffer? in
return self.getAudioConverterInput(packetCount, inputStatus: inputStatus)
})
if let error = error{
print("conv error:", error)
}else{
// TODO: Do stuff with buffer
}
And this is the function that handles the input.
func getAudioConverterInput(packetCount:AVAudioPacketCount, inputStatus:UnsafeMutablePointer<AVAudioConverterInputStatus>) -> AVAudioBuffer?{
if let currentAudioFormat = currentAudioFormat, firstPackets = packets.first{
inputStatus.memory = .HaveData
let buffer = AVAudioCompressedBuffer(format: currentAudioFormat, packetCapacity: packetCount, maximumPacketSize: Int(currentMaximumPacketSize))
var currentPackets:Packets = firstPackets
var currentStartOffset:Int64 = 0
for i in 0..<packetCount{
let currentDescription = currentPackets.packetDescriptions[currentPacket]
memcpy(buffer.data.advancedBy(Int(currentStartOffset)), currentPackets.data.advancedBy(Int(currentDescription.mStartOffset)), Int(currentDescription.mDataByteSize))
buffer.packetDescriptions[Int(i)] = AudioStreamPacketDescription(mStartOffset: currentStartOffset, mVariableFramesInPacket: currentDescription.mVariableFramesInPacket, mDataByteSize: currentDescription.mDataByteSize)
currentStartOffset += Int64(currentDescription.mDataByteSize)
currentPacket+=1
if (currentPackets.numberOfPackets == UInt32(currentPacket)){
currentPacket = 0
packets.removeFirst()
if let firstPackets = packets.first{
currentPackets = firstPackets
}else{
buffer.packetCount = i + 1
return buffer
}
}
}
buffer.packetCount = packetCount
return buffer
}
inputStatus.memory = .NoDataNow
return nil
}
This function is called once every time I call the convertToBuffer(...) function with the packetCount variable set to '1'. The error "conv error: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"" is thrown.
What am I doing wrong here?

Related

CMVideoFormatDescriptionCreateFromH264ParameterSets throw Initialization of 'UnsafePointer<Int>' results in a dangling pointer

So previously I opened Initialization of 'UnsafePointer<Int>' results in a dangling pointer but the mods said it's a duplicate one.
However I don't think this is a duplicate one, as the links being referred in that post cannot easily direct to a solution. Thus I have to open a new one after 3 days. I tried what is answered from #bscothern in the previous post, but more errors come.
The code being used currently is like below from #bscothern, and it still throws
Generic parameter 'R' could not be inferred
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let status = SPS.withUnsafeBufferPointer { SPS in
PPS.withUnsafeBufferPointer { PPS in
let paramSet = [SPS.baseAddress!, PPS.baseAddress!]
let paramSizes = [SPS.count, PPS.count]
return paramSet.withUnsafeBufferPointer { paramSet in
paramSizes.withUnsafeBufferPointer { paramSizes in
CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSet.baseAddress!, parameterSetSizes: paramSizes.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
}
}
}
}
return status
}
Original question from previous post:
So I have some code to create H264ParameterSets like:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let paramSet = [UnsafePointer<UInt8>(SPS), UnsafePointer<UInt8>(PPS)]
let paramPointers = UnsafePointer<UnsafePointer<UInt8>>(paramSet)
let paramSizes = UnsafePointer<Int>([SPS.count, PPS.count])
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramPointers, parameterSetSizes: paramSizes, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
return status
}
Starting on Xcode 11.4 I got warnings for those UnsafePointer(), which seems not happen before:
Initialization of UnsafePointer<UInt8> results in a dangling pointer
Initialization of UnsafePointer<UnsafePointer<UInt8>> results in a dangling pointer
Initialization of UnsafePointer<Int> results in a dangling pointer
I'm not sure why we see this? and how can I remove the warning? Thank in advance.
When you find some error message like: Generic parameter 'R' could not be inferred, there may be two possible reasons.
There is some type-related error somewhere, so Swift cannot infer the actual type R in that context
The expression is a little more complex than Swift can infer the type
In case 1, you need to find where is the actual error causing the issue.
In case 2, you can add some explicit type annotations to help Swift infer the types.
Please try something like this:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS sps: Array<UInt8>, PPS pps: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let status = sps.withUnsafeBufferPointer { spsBP->OSStatus in //<- Specify return type explicitly.
pps.withUnsafeBufferPointer { ppsBP in
let paramSet = [spsBP.baseAddress!, ppsBP.baseAddress!]
let paramSizes = [spsBP.count, ppsBP.count]
return paramSet.withUnsafeBufferPointer { paramSetBP in
paramSizes.withUnsafeBufferPointer { paramSizesBP in
CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSetBP.baseAddress!, parameterSetSizes: paramSizesBP.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
}
}
}
}
return status
}

How to invoke device method in ios using azure iot sdk

I am trying to call a method associated with the device using connection string.
I tried with the samples provided with other languages I am able to call the method in the device. eg: "setState" or "getState" of the lamp.
But I am not able to implement in iOS using swift.
I tried to match parameter parameter requirement by referring to the C sample. But I am getting
1. Func:sendHttpRequestDeviceMethod Line:337 Http Failure status code 400.
2. Func:IoTHubDeviceMethod_Invoke Line:492 Failure sending HTTP request for device method invoke
var status :Int32! = 0
var deviceId = "simulated_device_one";
var methodName = "GetState";
var uint8Pointer:UnsafeMutablePointer<UInt8>!
uint8Pointer = UnsafeMutablePointer<UInt8>.allocate(capacity:8)
var size = size_t(10000)
var bytes: [UInt8] = [39, 77, 111, 111, 102, 33, 39, 0]
uint8Pointer?.initialize(from: &bytes, count: 8)
var intValue : UnsafeMutablePointer<UInt8>?
intValue = UnsafeMutablePointer(uint8Pointer)
var char: UInt8 = UInt8(20)
var charPointer = UnsafeMutablePointer<UInt8>(&char)
var prediction = intValue
let serviceClientDeviceMethodHandle = IoTHubDeviceMethod_Create(service_client_handle)
let payLoad = "test"
var responsePayload = ""
let invoke = IoTHubDeviceMethod_Invoke(serviceClientDeviceMethodHandle, deviceId, methodName, payLoad , 100, &status, &prediction,&size )
I want to call a method in the device using IoTHubDeviceMethod_Invoke
You can download View controller file which I worked on from here
1.Create Connection in view did load
// declaring your connection string you can find it in azure iot dashboard
private let connectionString = "Enter your connection String";
// creating service handler
private var service_client_handle: IOTHUB_SERVICE_CLIENT_AUTH_HANDLE!;
// handler for the method invoke
private var iot_device_method_handle:IOTHUB_SERVICE_CLIENT_DEVICE_METHOD_HANDLE!;
// In view did load establish the connection
service_client_handle = IoTHubServiceClientAuth_CreateFromConnectionString(connectionString)
if (service_client_handle == nil) {
showError(message: "Failed to create IoT Service handle", sendState: false)
}
create method invoke function
I created it based on the demo provided for sending message
func openIothubMethodInvoke() -> Bool
{
print("In openIotHub method invoke")
let result: Bool;
iot_device_method_handle = IoTHubDeviceMethod_Create(service_client_handle);
let testValue : Any? = iot_device_method_handle;
if (testValue == nil) {
showError(message: "Failed to create IoT devicemethod", sendState: false);
result = false;
}
else
{
result = true;
}
return result;
}
call method invoke
** this is the main function for calling the method
func methodInvoke()
{
let testValue : Any? = iot_device_method_handle;
if (testValue == nil && !openIothubMethodInvoke() ) {
print("Failued to open IoThub messaging");
}
else {
let size = UnsafeMutablePointer<Int>.allocate(capacity: 1)
let responseStatus = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
// Payload is the main change it is like specifying the format
var payload = UnsafeMutablePointer<UnsafeMutablePointer<UInt8>?>.allocate(capacity: 1)
// if payload is not expected please send empty json "{}"
let result = IoTHubDeviceMethod_Invoke(iot_device_method_handle, "nameOfTheDeviceYouWantToCallOn", "MethodName", "{payload you want to send}", 100, responseStatus, payload , size)
// extracting the data from response
let b = UnsafeMutableBufferPointer(start: payload.pointee, count: size.pointee)
let data = Data(buffer: b)
let str = String(bytes: data, encoding: .utf8)
print(str)
do{
let value = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(value)
}catch{
print(error)
}
}
}
As discussed above: the payload needs to be valid JSON. Even an empty json will do such as {}

WebRTC(iOS): local video is not getting stream on remote side

I am trying to make an app with Audio, video call using WebRTC.
remote video and audio are working properly in my app, but my local stream is not appearing on the client side.
here is what I have written to add a video track
let videoSource = self.rtcPeerFactory.videoSource()
let videoCapturer = RTCCameraVideoCapturer(delegate: videoSource)
guard let frontCamera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == .front }),
// choose highest res
let format = (RTCCameraVideoCapturer.supportedFormats(for: frontCamera).sorted { (f1, f2) -> Bool in
let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width
let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width
return width1 < width2
}).last,
// choose highest fps
let fps = (format.videoSupportedFrameRateRanges.sorted { return $0.maxFrameRate < $1.maxFrameRate }.last) else {
print(.error, "Error in createLocalVideoTrack")
return nil
}
videoCapturer.startCapture(with: frontCamera,
format: format,
fps: Int(fps.maxFrameRate))
self.callManagerDelegate?.didAddLocalVideoTrack(videoTrack: videoCapturer)
let videoTrack = self.rtcPeerFactory.videoTrack(with: videoSource, trackId: K.CONSTANT.VIDEO_TRACK_ID)
and this is to add Audio track
let constraints: RTCMediaConstraints = RTCMediaConstraints.init(mandatoryConstraints: [:], optionalConstraints: nil)
let audioSource: RTCAudioSource = self.rtcPeerFactory.audioSource(with: constraints)
let audioTrack: RTCAudioTrack = self.rtcPeerFactory.audioTrack(with: audioSource, trackId: K.CONSTANT.AUDIO_TRACK_ID)
my full webRTC log attached here.
some logs I am getting (I think this is something wrong)
(thread.cc:303): Waiting for the thread to join, but blocking calls have been disallowed
(basic_port_allocator.cc:1035): Port[31aba00:0:1:0:relay:Net[ipsec4:2405:204:8888:x:x:x:x:x/64:VPN/Unknown:id=2]]: Port encountered error while gathering candidates.
...
(basic_port_allocator.cc:1017): Port[38d7400:audio:1:0:local:Net[en0:192.168.1.x/24:Wifi:id=1]]: Port completed gathering candidates.
(basic_port_allocator.cc:1035): Port[3902c00:video:1:0:relay:Net[ipsec5:2405:204:8888:x:x:x:x:x/64:VPN/Unknown:id=3]]: Port encountered error while gathering candidates.
finally, find the solution
it was due to TCP protocol in the TURN server.

Read exactly n bytes from InputStream in Swift 4

I have a server that sends me messages over TCP where the first 4 bytes determine the length of the rest of the message. So I need to
1) read 4 bytes into an UInt32 (works) and store it into bytes_expected
2) read bytes_expected bytes into message
Right now my code looks like this:
private let inputStreamAccessQueue = DispatchQueue(label: "SynchronizedInputStreamAccess")
func inputStreamHandler(_ event: Stream.Event) {
switch event {
case Stream.Event.hasBytesAvailable:
self.handleInput()
...
}
}
func handleInput() {
// **QUESTION: Do I use this barrier wrong?**
self.inputStreamAccessQueue.sync(flags: .barrier) {
guard let istr = self.inputStream else {
log.error(self.buildLogMessage("InputStream is nil"))
return
}
guard istr.hasBytesAvailable else {
log.error(self.buildLogMessage("handleInput() called when inputstream has no bytes available"))
return
}
let lengthbuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 4)
defer { lengthbuffer.deallocate(capacity: 4) }
let lenbytes_read = istr.read(lengthbuffer, maxLength: 4)
guard lenbytes_read == 4 else {
self.errorHandler(NetworkingError.InputError("Input Stream received \(lenbytes_read) (!=4) bytes"))
return
}
let bytes_expected = Int(UnsafeRawPointer(lengthbuffer).load(as: UInt32.self).bigEndian)
log.info(self.buildLogMessage("expect \(bytes_expected) bytes"))
print("::DEBUG", call, "bytes_expected", bytes_expected)
var message = ""
var bytes_missing = bytes_expected
while bytes_missing > 0 {
//print("::DEBUG", call, "bytes_missing", bytes_missing)
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bytes_missing)
let bytes_read = istr.read(buffer, maxLength: bytes_missing)
print("::DEBUG", call, "bytes_read", bytes_read)
guard bytes_read > 0 else {
print("bytes_read not > 0: \(bytes_read)")
return
}
guard bytes_read <= bytes_missing else {
print("Read more bytes than expected. missing=\(bytes_missing), read=\(bytes_read)")
return
}
guard let partial_message = String(bytesNoCopy: buffer, length: bytes_read, encoding: .utf8, freeWhenDone: true) else {
log.error("ERROR WHEN READING")
return
}
message = message + partial_message
bytes_missing -= bytes_read
}
self.handleMessage(message)
}
}
My problem is that istr.read(buffer, maxLength: bytes_missing) sometimes does not read all messages at once, so I loop until I have read all I want. But I still see my app crashing (rarely) because handleInput() is called again while another call to that method is still running. In this case, bytes_expected contains random values and the app crashes due to illegal memory allocation.
I thought I could avoid this by using the barrier. But it seems this does not work... Am I using the barrier wrong?
My suggestion is not to fight against the asynchronous nature of network I/O.
Read and collect data in a buffer whenever the Stream.Event.hasBytesAvailable event
is signalled. If the buffer contains enough data (4 length bytes plus the
expected message length) then process the data and remove it. Otherwise do nothing
and wait for more data.
The following (untested) code is meant as a demonstration.
It shows only the parts which are relevant for this particular problem.
Initialization, event handler, etc are omitted for brevity.
class MessageReader {
var buffer = Data(count: 1024) // Must be large enough for largest message + 4
var bytesRead = 0 // Number of bytes read so far
// Called from `handleInput` with a complete message.
func processMessage(message: Data) {
// ...
}
// Called from event handler if `Stream.Event.hasBytesAvailable` is signalled.
func handleInput(istr: InputStream) {
assert(bytesRead < buffer.count)
// Read from input stream, appending to previously read data:
let maxRead = buffer.count - bytesRead
let amount = buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
istr.read(p + bytesRead, maxLength: maxRead)
}
guard amount > 0 else {
// handle EOF or read error ...
fatalError()
}
bytesRead += amount
while bytesRead >= 4 {
// Read message size:
let messageSize = buffer.withUnsafeBytes { (p: UnsafePointer<UInt32>) in
Int(UInt32(bigEndian: p.pointee))
}
let totalSize = 4 + messageSize
guard totalSize <= buffer.count else {
// Handle buffer too small for message situation ...
fatalError()
}
if bytesRead < totalSize {
break // Not enough data to read message.
}
// Buffer contains complete message now. Process it ...
processMessage(message: buffer[4 ..< totalSize])
// ... and remove it from the buffer:
if totalSize < bytesRead {
// Move remaining data to the front:
buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
_ = memmove(p, p + totalSize, bytesRead - totalSize)
}
}
bytesRead -= totalSize
}
}
}
Inspired by Martin R (https://stackoverflow.com/a/48344040/3827381 - Thank you very much!) I came up with this solution:
var buffer = Data(count: 4096)
var offset = 0 // the index of the first byte that can be overridden
var readState = 0
var missingMsgBytes = 0
var msg = ""
func handleInput(_ istr: InputStream) {
assert(buffer.count >= 5, "buffer must be large enough to contain length info (4 bytes) and at least one payload byte => min 5 bytes buffer required")
assert(offset < buffer.count, "offset \(offset) is not smaller than \(buffer.count)")
let toRead = buffer.count - offset
let read = buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in istr.read(p + offset, maxLength: toRead) }
guard read > 0 else {
self.errorHandler(NetworkingError.InputError("Input Stream received \(read) bytes which is smaller than 0 => Network error"))
return
}
offset += read
var msgStart = 0
var msgEnd = 0
if readState == 0 {
if offset < 4 {
return
}
missingMsgBytes = buffer[0...3].withUnsafeBytes { (p: UnsafePointer<UInt32>) in Int(UInt32(bigEndian: p.pointee)) }
msgStart = 4
msgEnd = offset
readState = 1
} else {
msgStart = 0
msgEnd = offset
}
var fullMessageRead = false
if readState == 1 {
let payloadRead = msgEnd - msgStart
if payloadRead <= missingMsgBytes {
assert(msgEnd > msgStart, "msgEnd (\(msgEnd) <= msgStart \(msgStart). This should not happen")
if msgEnd > msgStart {
msg += String(data: buffer[msgStart..<msgEnd], encoding: .utf8)!
missingMsgBytes -= payloadRead
offset = 0
}
fullMessageRead = (missingMsgBytes == 0)
} else { // read more than was missing
msg += String(data: buffer[msgStart..<msgStart+missingMsgBytes], encoding: .utf8)!
fullMessageRead = true
buffer.withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) in
_ = memmove(p, p + missingMsgBytes, read - missingMsgBytes) // dst, src, number
}
offset = read-missingMsgBytes
}
}
if fullMessageRead {
handleMessage(msg)
readState = 0
msg = ""
missingMsgBytes = 0
}
}
This solution is able to read messages of arbitrary size. The buffer size only determines how much can be read at one time => The bigger the buffer, the faster the app.
I tested the code for about an hour now and it did not crash. The old code crashed after 1-2 minutes. It seems to be finally working now.
But as I want to improve my programming knowledge I'd like to ask if there are some unnecessary complicated things in my code or if anyone sees a bug that could possibly still cause the app to crash or to read wrong data?

Read a text file line by line in Swift?

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.

Resources