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