Since the cocoa framework can be used to quickly create an available application, many developers prefer OS X or iOS development. Today, even small teams can design and develop complex applications, thanks to the tools and frameworks provided by these platforms. Swift playground not only inherits the tradition of rapid development, but also has the potential to change the way we design and write OS X and iOS applications.
To those who are not familiar with this concept, swift playground is like an interactive document where you can enter Swift code to compile and execute it immediately. The operation result is displayed step by step along with the execution timeline. developers can output and monitor variables at any time. Playground can be created in an existing xcode project and can also be used as a separate package.
Swift playground is mainly valued as a tool for learning this language. However, we only need to pay attention to similar projects, such as ipython notebooks, you can see the potential application of the interactive programming environment in a wider range. From Scientific Research to machine vision experiments, these tasks now use ipython notebooks. This method is also used to explore examples of other languages, such as Haskell functional programming.
Next we will explore the use of swift playground in terms of documentation, testing, and quick prototyping. All the swift playground source code used in this article can be downloaded here.
Use playground for documentation and Testing
SWIFT is a brand new language. Many people use playground to understand its syntax and conventions. Swift also provides a new standard library. The methods described in this standard library document are not very detailed, so many resources such as practicalswift.org standard library method list have sprung up.
The editor's note here is a swift standard library document automatically generated and organized for reference.
However, it is one thing to know the role of a method through the document, and the actual call in the Code is another. In particular, many methods show interesting features in the Collection class of the new language Swift. Therefore, it is very helpful to test their functions in collections.
Playground displays the syntax and Real-time Data Execution features, providing a good opportunity for writing methods and library interfaces. To introduce the use of the collection method, we created an example called collectionoperations. playground, which contains a series of collection methods, and all the sample data can be modified in real time.
For example, we have created the following initial array:
let testArray = [0, 1, 2, 3, 4]
Try againfilter()
Method:
let odds = testArray.filter{$0 % 2 == 1}
The last row displays the result array of this operation:[1, 3]
. Through real-time compilation, we can understand the syntax, Write examples, and get instructions on how to use the method. All of these are shown as a live document.
This works for other Apple frameworks and third-party libraries. For example, you may want to show others how to use scene kit. This is a great framework provided by apple that can quickly build 3D scenes on Mac and IOS. You may write a sample application, but you need to build and compile the application in this way.
In the scenekitmac. playground example, we have created a 3D scene with complete functions and animations. You need to open the assistant editor (Click View | assistant editor | show assistant editor in sequence on the menu). 3D effects and animations will be automatically rendered. This does not require compilation loops, and any changes, such as changing colors, geometric shapes, and brightness, can be reflected in real time. Using it can record and describe how to use the framework in an interactive example.
In addition to displaying the operations of methods and methods, you will also notice that by checking the output results, we can verify whether the execution of a method is correct, even when loading to playground, you can determine whether the method is correctly parsed. It is not hard to imagine that we can also add assertions in playground and create a true unit test. Alternatively, create a qualified test to achieve Test-driven development when you type.
In fact, in pragpub, July 2014, Ron Jeffries mentioned this point in his article "from the perspective of test-driven development:
Playground will largely influence how we execute test-driven development. Playground can quickly show what we can do, so we will go faster than before. But can it be better if it is combined with the previous test-driven development framework? Can we develop better code to meet the need for fewer defects and refactoring?
Let's leave questions about code quality to others. Next let's take a look at how playground accelerates the development of a rapid prototype.
Create an accelerate prototype-Optimized Signal Processing
The accelerate framework includes many powerful parallel processing methods for large datasets. These methods can take advantage of the SSE instruction set in an Intel chip, or the neon technology in an ARM chip, such as the advantages of vector processing instruction in a modern CPU. However, their interfaces seem a little opaque compared to the powerful functions, and their documents used are also a little lacking. This causes many developers to be unable to use the advantage of the powerful accelerate tool.
Swift provides an opportunity to make interaction easier after methods are reloaded or packaged for the accelerate framework. This has been proven in the practice of Chris liscio's library smugmath, which is the source of inspiration for the prototype we will create next.
Suppose you have a series of sine wave data samples and want to use these data to determine the frequency and amplitude of the sine wave. What would you do? One solution is to use Fourier transformation to calculate these values. Fourier transformation can extract frequency and amplitude information from one or more overlapping sine waves. The accelerate framework provides another solution called Fast Fourier Transform (FFT). Here is a good explanation of this solution (based on ipython notebook.
We have implemented this prototype in the example acceleratefunctions. playground. You can refer to the following content in this example. Make sure that you have enabled the assistant editor (Click View | assistant editor | show assistant editor in sequence on the menu) to view the images generated by each stage.
First, we need to generate some sample waveforms for the experiment. Use Swift'smap()
Methods can be easily implemented:
let sineArraySize = 64let frequency1 = 4.0let phase1 = 0.0let amplitude1 = 2.0let sineWave = (0..<sineArraySize).map { amplitude1 * sin(2.0 * M_PI / Double(sineArraySize) * Double($0) * frequency1 + phase1)}
To facilitate the subsequent use of FFT, the initial array size must be the power of 2. SetsineArraySize
Changing the value to 32,128 or 256 will change the density of the displayed image, but it will not change the basic result of calculation.
To plot our waveforms, we will use the new xcplayground framework (pilot-in required) and the following helper functions:
func plotArrayInPlayground<T>(arrayToPlot:Array<T>, title:String) { for currentValue in arrayToPlot { XCPCaptureValue(title, currentValue) }}
When we execute:
plotArrayInPlayground(sineWave, "Sine wave 1")
We can see the following chart:
This is a sine wave with a frequency of 4.0, an amplitude of 2.0, and a phase of 0. To make it more interesting, we created the second sine wave with a frequency of 1.0, an amplitude of 1.0, and a phase of π/2, and then superimposed it on the first sine wave:
let frequency2 = 1.0let phase2 = M_PI / 2.0let amplitude2 = 1.0let sineWave2 = (0..<sineArraySize).map { amplitude2 * sin(2.0 * M_PI / Double(sineArraySize) * Double($0) * frequency2 + phase2)}
Now we need to combine two waves. From here, accelerate will help us complete our work. Adding two independent floating-point arrays is suitable for merging rows. Here we need to use the accelerate vdsp library, which has such functions. To make it all more interesting, we will overload a swift operator for vector superposition. Unfortunately+
This operator has been used for Array join (which is confusing in fact), and++
It is more suitable as an incremental operator, so we will define+++
As the addition operator.
infix operator +++ {}func +++ (a: [Double], b: [Double]) -> [Double] { assert(a.count == b.count, "Expected arrays of the same length, instead got arrays of two different lengths") var result = [Double](count:a.count, repeatedValue:0.0) vDSP_vaddD(a, 1, b, 1, &result, 1, UInt(a.count)) return result}
The preceding definition defines an operator that can combine twoDouble
Elements in the swift array are merged into an array in sequence. In the operation, a blank array with the same length as the input array is created (assuming that the two input arrays are of the same length ). Because Swift's one-dimensional array can be directly mapped to an array in the C language, we only needDoubles
Type array directly transmittedvDSP_vaddD()
Method, and add the prefix before the result of our array&
.
To verify that the preceding superposition is correctly executed, we can use the for loop and accelerate methods to plot the combined sine wave:
var combinedSineWave = [Double](count:sineArraySize, repeatedValue:0.0)for currentIndex in 0..<sineArraySize { combinedSineWave[currentIndex] = sineWave[currentIndex] + sineWave2[currentIndex]}let combinedSineWave2 = sineWave +++ sineWave2plotArrayInPlayground(combinedSineWave, "Combined wave (loop addition)")plotArrayInPlayground(combinedSineWave2, "Combined wave (Accelerate)")
Sure enough, the results are consistent.
Before proceeding to the FFT itself, we need another vector operation to process the computation results. All the results obtained in the FFT Implementation of accelerate are after the square, so we need to perform the square root operation on them. We need to call similarsqrt()
Method, which sounds like an opportunity to use accelerate.
The veclib library of accelerate has many equivalent mathematical methods, including square rootvvsqrt()
. This is a good example of using method overloading. Let's create a new versionsqrt()
To processDouble
Type array.
func sqrt(x: [Double]) -> [Double] { var results = [Double](count:x.count, repeatedValue:0.0) vvsqrt(&results, x, [Int32(x.count)]) return results}
Like our superimpose operators, the input of the overloaded square function isDouble
Array, createsDouble
And pass all the parameters in the input array directly to the acceleratevvsqrt()
. By entering the following code in playground, we can verify the method that was just loaded.
sqrt(4.0)sqrt([4.0, 3.0, 16.0])
We can see that the standardsqrt()
The function returns 2.0, while the newly created overload method returns [2.0, 1.73205080756888, 4.0]. This is indeed an easy-to-use overload method. You can even imagine using veclib in the above method to write a parallel version for all mathematical methods (but mattt Thompson has already done this ). Process an array with 2012 elements in a 15-inch 0.1 billion i7 version of MacBook Pro, using the accelerate-basedsqrt()
The method is faster than iteration using a common one-dimensionalsqrt()
Almost doubled.
With this, we can implement FFT. We do not intend to spend a lot of time on the details of the FFT settings. below is our FFT method:
let fft_weights: FFTSetupD = vDSP_create_fftsetupD(vDSP_Length(log2(Float(sineArraySize))), FFTRadix(kFFTRadix2))func fft(var inputArray:[Double]) -> [Double] { var fftMagnitudes = [Double](count:inputArray.count, repeatedValue:0.0) var zeroArray = [Double](count:inputArray.count, repeatedValue:0.0) var splitComplexInput = DSPDoubleSplitComplex(realp: &inputArray, imagp: &zeroArray) vDSP_fft_zipD(fft_weights, &splitComplexInput, 1, vDSP_Length(log2(CDouble(inputArray.count))), FFTDirection(FFT_FORWARD)); vDSP_zvmagsD(&splitComplexInput, 1, &fftMagnitudes, 1, vDSP_Length(inputArray.count)); let roots = sqrt(fftMagnitudes) // vDSP_zvmagsD returns squares of the FFT magnitudes, so take the root here var normalizedValues = [Double](count:inputArray.count, repeatedValue:0.0) vDSP_vsmulD(roots, vDSP_Stride(1), [2.0 / Double(inputArray.count)], &normalizedValues, vDSP_Stride(1), vDSP_Length(inputArray.count)) return normalizedValues}
In the first step, we set the FFT weight to be used in calculation, which is related to the size of the array to be processed. These weights will be used in the actual FFT calculation later.vDSP_create_fftsetupD()
And can be reused for arrays of a given size. Because the size of the array is a constant here, we only need to calculate the weight once and use it as a global variable and reuse it in each FFT.
In the FFT method, we Initialize an array for storing operation results.fftMagnitudes
The initial element of the array is 0, and the size of the sine wave is the same as that of the previous one. The input parameters of the FFT operation are in the form of real-part and virtual-part plural, but what we really care about is its real-part. Therefore, we initializesplitComplexInput
Use the input array as the real part, and use zero as the virtual part. ThenvDSP_fft_zipD()
AndvDSP_zvmagsD()
Responsible for executing FFT and usingfftMagnitudes
Array to store the number of records obtained from the FFT.
Here, we use the accelerate-basedsqrt()
Method to Calculate the square root, return the actual size, and then normalize the value based on the size of the input array.
For a single sine wave, the results of all the above operations are as follows:
The superimposed sine wave looks like this:
These results represent a set of sine wave frequencies. from the left, the values in the Set represent the amplitude of the waves detected at this frequency. They are about center symmetry, so you can ignore the values in the right half of the graph.
We can see that for a wave whose frequency is 4.0 and its amplitude is 2.0, a value located at 4 corresponds to 2.0 in FFT. Similarly, for a wave whose frequency is 1.0 and its amplitude is 1.0, in FFT, it is a point where 1 corresponds to 1.0. Although the FFT waveform obtained by the superimposed sine wave is complex, the amplitude and frequency of the two waves in their respective sets can still be clearly defined, as if their FFT results were added separately.
Again, this is a simplified version of the FFT operation. The above FFT code has simplified operations, but the key is to step through the step-by-step creation method in playground, we can easily explore a complex signal processing operation, and each step of operation testing can immediately get graphical feedback.
Use swift playgrounds to quickly create a prototype
We hope these examples can illustrate the role of swift playground in implementing new class libraries and new concepts.
In each step in the previous example, we can observe the status of the intermediate array through the pattern in the timeline during execution. This is very useful for a sample program and provides an interface for the program in some way. All these images are updated in real time, so you can return to the implementation at any time and modify the frequency or amplitude of one of the waves, and then watch the waveforms change with the processing steps. This shortens the development cycle and greatly helps users experience the computing process.
This type of instant feedback Interactive Development is a good case for creating prototypes for complex algorithms. Before deploying such a complex algorithm to practical applications, we have the opportunity to verify and study it in playground.
Topic # more articles under 16
Original article Rapid Prototyping in swift playgrounds
Playground quick Prototyping (conversion)