The articles

Multipeer Connectivity Near-field multipoint communication implements a simple sandbox file browser implements a simple breadcrumb navigation

IO stream

Introduction to the

Input/Output refers to the Input and Output, centered on memory

Input refers to reading data from the outside into memory, for example, reading data from disk or network into memory

Output refers to writing data from memory to the outside world, such as writing data from memory to the network

The data has to be in memory to be processed, because the code is running in memory, the data has to be read into memory, and it ends up showing up as an array of bytes, or an array of strings

IO flow is a pattern of sequential reading and writing data. It is characterized by one-way flow. Data flows in a water pipe like running water, so we call it an IO stream.

Usage scenarios

When our programs copy files from one place to another, they often need to be read into memory

Directly read

If we just use the way below, we first convert the network video into NSData and save it in memory, and then write the NSData in memory into the sandbox and save it.

In this way, when the video is only tens of meters, it will not have a big impact. If the video is several GIGABytes, several GIGABytes of NSData data will be written into our memory temporarily, which will occupy a lot of running memory. In the case of insufficient running memory of mobile phone, crash will be caused.

Use the stream

For example, if we carry water in a bucket, if we need to carry a lot of water at one time, we need to use a bigger bucket. It would be a lot easier if we had a pipe connecting the outlet to the pool, and we could move the water from one place to another without too much effort.

Data flow is a similar concept. We compare data to water, flow to pipe, and memory to power a pump. In this way, there is no memory explosion due to too much data, and the fast read performance of memory can be used to improve efficiency.

So a stream is like a pipe in memory that allows the output file and the input file to flow in one direction.

IO streams in IOS

NSStream

Is an abstract class that defines the interface for manipulating a stream in iOS. A stream can also be understood as an object in objC, where everything is an object

Provides a simple way to read and write data from a variety of media in a device-independent manner. You can create stream objects for data that is in memory, a file, or a network (using sockets), and you can use stream objects without having to load all the data into memory at once

The interfaces are defined as follows

Open and close

Open Opens the stream for reading or writing. If the stream object is added to the runloop, the stream object’s delegate method takes effect

To close a stream, note that the stream cannot be opened again after being closed

delegate

Delegate the stream object. Note that the stream object must be added to the Runloop in order for the principal to receive information from the stream object.

ScheduleInRunLoop: forMode: and removeFromRunLoop: forMode:

ScheduleInRunLoop: forMode: objects are added to the runloop will flow

RemoveFromRunLoop: forMode: object is removed from the runloop will flow

PropertyForKey: and setProperty: forKey

Sets and gets property values in the NSStream stream object

StreamStatus and streamError

The transmission status and error code of the stream object

NSSInputStream

NSStream is an abstract class that does not instantiate objects, but defines a set of implementation standards.

Its subclass NSSInputStream implements the reading function of the stream object, which is equivalent to the pumping function in our water pipe model. It is mainly used to write data to a stream object.

The main methods used are as follows

Read buffer

Taking the water pipe model as an example, in real life we can actually have a piece of water pipe, but in memory we can’t really build a pipe, so we need to use the buffer to achieve the effect of pipe. The cache is limited in size and read length, that is, the size of the pipe and the speed of the water flow. Only data read into the buffer can be sent to the writing end

read:maxLength:

Method of reading data,

  • The first argument, buf, represents the read buffer and is a pointer to Int8, since we know that 8 bits are a byte.

  • The second parameter: maxLength represents the maximum length of data to be read from the buffer each time.

  • Return value: indicates the number of bytes read. Generally, it is equal to maxLength. If the remaining data in the buffer is smaller than maxLength, the return value is smaller than maxLength, indicating that the data in the buffer is read.

getBuffer:length:

Gets the data and size in the current stream

  • First argument: a pointer to the read buffer

  • Second argument: read the number of bytes available in the buffer

  • Return value: is a BOOL

YES if the buffer is available, NO otherwise

hasBytesAvailable

Return YES if there is more data to read from the stream, and NO if there is none

use

Cyclic read mode

This method uses a primitive loop that reads the file data extracted from InputStream into the buffer one at a time. A buffer used to write a large file into segments that are inserted into the next segment after the buffer has read one segment. But if it’s in the main thread it’s going to cause a UI wait. So put it in a child thread again.


func setupInputStream(a){
        // Set the path to read the file
        let input = InputStream.init(fileAtPath: path)
        input?.open()
        let readLength = 32;
        let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: readLength);

        while true {
            let bytes = input?.read(buf, maxLength: readLength)
            if bytes! < readLength {
                break
            }
        }
        input?.close()
        buf.deallocate()
    }
Copy the code

Note that in Swift integer array Pointers are held using an UnsafeMutablePointer and therefore are not automatically GC, so we need to release them manually, otherwise we will leak memory

Runloop mode

From the above pattern, we found that the write stream (NSInputStream) needs to read the file one by one through the cache, so a loop is needed to perform the read, but as far as we know, IOS has an extremely powerful loop called RunLoop. So we can use the features of Runloop to help us use high-quality loops to get the stream objects to read data

After adding the stream object to the runloop, I can get the state of the stream read by implementing the NSStream delegate.

// Get the resource address
func setupInputStream(a){
        let path = self.getResource()
       // Create an input stream to set the path to read the file
        let inputStream = InputStream.init(fileAtPath: path)
        self.inputStream = inputStream;
        inputStream?.delegate = self;
        // Add a run loop to the stream object
        inputStream?.schedule(in: RunLoop.current, forMode: .common)
        // Open the input stream object
        inputStream?.open();
}

func stream(_ aStream: Stream.handle eventCode: Stream.Event) {

        switch eventCode {
        
        case .openCompleted: // The stream object was opened successfully

            print("Stream object opened successfully")

        case .hasBytesAvailable: // The stream object has data to read (NSSInputStream)
            let maxLength = 4*1024
            let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: maxLength);
            self.inputStream!.read(buf, maxLength: maxLength)
            buf.deallocate()
            buf.deinitialize(count: maxLength)
  
        case .hasSpaceAvailable: // There is space for the stream object to write buffer (NSOuputStream)

        case .endEncountered:  // The stream object completes the data reading
            aStream.close()
            aStream.remove(from: RunLoop.current, forMode: .common)
            
        case .errorOccurred: // Error reading data from stream object

            print("Error reading data from stream object")

  
        default: break}}Copy the code

NSOutputStream

It is also a subclass of NSStream, which implements the write function of stream objects. It is equivalent to the water discharge function in the water pipe model.

Write buffer

A buffer used to write data to an NSOutputStream in the same way as a read buffer. Used to provide external data read by NSOutputStream

write:maxLength:

Write data method,

  • The first argument: buf stands for write buffer, is a pointer, because we know that 8 bits is a byte, so is an array pointer to Int8.

  • The second parameter: maxLength represents the maximum length of data to be read from the buffer each time.

  • Return value: indicates the number of bytes written to the buffer. Generally speaking, it is equal to the maximum read length of the buffer. If the remaining data in the buffer is smaller than maxLength, the return value is smaller than maxLength, indicating that the data in the buffer is read.

hasSpaceAvailable

YES indicates that data can be written to the current stream. NO indicates that data cannot be written to the current stream

use

Cyclic read mode

The principle is the same as the read stream, but also according to the buffer size of the loop to read. Also be careful to release variables that do not support automatic GC. Avoiding memory leaks

func setupOutputStream(a) {
        let output = OutputStream.init(url: writeResource(), append: false)
        output?.open()
        let readLength = 32;
        let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: readLength);
        while true {
            let bytes = outputStream?.write(buf, maxLength: readLength)
            if bytes! < readLength {
                break
            }
        }
        output?.close()
        buf.deallocate()
    }
Copy the code

Runloop mode

func setupInputStream(a){
        // Create the output stream output file address
        let fileUrl = writeResource()
        // Create an output stream
        let outputStream = OutputStream.init(url: fileUrl as URL, append: false)
        self.outputStream = outputStream;
        outputStream?.delegate = self
        outputStream?.schedule(in: RunLoop.current, forMode: .common)
        outputStream?.open()
}

func stream(_ aStream: Stream.handle eventCode: Stream.Event) {

        switch eventCode {
        
        case .openCompleted: // The stream object was opened successfully

            print("Stream object opened successfully") 

        case .hasBytesAvailable: // The stream object has data to read (NSSInputStream)

        case .hasSpaceAvailable: // There is room for writing to the stream object (NSOuputStream)

            let maxLength = 4*1024

            let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: maxLength);

            self.outputStream?.write(buf, maxLength: maxLength)

            buf.deallocate()

            buf.deinitialize(count: maxLength)


        case .endEncountered:  // The stream object completes the data reading

            aStream.close()

            aStream.remove(from: RunLoop.current, forMode: .common)


        case .errorOccurred: // Error reading data from stream object

            print("Error reading data from stream object")

        default: break}}Copy the code

Complete IO stream reading example

Here we test whether using and not using IO streams results in running memory inflation by reading a 70MB video

We write videos from the project resources directory to the application directory to observe memory changes with and without IO stream

IO streams are not used

The file is read directly into memory and then written to the sandbox

We can observe that the memory suddenly increases several times

Use the IO stream

After using the IO stream, we found that the memory changes very smoothly, indicating that there is no large amount of data suddenly flooded into memory, files are read through our IO stream built pipe from the send position to the receive position bit by bit

Read and write flow code

func openIOStream(a) {
        // Get the resource address

        let path = self.getResource()

// // Create an input stream

        let inputStream = InputStream.init(fileAtPath: path)

        self.inputStream = inputStream;

        inputStream?.delegate = self;

        // Add a run loop to the stream object

        inputStream?.schedule(in: RunLoop.current, forMode: .common)

        // Open the input stream object
        inputStream?.open();

        // Create the output stream output file address

        let fileUrl = writeResource()

        // Create an output stream

        let outputStream = OutputStream.init(url: fileUrl as URL, append: false)

        self.outputStream = outputStream;

        outputStream?.delegate = self

        outputStream?.schedule(in: RunLoop.current, forMode: .common)

        outputStream?.open()

}


func stream(_ aStream: Stream.handle eventCode: Stream.Event) {

        switch eventCode {

        case .openCompleted: // The stream object was opened successfully
            print("Stream object opened successfully")
          
        case .hasBytesAvailable: // The stream object has data to read (NSSInputStream)
            let maxLength = 4*1024
            
            let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: maxLength);

            self.inputStream!.read(buf, maxLength: maxLength)
           
            buf.deallocate()
            
            buf.deinitialize(count: maxLength)
        

        case .hasSpaceAvailable: // There is room for writing to the stream object (NSOuputStream)

            let maxLength = 4*1024

            let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: maxLength);

            self.outputStream?.write(buf, maxLength: maxLength)

            buf.deallocate()

            buf.deinitialize(count: maxLength)
            

        case .endEncountered:  // The stream object completes the data reading

             self.inputStream?.close()

            self.inputStream?.remove(from: RunLoop.current, forMode: .common)

            self.outputStream?.close()

            self.outputStream?.remove(from: RunLoop.current, forMode: .common)
            

        case .errorOccurred: // Error reading data from stream object

            print("Error reading data from stream object")

        default: break}}Copy the code