Wednesday, May 31, 2017
Tuesday, May 30, 2017
Monday, May 29, 2017
The Pool Window in Cubase—Part 3
How to Fix Missing Files
Sometimes when working with huge projects, there are cases where the project files go missing.
This might be because you renamed the folder containing the files, or moved the folder to save space or it could be due to another reason. Unless you specify Cubase that the files have been moved to another location, Cubase will accept the files as missing and will continue the project without the missing files.
Cubase has an in-built function that helps find the missing files that are being used in a given project and it can re-link them into the project.
To do this, open the Find Missing Files option from the Media menu. In the dialog box that appears, you'll find the list of the files that are missing from the project.
Select either of the three options:
- Locate: With this option, select the location of the file manually. Use this option if you're sure of the location of the missing file or if you have moved the file to a specific location. This option helps manually select the file and re-link it to the project. If you have renamed the file to something else, this option is the best bet to re-link it back to the project
- Folder: Selects the folder in which the file was moved. If you have moved the file to a new folder without renaming it, you can re-link it using this method. Select the folder and Cubase automatically detects the correct file and link it to the project
- Search: With this option, select a disk or a folder in which Cubase can search and find the file that is missing. Specify whether the search term is case-sensitive or not. Once the drive or folder is , click on Start and Cubase will search the specific location and find the files for you. If there are more missing files in the project and they are in the location that you specified, they'll also be re-linked into the project. Once the missing files are found, click on Accept
Sometimes, if the file that is missing was created by an edit, you can use the Reconstruct option in the Pool window. This re-creates the files by using the original file and processing it to create the missing file.
Cubase also has an option to remove the files that are missing in the project. Select Remove Missing Files in the Pool window and Cubase will remove the missing files from the Pool and from the project.
This option is very useful when you have to share your project with someone else. By removing the missing files from the project, once the other person opens the project, there will be no warnings saying that some files are missing from the session. Doing this will help reduce the size of a project and enable easy sharing.
Auditioning Clips
Auditioning clips in Cubase is easy and it's possible to audition clips in many different ways. The three main methods are described below:
- Spacebar: The easiest method to audition a clip. Just as in Pro Tools, use the spacebar to audition a clip. You need to activate the Playback Toggle Triggers Local Preview option found under File > Preferences > Transport. To stop the preview, press the spacebar again
- Audition Button: Use the Audition button on the top left corner of the Pool Window to audition the audio. To stop the audio, click the same button again
- Waveform: An interesting way to audition clips. Click on the waveform and the playback starts from the place you clicked. It plays from that position to the end of the clip. To stop the playback, just click anywhere in the Pool window or on the Audition button
To loop a clip in the audition stage, click on the Loop button besides the Audition button.
The clip loops until the Stop button or Loop button is pressed again. In this mode, if you click on the waveform of the clip, the playback starts from the point that you clicked on the waveform and again start from the beginning once the clip has been played back.
You can adjust the level of the playback by adjusting the fader on the right side of the audition button.
The default playback route is via the Control Room, if the Control Room is set up, else the audio will be routed through the main bus itself.
Conclusion
In this tutorial, I've shown you how to fix the missing files in a project and to audition clips in the Pool Window.
Including these methods in projects speeds up workflow. Let me know in the comments below how you use the Pool Window.
Saturday, May 27, 2017
Friday, May 26, 2017
Thursday, May 25, 2017
How to Handle Exceptions in Elixir
Exception handling is a great practice for any software development methodology. Whether it's for test-based development, agile sprints, or a hacking session with just a good old todo list, we all can benefit from ensuring our bases are covered with a robust approach to defect handling.
It's paramount to ensure errors are taken care of, whilst being aesthetically pleasing and of course not becoming a big problem logically with cryptic messages for the end user to try and glean meaning from. If you do that, you certainly are on a great route to make a solid, stable and sticky app that users enjoy working with and will recommend highly to others.
Ideally for us, Elixir provides extensive exception handling via several mechanisms such as try/catch
, throws
, and the {:error, reason}
tuple.
To display an error, use raise
in your interactive shell to get a first taste:
iex> raise "Oh noez!" ** (RuntimeError) Oh noez!
We can also add a type to this like so:
iex> raise ArgumentError, message: "error message here..." ** (ArgumentError) error message here...
How Error Handling Works in Elixir
Some of the ways errors are dealt with in Elixir may not be obvious at first glance.
- Firstly about processes—in using
spawn
, we can create independent processes. That means a failure on one thread should not affect any other process, unless there was a linkage in some manner. But by default, everything will stay stable.
- To notify the system of a failure in one of these processes, we can use the
spawn_link
macro. This is a bidirectional link, which means that if a linked process terminates, an exit signal will be triggered.
- If the exit signal is anything other than
:normal
, we know we have a problem. And if we trap the exit signal withProcess.flag(:trap_exit, true)
, the exit signal will be sent to the process's mailbox, where the logic can be placed on how to handle the message, thus avoiding a hard crash.
- Finally we have Monitors, which are similar to
spawn_links
, but these are unidirectional links, and we can create them withProcess.monitor
.
- The process which invokes the
Process.monitor
will receive the error messages on failure.
For a sample error, try adding a number to an atom and you will get the following:
iex> :foo + 69 ** (ArithmeticError) bad argument in arithmetic expression :erlang.+(:foo, 69)
To ensure the end user does not get errored out, we can use the try, catch and rescue methods provided by Elixir.
Try/Rescue
First in our toolbox for exception handling is try/rescue
, which catches errors produced by using raise
so is really best suited for developer errors, or exceptional circumstances such as input error.
try/rescue
is similar in usage to a try/catch
block you may have seen in other programming languages. Let's look at an example in action:
iex> try do ...> raise "do failed!" ...> rescue ...> e in RuntimeError -> IO.puts("Error: " <> e.message) ...> end Error: do failed! :ok
Here we utilize the try/rescue
block and the aforementioned raise
to catch the RuntimeError
.
This means the ** (RuntimeError)
default output of raise
is not displayed, and is replaced with a nicer formatted output from the IO.puts
call.
As a best practice, you must use the error message to give the user useful output in plain English, which helps them with the issue. We'll look at that more in the next example.
Multiple Errors in a Try/Rescue
A major benefit of Elixir is that you can catch multiple outcomes in one of these try/rescue
blocks. Look at this example:
try do opts |> Keyword.fetch!(:source_file) |> File.read! rescue e in KeyError -> IO.puts "missing :source_file option" e in File.Error -> IO.puts "unable to read source file" end
Here we have caught two errors in the rescue
.
- If the file is unable to read.
- If the
:source_file
symbol is missing.
As mentioned before, we can use this for making easy-to-understand error messages for our end user.
This powerful and minimal syntax approach of Elixir makes writing multiple checks very accessible for us to check many possible points of failure, in a neat and concise way. This helps us to ensure we don't need to write elaborate conditionals making long-winded scripts that may be hard to visualize fully and debug correctly during later development or for a new developer to join.
As always when working in Elixir, KISS is the best approach to take.
After
There are situations when you will require a specific action performed after the try/rescue block, regardless of if there was any error. For Java or PHP developers, you may be thinking of the try/catch/finally
or Ruby's begin/rescue/ensure
.
Let's take a look at a simple example of using after
.
iex> try do ...> raise "I wanna speak to the manager!" ...> rescue ...> e in RuntimeError -> IO.puts("An error occurred: " <> e.message) ...> after ...> IO.puts "Regardless of what happens, I always turn up like a bad penny." ...> end An error occurred: I wanna speak to the manager! Regardless of what happens, I always turn up like a bad penny. :ok
Here you see the after
being used to constantly make a message display (or this could be any function you wished to throw in there).
A more common practice you will find this used on is where a file is being accessed, for example here:
{:ok, file} = File.open "would_defo_root.jpg" try do # Try accessing file here after # Ensure we clean up afterwards File.close(file) end
Throws
As well as the raise
and try/catch
methods we have outlined earlier, we also have the throw and catch macros.
Using the throw
method exits execution with a specific value we can look for in our catch
block and use further like so:
iex> try do ...> for x <- 0..10 do ...> if x == 3, do: throw(x) ...> IO.puts(x) ...> end ...> catch ...> x -> "Caught: #{x}" ...> end 0 1 2 "Caught: 3"
So here we have the ability to catch
anything we throw
inside the try block. In this case, the conditional if x == 3
is the trigger for our do: throw(x)
.
The output from the iteration produced from the for loop gives us a clear understanding of what has occurred programmatically. Incrementally we have stepped forward, and execution has been halted on the catch
.
Because of this functionality, sometimes it can be hard to picture where the throw
catch
would be implemented in your app. One prime place would be in usage of a library where the API does not have adequate functionality for all outcomes presented to the user, and a catch would suffice to rapidly navigate around the issue, rather than having to develop much more within the library to handle the issue and return appropriately for it.
Exits
Finally in our Elixir error handling arsenal we have the exit
. Exiting is done not through the gift shop, but explicitly whenever a process dies.
Exits are signaled like so:
iex> spawn_link fn -> exit("you are done son!") end ** (EXIT from #PID<0.101.0>) "you are done son!"
Exit signals are triggered by processes for one of the following three reasons:
- A normal exit: This happens when a process has completed its job and ends execution. Since these exits are totally normal, usually nothing needs to be done when they happen, much like a
exit(0)
in C. The exit reason for this kind of exit is the atom:normal
. - Because of unhandled errors: This happens when an uncaught exception is raised inside the process, with no
try/catch/rescue
block orthrow/catch
to deal with it. - Forcefully killed: This happens when another process sends an exit signal with the reason
:kill
, which forces the receiving process to terminate.
Stack Traces
At any given update juncture on throw
, exit
or errors
, calling the System.stacktrace
will return the last occurrence in the current process.
The stack trace can be formatted quite a bit, but this is subject to change in newer versions of Elixir. For more information on this, please refer to the manual page.
To return the stack trace for the current process, you can use the following:
Process.info(self(), :current_stacktrace)
Making Your Own Errors
Yep, Elixir can do that also. Of course, you always have the built-in types such as RuntimeError
at your disposal. But wouldn't it be nice if you could go a step further?
Creating your own custom error type is easy by using the defexception
macro, which will conveniently accept the :message
option, to set a default error message like so:
defmodule MyError do defexception message: "your custom error has occurred" end
Here's how to use it in your code:
iex> try do ...> raise MyError ...> rescue ...> e in MyError -> e ...> end %MyError{message: "your custom error has occurred"}
Conclusion
Error handling in a meta-programming language like Elixir has a whole heap of potential implications for how we design our applications and make them robust enough for the rigorous bashing of the production environment.
We can ensure the end user is always left with a clue—a simple and easy-to-understand guiding message, which won't make their task difficult but rather the inverse. Error messages must always be written in plain English and give plenty of information. Cryptic error codes and variable names are no good to the average users, and can even confuse developers!
Going forward, you can monitor the exceptions raised in your Elixir application and set up specific logging for certain trouble spots, so you can analyze and plan your fix, or you can look at using an off-the-shelf solution.
Third-Party Services
Improve the accuracy of our debugging work and enable monitoring for your apps' stability with these third-party services available for Elixir:
- AppSignal can be very beneficial for the quality assurance phase of the development cycle.
- GitHub repo bugsnex is a great project for using the API interface with Bugsnag to further detect defects in your Elixir app.
- Monitor uptime, system RAM and errors with Honeybadger, which provides production error monitoring so you don't need to babysit your app.
Extending Error Handling Further
Going forward, you may wish to further extend the error handling capabilities of your app and make your code easier to read. For this, I recommend you check out this project for elegant error handling on GitHub.
I hope you have gained a further insight from this guide and will be able to practically handle any exception case you need in your Elixir app now!
Wednesday, May 24, 2017
Tuesday, May 23, 2017
Working With MeSH Files in Python: Linking Terms and Numbers
This tutorial shows how we can use different aspects of Python (i.e. dictionaries, lists, and regular expressions) together to solve different issues. It also shows how we can use Python to link the relationships in the MeSH file, making it easier to understand its hierarchy and structure.
Before moving ahead with this tutorial, you might be wondering what we mean by MeSH. So let's start by defining this term first, and then go into a bit more detail on its structure.
What Is MeSH?
MeSH is an acronym for Medical Subject Headings. It is considered the U.S. National Library of Medicine's controlled vocabulary (thesaurus), which gives uniformity and consistency to the indexing and cataloging of biomedical literature. MeSH, a distinctive feature of MEDLINE, is arranged in a hierarchical manner called the MesH Tree Structure, and is updated annually.
MeSH is thus a nomenclature of medical terms available from the U.S. National Library of Medicine, that aims to create new knowledge by exploiting the relationships among terms that annotate the biomedical literature.
People searching MEDLINE/PubMed and other databases make use of MeSH to assist with subject searching. The National Library of Medicine (NLM) indexers use MeSH to describe the subject content of journal articles for MEDLINE. Catalogers use MeSH to describe books and audiovisuals in the NLM and other library collections. So MeSH can be used for numerous tasks involving indexing, tagging, searching, retrieving, analyzing, coding, merging, and sharing biomedical text.
MeSH File Structure
MeSH descriptors are organized into 16 categories:
- A: anatomy
- B: organisms
- C: diseases
- D: drugs and chemicals
- E: analytical, diagnostic and therapeutic techniques and equipment
- F: psychiatry and psychology
- G: phenomena and processes
- H: disciplines and occupations
- I: anthropology, education, sociology, and social phenomena
- J: technology, industry, agriculture
- K: humanities
- L: information science
- M: named groups
- N: health care
- V: publication characteristics
- Z: geographicals
You can find more information about the categories from the U.S. National Library of Medicine. As we can see, each category is further divided into subcategories. This structure is, however, not considered an authoritative subject classification system, but rather as an arrangement of descriptors for the guidance and convenience of people who are assigning subject headings to documents or are searching for literature. It is thus not an exhaustive classification of the subject and contains only the terms that have been selected for inclusion in this thesaurus.
Here's some more information on the MeSH Tree Structures:
Because of the branching structure of the hierarchies, these lists are sometimes referred to as "trees". Each MeSH descriptor appears in at least one place in the trees, and may appear in as many additional places as may be appropriate. Those who index articles or catalog books are instructed to find and use the most specific MeSH descriptor that is available to represent each indexable concept.
Downloading a MeSH File
For the purpose of this tutorial, we need a MeSH file to work with in Python. You can find MeSH file on the NLM download site.
Let's go ahead and download the latest ASCII MeSH file. We can first go to the MeSH FTP Archive: http://ift.tt/28NhyNm, and then choose the 2017
directory. In the asciimesh/
directory, you will find three .bin
files: c2017.bin
, d2017.bin
, and q2017.bin
. Let's download d2017.bin
. You can download the file from: http://ift.tt/2rOr33x(27.5 MB).
Linking Terms to Numbers
Let's jump into the core of this article. What we are trying to do is read a MeSH file (i.e. the .bin
file you just downloaded), browse through the entries, find all the MeSH numbers for each entry, and list the terms along with their relevant numbers.
The first thing we would normally do is read the .bin
file, as follows:
meshFile = 'd2017.bin' with open(meshFile, mode='rb') as file: mesh = file.readlines()
Notice that we have used the rb
mode, meaning that we are reading binary with no line-break translation.
We also need to define an output file where we would store the results (output):
outputFile = open('mesh.txt', 'w')
At this point, we want to check the lines that start with MH =
(MeSH term) and MN =
(MeSH number). I shouldn't do this now, but will show you a snapshot of the MeSH file to have some idea of the structure and to remove any confusions (MH
and MN
are surrounded by red rectangles, respectively).
To check lines that start with MH =
and MN =
, we need to use regular expressions. So, if we want to check the lines that start with MH =
followed by any characters, we would do as shown in the code below (I'll get to what line
is in a moment). Notice that I have used b
instead of r
for the regular expression, since we are applying the pattern on a byte object and not a string object, so we should use a byte pattern.
import re meshTerm = re.search(b'MH = (.+)$', line)
The same thing would apply for the MeSH number, but this time for lines starting with MN =
.
Coming back to line
, this refers to the lines in the MeSH file. So we would be walking through the file line by line, looking for the MeSH terms and numbers. As you can see from the above MeSH file snapshot, the MeSH term comes before the MeSH number. So, in our code, the MeSH number will always be the number corresponding to the previously captured MeSH term. We will thus do the following:
for line in mesh: meshTerm = re.search(b'MH = (.+)$', line) if meshTerm: term = meshTerm.group(1) meshNumber = re.search(b'MN = (.+)$', line) if meshNumber: number = meshNumber.group(1) numbers[number.decode('utf-8')] = term.decode('utf-8') if term in terms: terms[term] = terms[term] + ' ' + number.decode('utf-8') else: terms[term] = number.decode('utf-8')
Let's go through the above code step by step. If we look at the regular expression MH = (.+)$
, this is basically telling us to find the literal MH =
followed by at least one character. (.
) means any character, and +
means that it has to be one or more characters, and return everything to the end of the line ($
).
The parenthesis around .+
, that is (.+)
, is a capture group so we can retrieve the result. So, for the MeSH term surrounded by a red rectangle in the above snapshot, the retrieved term will be Calcomycin
. The reason we are using if-statements is that some lines will neither start with MH =
nor MN =
.
For the captured MeSH term and MeSH number, we create a new key-value pair for a dictionary object, as shown in this line of code: numbers[str(number)] = term
.
It is important to note that a single MeSH term might have more than one MeSH number. So we concatenate every new MeSH number with the relevant term into a string, as shown in this portion of the code:
if term in terms: terms[term] = terms[term] + ' ' + number.decode('utf-8') else: terms[term] = number.decode('utf-8')
Thus in this case we will be having a dictionary object with key-value pairs that consist of a MeSH term as the key, and the concatenation collection of all corresponding MeSH numbers as the value.
What we want to do now is list the different keys (terms), and have the relevant values (numbers) listed under the relevant term. To list the different terms, we do the following:
meshNumberList = [] meshTermList = terms.keys() for term in meshTermList: item_list = terms[term].split(' ') for phrase in item_list: meshNumberList.append(phrase)
Finally, we will list the term and its relevant numbers as follows:
used_items = set() for item in meshNumberList: if numbers[item] not in used_items: print(numbers[item], '\n', item, file=outputFile) used_items.add(numbers[item]) else: print(item, file=outputFile)
Before showing the output of the program, let's put it all together.
Putting It All Together
In this section, I will show you what our full Python program that links the MeSH term to its numbers looks like:
import re terms = {} numbers = {} meshFile = 'd2017.bin' with open(meshFile, mode='rb') as file: mesh = file.readlines() outputFile = open('mesh.txt', 'w') for line in mesh: meshTerm = re.search(b'MH = (.+)$', line) if meshTerm: term = meshTerm.group(1) meshNumber = re.search(b'MN = (.+)$', line) if meshNumber: number = meshNumber.group(1) numbers[number.decode('utf-8')] = term.decode('utf-8') if term in terms: terms[term] = terms[term] + ' ' + number.decode('utf-8') else: terms[term] = number.decode('utf-8') meshNumberList = [] meshTermList = terms.keys() for term in meshTermList: item_list = terms[term].split(' ') for phrase in item_list: meshNumberList.append(phrase) meshNumberList.sort() used_items = set() for item in meshNumberList: if numbers[item] not in used_items: print(numbers[item], '\n', item, file=outputFile) used_items.add(numbers[item]) else: print(item, file=outputFile)
Output
You can download the output from Dropbox (1.77 MB). Taking a sample of the output as shown below, we can see how a MeSH term (Pterygopalatine Fossa
) is listed with its MeSH numbers that are grouped immediately underneath.
Pterygopalatine Fossa A02.835.232.781.670 A02.835.232.781.750 A02.835.232.781.750.150 A02.835.232.781.750.165 A02.835.232.781.750.400
Conclusion
The tutorial showed how we can use different aspects of Python (i.e. dictionaries, lists, and regular expressions) together to solve different issues. It also shows how we can use Python to work with MeSH files for linking some parts of this complex file in a way that makes it easier to understand its hierarchy and structure, as we did here by linking the MeSH term to its relevant MeSH numbers.
Monday, May 22, 2017
How to be an Online Session Musician: Part 2
In the previous tutorial How To Be An Online Session Musician: Part 1 I looked at what’s involved with this line of work. In this tutorial I’ll show you what you need to get started.
The Studio
It is not necessarily the case that you'll need a purpose-built studio but it does depend on the work you do.
Recording keyboards, for example, don’t require microphones and that means that they can be used almost anywhere.
If microphones are involved, however, such as for work involving acoustic instruments or voice-overs then you’ll need a dedicated space. A dedicated space could be somewhere reasonably isolated and acoustically-treated that will further improve the probability of quality recordings.
The other advantage to having a work-space is efficiency; if everything’s set up, you can get started quickly and get more done.
About Recording and Production
Knowledge is power. The more you know about related topics then the more quickly, productively, and efficiently you will work.
If you don’t know your way around the most basic DAW, then this probably isn’t for you. If you don’t know what DAW refers to, then this definitely isn’t for you just yet, but keep reading, because I'll explain later on.
File Formats
In terms of quality, CD standard is what you’re aiming for as a minimum. In technical terms, this is audio sampled at a rate of 44.1KHz, and to a depth of 16 bits. My default setting is 44.1KHz at 24 bits.
You can use higher settings than this but there’s little to be gained by doing so. Furthermore, the file sizes balloon accordingly and increase subequent upload and download times.
Export work as .wav
files; mp3
is fine for demos but not good enough as a finished product.
Hardware, Quality and Cost
You should consider the sort of hardware you’ll need as a minimum to get started, assuming that you haven’t got some or all of this already. Important for any musician is budget.
‘You get what you pay for’ is an age-old maxim meaning that higher cost implies higher quality. There’s still something to be said for this, but it’s not as true as it used to be.
Companies such as Behringer and SE Electronics produce great gear at very reasonable prices so don’t worry if you can’t afford hideously expensive high-end hardware. Ultimately, what you need is good quality, reliable equipment.
When looking to buy, ensure that you research, read reviews, watch videos and buy the best that falls within your budget. You can always upgrade later.
Here’s a basic, albeit extensive, shopping list.
Laptop or desktop computer
The more powerful the better. Consider the processing power, memory and hard drive. A solid-state drive will be faster and quieter,
Ensure it has a decent set of inputs and outputs. Mac or PC doesn’t matter as much as it used to as most DAWs are now available for both systems.
Storage
Music projects are memory-heavy things, and will eat up your computer’s on-board memory. A 1TB drive may sound huge but you’ll be amazed how quickly you’ll fill it.
Buy an external hard drive to store work there. Not only will it keep space free on the computer, helping to ensure it runs efficiently, it means that if it crashes all of the work is safe on an external drive.
As always, go for the biggest drive you can afford.
Backup
Before a catastrophic failure occurs, and you lose all of the work, consider how you'll protect the work produced.
In addition to my work being stored on an external drive, everything’s backed up to a further separate drive. It’s like insurance, you don’t need it right up until the moment that you do.
Recording Interface
Consider how many inputs you’ll need and then buy the best one you can afford. If you’re using condenser mics ensure it can provide phantom power.
Microphones
The sort of work you do determines the mics you’ll need. For example, use large diaphragm condensers for voice-over work, small diaphragm condensers for acoustic recording and dynamic mics for speaker cabinets.
Monitor Speakers
The speakers in a laptop won’t do the job and neither will in-ear buds. If you want to produce quality work you must be able to hear with clarity what you’re doing. Invest accordingly.
Recording Software
A Digital Audio Workstation (or DAW) is a must. Choose the one that best suits your needs and budget. Here are some examples:
- Logic and Pro Tools are industry-standard, and good all-rounders, but take some learning
- Reaper is well worth considering as a cheaper alternative, and is easier to learn
- If you’re more into producing beats, look at Reason and Ableton
- If you’re just getting started, and/or your budget’s limited, GarageBand is amazing for the price
Plugins
DAWs come bundled with these. If, however, they don’t cover the sort of thing you’ll be doing, invest accordingly.
For example, I do a lot of guitar work, so I use BIAS FX by Positive Grid to provide my sounds. If you’re not sure what to get, web-shops such Plugin Alliance and Plugin Boutique allow you to browse by brand or type.
Internet Connectivity
This sounds like a no-brainer, but it’s a must. And the faster, the better. Though this will depend on your location and local broadband infrastructure.
Online Storage
Some files will exceed the limit of the online session service employed; for example, Fiverr’s limit is 150MB.
Such files are also far too large to email, so an online storage facility is ideal. A way that works is:
- Upload files to online storage
- email a link to the client, and
When the client’s downloaded the files, you can remove them, thus freeing up your account.
I would therefore definitely recommend using Google Drive or Dropbox; better still, their basic accounts are free.
Conclusion
Being an online session musician requires more than just instrumental skill. In summary, you’ll need the following:
- A dedicated workspace
- Recording and production knowledge helps
- Reliable equipment
- A decent computer
- Lots of external storage
- File backup facility
- A suitable recording interface
- Quality monitor speakers
- Microphones for acoustic work
- An appropriate DAW
- Specialist plugins
- Online storage
In the next tutorial, I’ll describe how to deal with clients, managing orders and overseeing the whole process.
How to Use CSS Variables for Animation
When we mention CSS in discussions we often speak of it as a dumbed down language. A declarative language, lacking logic and insight; but that isn’t the true reality. For years developers have been craving variables in standard CSS, having been spoiled by pre-processors such as LESS and Sass for so long. Not only are CSS variables a real and tangible option for developers, they can also be used in animation scenarios. Still skeptical? Follow along to find out more!
Variable Basics
CSS variables are stored values intended for reuse throughout a stylesheet. They’re accessible using the custom var()
function and set using custom property notation.
:root { --main-color: goldenrod; } div { background-color: var(--main-color); }
Variables defined within :root
are global, whereas variables defined within a selector are specific to that selector.
div { --module-color: goldenrod; background-color: var(--module-color); }
In the example above any div
will accept the variable, but we could get more specific with naming using targeting methods such as class
and id
for example.
The var()
function can also accept fallback values too.
.nav { background: var(--navbg, blue); }
This can be useful in situations when a variable isn’t yet defined or when working with custom elements and the Shadow DOM.
CSS variables aren’t quite ready for prime time, but the outlook for the future is very bright with many leading browsers already implementing the spec. It’s only a matter of time until they can be used without any worries, and that time is approaching fast.
Variables for Animation
There are many options for combining CSS variables with animation. Think about contexts such as audio visualizations, JavaScript event-driven scenarios, and even CSS driven events such as hover
, focus
and target
. In theory, an Apple Watch could be connected to an API whilst using a CSS variable-based animation of a beating heart. Then we have accelerometers, device tilt APIs and even gamepad APIs, but why should we consider animating with CSS Variables at all? Here are some reasons:
- Easily debuggable.
- No excessive DOM manipulation.
- DOM node independent.
- Theming
- Works with SVG.
Operations in CSS are really the key part to the whole puzzle with animations. CSS functions such as calc
can accept a value at runtime and execute operators such as multiplication, division, addition, subtraction, mutating values into a new ones. This helps make CSS variables dynamic.
CSS Variables in JavaScript
Now that we know what CSS variables look like and what they can do it’s time to gain an understanding how JavaScript fits into all this.
document.documentElement.style.getPropertyValue('--bgcolor'); document.documentElement.style.setProperty('--bgcolor', 'red'); document.documentElement.style.removeProperty('--bgcolor');
The methods shown above are used to set, get and remove property values. They can be used for our typical CSS properties (background-color
, font-size
etc), but they can also be used for CSS variables too. These options will set a new value for the globally defined property otherwise known as :root
in CSS.
They’re also the true secret to animating with CSS variables because our JS methods can get, set or remove a variable at run-time dynamically!
var element = document.querySelector('div'); element.style.getPropertyValue('--bgcolor'); element.style.setProperty('--bgcolor', 'red'); element.style.removeProperty('--bgcolor');
You can also set a new value for a specific element if desired. In the example snippet above we’re manipulating a variable that is attached to a div
selector versus being attached globally.
Demos In the Wild
There are plenty of awesome and extremely talented developers building and experimenting with these concepts of reactive and theme-based animations using CSS variables. Here are just a few pens to dive into and discover what’s going on under the hood.
Sunset/Sunrise
By David Khourshid.
This example shows the power of CSS variable animations used in a theme-based manner. It would generally be twice as much code to execute this demo without CSS variables and many times more if you desired to exceed two themes.
CSS Variables Animation
By GRAY GHOST.
Here’s another example using mouse events in JavaScript that drive the location of a circle.
Each time you move your mouse the JavaScript updates our declared variables, allowing the circle to move position in relation to your cursor.
Alex the CSS Husky
By David Khourshid.
Here’s the same principle demonstrated above, but used in another context.
Notice what happens when you move your mouse around? Pretty cool huh?
Animation with CSS Variables
By Wes Bos.
How about updating the values of variables in other ways? Let’s take a look at the following demo by Wes Bos using sliders to update the positions of a picture.
Move the sliders around at your leisure. Notice the coolness that ensues? Simple, but simply magical and all it’s doing is updating the variables for the transform position each time the sliders are adjusted. Suuuuhhhweeet!
Single Div Accordion (Animated with CSS Variables)
By Dan Wilson.
How about something a little different for the musicians out there? Check out this hip accordion by Dan Wilson.
Whoa! Watch those keys move! This might seem a bit intimidating to go through, but at the core it’s just JavaScript updating CSS variables. Nothing more, nothing less.
CSS Variables + Transform = Individual Properties (with Inputs)
By Dan Wilson.
In this demo, use the input ranges to modify each transform property and witness how smooth they are even if you change a property mid transition.
Side Effects of CSS Variables
As CSS variables are always inheritable properties, they can cause a style recalculation of children, adversely effecting performance in the process. This will be something you have to measure, as opposed to guessing depending on your context.
Huh, it seems modifying CSS variables on an element triggers a style recalculation for _all_ of its children. Ouch. http://pic.twitter.com/jvpDT5UB2h
— Joni Korpi (@jonikorpi) May 18, 2017
Here’s a demo Shaw posted on the Animation at Work Slack group that was used for testing: CSS Variables Recalc Style Performance Test
During tests (Chrome 58. Mac 10.12) it was discovered that when the Set CSS Var button is triggered until the time the browser paints the background there’s 52.84ms of recalc time and 51.8ms rendering. Switching gears to the CSS Property test a very different result is discovered. From the time the Set CSS Property button is triggered until the background paints there’s roughly 0.43ms of recalc and 0.9ms rendering.
If you switch background
out for color
you’ll get equivalent measurements since currentColor
might or might not exist in the children (shout out to David Khourshid). Any property that is inheritable will always cause a style recalc for any and all children. The property background-color
is not inheritable, hence the tremendous difference with CSS variables which are always inheritable. Typically CSS properties that default to inherit
will in most cases cause a large recalc of styles. It’s still important to note that changing CSS variables continually can be a performance drain. A good practice to avoid this is to animate CSS variables at the most specific level (or deepest level), in order to prevent a multitude of children affected. You can read more about reducing the scope and complexity of style calculations via Google’s Web Fundamentals page.
Conclusion
I encourage you all to dive in and test this demo for yourselves and make your own conclusion/changes/custom tests and share your results in the comments. The main takeaway in all of this is that CSS variables offer huge flexibility, but there will be performance implications for setting CSS variables on a parent with a vast amount children.
Special thanks to the gang in the Animations At Work Slack channel for the ongoing testing, feedback and discussions (David Khourshid, Joni Korpi, Dan Wilson and Shaw).
Resources
- CSS Custom Properties for Cascading Variables Module Level 1 on W3C
- Making Custom Properties (CSS Variables) More Dynamic on CSS Tricks
- Individualizing CSS Properties with CSS Variables by Dan Wilson
- Reactive Animations CSS Variables slides by David Khourshid
- Reactive Animations with CSS Variables - JSConf Iceland 2016 David Khourshid
-
Get variable from inline style by Lea Verou
Sunday, May 21, 2017
Saturday, May 20, 2017
Friday, May 19, 2017
Securing iOS Data at Rest: Protecting the User's Data
This is the first of three articles on securing user data at rest. In this post, we'll start off with the basics of protecting data on iOS so you can learn the current best practices for storing data securely with Swift.
Any app that saves the user's data has to take care of the security and privacy of that data. As we've seen with recent data breaches, there can be very serious consequences for failing to protect your users' stored data. In this tutorial, you'll learn some best practices for protecting your users' data.
Permissions
Before we get into storing your custom data, let's take a look at data that can be shared by system apps.
For many iOS versions, it has been required to request app permissions to use and store some of the user's private data that is external to the app, such as when saving and loading pictures to the photo library. Starting in iOS 10, any APIs that access the user's private data require you to declare that access ahead of time in your project's info.plist file.
There are many frameworks that can access data outside of your app, and each framework has a corresponding privacy key.
- Bluetooth Sharing:
NSBluetoothPeripheralUsageDescription
- Calendar:
NSCalendarsUsageDescription
- CallKit:
NSVoIPUsageDescription
- Camera:
NSCameraUsageDescription
- Contacts:
NSContactsUsageDescription
- Health:
NSHealthShareUsageDescription
,NSHealthUpdateUsageDescription
- HomeKit:
NSHomeKitUsageDescription
- Location:
NSLocationAlwaysUsageDescription
,NSLocationUsageDescription
,NSLocationWhenInUseUsageDescription
- Media Library:
NSAppleMusicUsageDescription
- Microphone:
NSMicrophoneUsageDescription
- Motion:
NSMotionUsageDescription
- Photos:
NSPhotoLibraryUsageDescription
- Reminders:
NSRemindersUsageDescription
- Speech Recognition:
NSSpeechRecognitionUsageDescription
- SiriKit:
NSSiriUsageDescription
- TV Provider:
NSVideoSubscriberAccountUsageDescription
For example, here is an entry in info.plist to allow your app to load and store values to the calendar.
<key>NSCalendarsUsageDescription</key> <string>View and add events to your calendar</string>
If a usage description is missing when the API tries to access the data, the app will simply crash.
The Data Protection API
For any user data that's internal to the app, the first thing to think about is whether you need to store the information, and what data is essential to the app. Keep as much of that essential data in working memory instead of in file storage. This is especially important for any personally identifiable information.
But, if you must store data, it's a good idea to enable Apple's Data Protection.
Data Protection encrypts the contents of your app’s container. It relies on the user having a passcode, and thus the security of the encryption is tied to the strength of the passcode. With Touch ID and the upgraded file system encryption introduced in iOS 10.3, the data protection system has had many improvements. You can enable data protection across your app by turning on Data Protection in the Capabilities section of your project file. This updates your provisioning profile and entitlements file to include the Data Protection capability. Data Protection offers four levels of protection, depicted by the FileProtectionType
structure:
none
: no protection.complete
: data is not accessible while the device is locked. This is the recommended setting for most applications.completeUnlessOpen
: data is accessible when the device is unlocked, and continues to be accessible until the file is closed, even if the user locks the device. Files can also be created when the device is locked. This option is good for when you need to open a file to process and have the process continue even if the user puts the app into the background and locks the device. An example might be a job that uploads a file to a server.completeUntilFirstUserAuthentication
: when the device is booted, files are not accessible until the user first unlocks the device. After that, files are available even when the device is locked again. The option is good for files that need to be accessed sometime later in the background when the device is locked, such as during a background fetch job.
complete
is the default level. To help avoid crashes when your code tries to access data that is locked, you can register for notifications via UIApplicationProtectedDataDidBecomeAvailable
and UIApplicationProtectedDataWillBecomeUnavailable
to find out when the data is available.
NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataDidBecomeAvailable, object: nil, queue: OperationQueue.main, using: { (notification) in //... }) NotificationCenter.default.addObserver(forName: .UIApplicationProtectedDataWillBecomeUnavailable, object: nil, queue: OperationQueue.main, using: { (notification) in //... })
Additionally, you can also check the UIApplication.shared.isProtectedDataAvailable
flag.
One important thing to keep in mind when enabling data protection is that if you are using any background services such as background fetch, that code may need access to your data in the background when the device is locked. For those files, you will need to set a protection level of completeUntilFirstUserAuthentication
. You can control the protection level of each file individually when creating files and directories using the FileManager
class.
let ok = FileManager.default.createFile(atPath: somePath, contents: nil, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete]) do { try FileManager.default.createDirectory(atPath: somePath, withIntermediateDirectories: true, attributes: [FileAttributeKey.protectionKey.rawValue: FileProtectionType.complete]) } catch { print(error) }
You can also set the protection level when you write to a file. The Data
object has a method that can write its data to a file, and you can set the protection level when you call this method.
let data = Data.init() let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somedata.dat") do { try data.write(to: fileURL, options: ([.atomic, .completeFileProtection])) } catch { print(error) }
You can also set the protection level when setting up your Core Data model.
let storeURL = docURL?.appendingPathComponent("Model.sqlite") let storeOptions: [AnyHashable: Any] = [NSPersistentStoreFileProtectionKey: FileProtectionType.complete] do { try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: storeOptions) } catch { print(error) }
To change the protection level of an existing file, use the following:
do { try FileManager.default.setAttributes([FileAttributeKey.protectionKey : FileProtectionType.complete], ofItemAtPath: path) } catch { print(error) }
Data Integrity
Part of protecting your stored data includes checking its integrity. It's good practice not to blindly trust the data you are loading from storage; it may have been accidentally or maliciously altered. The NSSecureCoding
protocol can be used to safely load and save your data objects from storage. It will make sure the objects you load contain the expected data. If you will be saving your own object, you can conform to the secure coding protocol inside your class.
class ArchiveExample : NSObject, NSSecureCoding { var stringExample : String? ...
The class must be inherited from NSObject
. Then, to turn on secure coding, override the supportsSecureCoding
protocol method.
static var supportsSecureCoding : Bool { get { return true } }
If your custom object is deserialized with init?(coder aDecoder: NSCoder)
, the decodeObject(forKey:)
method should be replaced with decodeObject(of:forKey:)
, which makes sure that the correct object types are unpacked from storage.
required init?(coder aDecoder: NSCoder) { stringExample = aDecoder.decodeObject(of: NSString.self, forKey: "string_example") as String? } func encode(with aCoder: NSCoder) { aCoder.encode(stringExample, forKey:"string_example") }
If you are using NSKeyedUnarchiver
to load data from storage, make sure to set its requiresSecureCoding
property.
class func loadFromSavedData() -> ArchiveExample? { var object : ArchiveExample? = nil let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let url = NSURL(fileURLWithPath: path) let fileURL = url.appendingPathComponent("ArchiveExample.plist") if FileManager.default.fileExists(atPath: (fileURL?.path)!) { do { let data = try Data.init(contentsOf: fileURL!) let unarchiver = NSKeyedUnarchiver.init(forReadingWith: data) unarchiver.requiresSecureCoding = true object = unarchiver.decodeObject(of: ArchiveExample.self, forKey: NSKeyedArchiveRootObjectKey) unarchiver.finishDecoding() } catch { print(error) } } return object; }
Turning on secure coding for your save operations will prevent you from accidentally archiving an object that does not adhere to the secure coding protocol.
func save() { let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let url = NSURL(fileURLWithPath: path) let filePath = url.appendingPathComponent("ArchiveExample.plist")?.path let data = NSMutableData.init() let archiver = NSKeyedArchiver.init(forWritingWith: data) archiver.requiresSecureCoding = true archiver.encode(self, forKey: NSKeyedArchiveRootObjectKey) archiver.finishEncoding() let options : NSData.WritingOptions = [.atomic, .completeFileProtection] do { try data.write(toFile: filePath!, options:options) } catch { print(error) } }
Beyond NSSecureCoding
, it’s always good to implement your own data validation checks upon unpacking any archive or receiving any arbitrary input in general.
Data Trails
As iOS continues to evolve, there are always new features that have the potential to leak stored data. Starting in iOS 9, you can have your content indexed in the Spotlight search, and on iOS 10 you can expose your content to Widgets such as the Today Widget that shows up on the lock screen. Use caution if you would like to expose your content with these new features. You might end up sharing more than you planned to!
iOS 10 also adds a new Handoff feature where your copied pasteboard data is automatically shared between devices. Again, be careful not to expose any sensitive data in the pasteboard to Handoff. You can do this by marking the sensitive content as localOnly
. You can also set an expiry date and time for the data.
let stringToCopy = "copy me to pasteboard" let pasteboard = UIPasteboard.general if #available(iOS 10, *) { let tomorrow = Date().addingTimeInterval(60 * 60 * 24) pasteboard.setItems([[kUTTypeUTF8PlainText as String : stringToCopy]], options: [UIPasteboardOption.localOnly : true, UIPasteboardOption.expirationDate: tomorrow]) } else { pasteboard.string = stringToCopy }
Files that are saved to the device's storage can automatically get backed up, either in iTunes or in iCloud. Even though backups can be encrypted, it's a good idea to exclude any sensitive files that don't even need to leave the device. This can be done by setting the isExcludedFromBackup
flag on the file.
let path: String = ... var url = URL(fileURLWithPath: path) do { var resourceValues = URLResourceValues() //or if you want to first check the flag: //var resourceValues = try url.resourceValues(forKeys: [.isExcludedFromBackupKey]) resourceValues.isExcludedFromBackup = true; try url.setResourceValues(resourceValues) } catch { print(error) }
The animation that happens when putting an app into the background is achieved by iOS taking a screenshot of your app which it then uses for the animation. When you look at the list of open apps on the app switcher, this screenshot is used there too. The screenshot gets stored on the device.
It's a good idea to hide any views revealing sensitive data so that the data isn't captured in the screenshot. To do this, set up a notification when the application is going to the background and set the hidden property for the UI elements you want to exclude. They will be hidden before iOS captures the screen. Then when coming to the foreground, you can unhide the UI elements.
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
Remove your notifications when the view disappears.
NotificationCenter.default.removeObserver(self, name: .UIApplicationDidEnterBackground, object: nil) NotificationCenter.default.removeObserver(self, name: .UIApplicationWillEnterForeground, object: nil)
Your app also has a keyboard cache for text fields that have auto-correct enabled. Text that the user types, along with newly learned words, are stored in the cache so that it is possible to retrieve various words that the user has previously entered in your application. The only way to disable the keyboard cache is to turn off the auto-correct option.
textField.autocorrectionType = UITextAutocorrectionType.no
You should mark password fields as secure text entry. Secure text fields don't display the password or use the keyboard cache.
textField.isSecureTextEntry = true
Debug logs are saved to a file and could be retrieved for production builds of your app. Even when you're coding and debugging your app, make sure not to log sensitive information such as passwords and keys to the console. You might forget to remove that information from the logs before submitting your code to the app store! While debugging, it's safer instead to use a breakpoint to view sensitive variables.
Network connections may also get cached to storage. More information about removing and disabling the network cache can be found in the article Securing Communications on iOS.
Destroying Data
You may already know that when a file on a computer is deleted, often the file itself is not removed; only the reference for the file is removed. To actually remove the file, you can overwrite the file with random data before removing it.
The switch to solid state drives has made it hard to guarantee the data has been destroyed, and the best way to securely delete data is open to debate. However, this tutorial would not be complete without an example of how to wipe data from storage. Because of some other debates about the Swift optimizer, and because we hope to guarantee that each byte of the file is actually being overwritten, we are implementing this function in C.
The implementation below can go inside a .c file. You will need to add the function definition or the file that contains the function into your bridging header in order to use the function from Swift. You may then want to call this function right before places where you use FileManager
's removeFile
methods. Perhaps you may want to implement the best practices described in this and the upcoming tutorials on an app update. You could then wipe the previous unprotected data during migration.
#import <string.h> #import <sys/stat.h> #import <unistd.h> #import <errno.h> #import <fcntl.h> #import <stdio.h> #define MY_MIN(a, b) (((a) < (b)) ? (a) : (b)) int SecureWipeFile(const char *filePath) { int lastStatus = -1; for (int pass = 1; pass < 4; pass++) { //setup local vars int fileHandleInt = open(filePath, O_RDWR); struct stat stats; unsigned char charBuffer[1024]; //if can open file if (fileHandleInt >= 0) { //get file descriptors int result = fstat(fileHandleInt, &stats); if (result == 0) { switch (pass) { //DOD 5220.22-M implementation states that we write over with three passes first with 10101010, 01010101 and then the third with random data case 1: //write over with 10101010 memset(charBuffer, 0x55, sizeof(charBuffer)); break; case 2: //write over with 01010101 memset(charBuffer, 0xAA, sizeof(charBuffer)); break; case 3: //write over with arc4random for (unsigned long i = 0; i < sizeof(charBuffer); ++i) { charBuffer[i] = arc4random() % 255; } break; default: //at least write over with random data for (unsigned long i = 0; i < sizeof(charBuffer); ++i) { charBuffer[i] = arc4random() % 255; } break; } //get file size in bytes off_t fileSizeInBytes = stats.st_size; //rewrite every byte of the file ssize_t numberOfBytesWritten; for ( ; fileSizeInBytes; fileSizeInBytes -= numberOfBytesWritten) { //write bytes from the buffer into the file numberOfBytesWritten = write(fileHandleInt, charBuffer, MY_MIN((size_t)fileSizeInBytes, sizeof(charBuffer))); } //close the file lastStatus = close(fileHandleInt); } } } return lastStatus; }
Conclusion
In this article, you have learned about setting permissions for the data that your app has access to, as well as how to ensure basic file protection and integrity. We also looked at some ways that user data could be leaked accidentally from your app. Your users put their trust in you to protect their data. Following these best practices will help you repay that confidence.
While you're here, check out some of our other posts on iOS app development!
-
iOS SDKSecuring Communications on iOSCollin Stuart
-
Mobile DevelopmentBack-End as a Service for Mobile AppsBala Durage Sandamal Siripathi
-
iOS SDKThe Right Way to Share State Between Swift View ControllersMatteo Manferdini
-
SwiftSwift From Scratch: ClosuresBart Jacobs
Thursday, May 18, 2017
Wednesday, May 17, 2017
Quick Tip: Enumerations in Swift
Enumerations are a common design pattern in many programming languages. While you may be familiar with enumerations in C and Objective-C, Swift's implementation of enumerations is significantly more powerful and flexible. In this quick tip, you'll learn what's special about enumerations in Swift, how to use them in your projects, and what makes them so powerful.
1. What Is an Enumeration?
Enumerations aren't new and they're certainly not unique to Swift. However, if you're familiar with enumerations in C, then you're going to love Swift's powerful take on enumerations.
If enums or enumerations are new to you, then you may not be familiar with what they have to offer. In Swift, enumerations are first class types that define a list of possible values for that type.
An example might be the possible states of a network connection. The possible states could be:
- disconnected
- connecting
- connected
We could add a fourth state for the case the state is unknown. With this example in mind, let's see how to define and implement such an enumeration.
Basics
Like I said, enumerations are first class types in Swift. An enumeration definition looks very similar to a class or structure definition. In the example below, we define the ConnectionState
enumeration.
enum ConnectionState { }
The name of the enumeration is preceded by the enum
keyword and followed by a pair of curly braces. The ConnectionState
enumeration will define the possible states of a network connection. To define these states, we add member values or members to the enumeration's definition. The definition of a member value always starts with the case
keyword.
enum ConnectionState { case Unknown case Disconnected case Connecting case Connected }
In C or Objective-C, the above enumeration would look a bit different as illustrated in the example below. Each value of the enumeration corresponds with an integer, for example, ConnectionStateUnknown
equals 0
, ConnectionStateDisconnected
equals 1
, etc.
typedef enum : NSUInteger { ConnectionStateUnknown, ConnectionStateDisconnected, ConnectionStateConnecting, ConnectionStateConnected } ConnectionState;
This isn't true in Swift. The members of an enumeration don't automatically correspond with an integer value. The members of the ConnectionState
enumeration are values themselves and they are of type ConnectionState
. This makes working with enumerations safer and more explicit.
Raw Values
It is possible to explicitly specify the values of the members of an enumeration. In the following example, the members of the ConnectionState
enumeration have a raw value of type Int
. Each member is assigned a raw value, corresponding with an integer.
enum ConnectionState: Int { case Unknown = -1 case Disconnected = 0 case Connecting = 1 case Connected = 2 }
Note that we specify the type of the raw values in the enumeration's definition and that no two member values can have the same raw value. If we only specify a value for the Unknown
member, then Swift will automatically increment the value of the Unknown
member and assign unique values to the other members of the enumeration. To better illustrate this, the below example is identical to the previous definition of the ConnectionState
enumeration.
enum ConnectionState: Int { case Unknown = -1 case Disconnected case Connecting case Connected }
2. Working with Enumerations
Initialization
Using the ConnectionState
enumeration is similar to using any other type in Swift. In the next example, we declare a variable, connectionState
, and set its value to ConnectionState.Connecting
.
var connectionState = ConnectionState.Connecting
The value of connectionState
is ConnectionState.Connecting
and the variable is of type ConnectionState
.
Swift's type inference is very convenient when working with enumerations. Because we declared connectionState
as being of type ConnectionState
, we can now assign a new value by using the shorthand dot syntax for enumerations.
connectionState = .Connected
Control Flow
Using enumerations in an if
or switch
statement is straightforward. Remember that switch
statements need to be exhaustive. Add a default
case if necessary.
enum ConnectionState { case Unknown case Disconnected case Connecting case Connected } var connectionState = ConnectionState.Connecting connectionState = .Connected switch connectionState { case .Disconnected: println("Disconnected") case .Connecting: println("Connecting") case .Connected: println("Connected") default: println("Unknown State") }
The following example demonstrates how the ConnectionState
enum can be used. It also shows how to access the associated value of an enum member. The canConnect
function accepts a ConnectionState
instance and returns a Bool
.
func canConnect(connectionState: ConnectionState) -> Bool { var result = false switch connectionState { case .Connected(let port): if port == 3000 { result = true } default: result = false } return result } let state = ConnectionState.Connected(3000) if canConnect(state) { // ... }
The canConnect
function only returns true
if the ConnectionState
instance passed to the function is equal to .Connected
and its associated value is an Int
equal to 3000
. Note that the associated value of the Connected
member is available in the switch
statement as a constant named port
, which we can then use in the corresponding case
.
3. Associated Values
Another compelling feature of Swift enums are associated values. Each member of an enum can have an associated value. Associated values are very flexible. For example, associated values of different members of the same enum don't need to be of the same type. Take a look at the following example to better understand the concept of associated values.
enum ConnectionState { case Unknown case Disconnected case Connecting(Int, Double) case Connected(Int) }
The Unknown
and Disconnected
members don't have an associated value. TheConnecting
member has an associated value of type (Int, Double)
, specifying the port number and timeout interval of the connection. The Connected
member has an associated value of type Int
, specifying the port number.
It's important to understand that an associated value is linked to or associated with a member of the enumeration. The member's value remains unchanged. The next example illustrates how to create a ConnectionState
instance with an associated value.
var connectionState = ConnectionState.Connecting(3000, 30.0)
4. Methods and Value Types
Methods
Enumerations are pretty powerful in Swift. Enumerations can even define methods, such as an initializer to select a default member value if none was specified.
enum ConnectionState { case Unknown case Disconnected case Connecting(Int, Double) case Connected(Int) init () { self = .Unknown } } var connectionState = ConnectionState() // .Unknown
In this example, we initialize an instance of the ConnectionState
enumeration without explicitly specifying a value for it. In the initializer of the enumeration, however, we set the instance to Unknown
. The result is that the connectionState
variable is equal to ConnectionState.Unknown
.
Value Types
Like structures, enumerations are value types, which means that an enumeration is not passed by reference, like class instances, but by value. The following example illustrates this.
var connectionState1 = ConnectionState() var connectionState2 = connectionState1 connectionState1 = .Connected(1000) println(connectionState1) // .Connected(1000) println(connectionState2) // .Unknown
Even though we assign connectionState1
to connectionState2
, the values of connectionState1
and connectionState2
are different at the end of the example.
When connectionState1
is assigned to connectionState2
, Swift creates a copy of connectionState1
and assigns that to connectionState2
. In other words, connectionState1
and connectionState2
refer to two different ConnectionState
instances.
Conclusion
Enums in Swift are incredibly powerful compared to, for example, enums in C. One of the most powerful aspects of enumerations is that they are a first class types in Swift. Type safety is a key aspect of the Swift language and enumerations fit perfectly in that mindset.
Tuesday, May 16, 2017
Monday, May 15, 2017
Sunday, May 14, 2017
Saturday, May 13, 2017
Friday, May 12, 2017
How to Work With Session Data in CodeIgniter
As a CodeIgniter developer, it's really important for you to understand how to work with the core session library. Of course, you could always use the default $_SESSION
syntax, but it's always recommended to use the wrapper instead.
Starting with how to load a session library, we’ll move to the discussion of how to add, retrieve, remove and destroy session variables. In the last segment, we’ll have a look at the different built-in session drivers at your disposal provided by the CodeIgniter framework itself.
How to Load a Session Library
If you want to work with sessions in CodeIgniter, the first thing you'll need is a built-in session library. Unless and until you develop a web application that doesn't require sessions at all, you shouldn't bother about the session library. While that's not the case most of the time, you can autoload the session library in CodeIgniter so that it enables session handling features for every web request.
Go ahead and open the file located at application/config/autoload.php
. Find the following section.
/* | ------------------------------------------------------------------- | Auto-load Libraries | ------------------------------------------------------------------- | These are the classes located in system/libraries/ or your | application/libraries/ directory, with the addition of the | 'database' library, which is somewhat of a special case. | | Prototype: | | $autoload['libraries'] = array('database', 'email', 'session'); | | You can also supply an alternative library name to be assigned | in the controller: | | $autoload['libraries'] = array('user_agent' => 'ua'); */ $autoload['libraries'] = array();
The $autoload['libraries']
array holds the list of libraries that need to be autoloaded. As per our requirement, let's change it to look like this:
$autoload['libraries'] = array('session');
Also, there's another way you could have achieved that. You can use the following code somewhere in your controller file to load the session library.
$this->load->library('session');
That's pretty much it as far as initialization of the session library is concerned.
In the next couple of sections, we'll go through the different operations that you can do with the core session library. To demonstrate it, we'll build an example controller file that loads the session library and provides methods that will be discussed throughout this article.
Go ahead and create a file application/controllers/Example.php
with the following contents.
<?php defined('BASEPATH') OR exit('No direct script access allowed'); class Example extends CI_Controller { public function __construct() { parent::__construct(); // load Session Library $this->load->library('session'); // load url helper $this->load->helper('url'); } public function index() { /**** SET SESSION DATA ****/ // set single item in session $this->session->set_userdata('favourite_website', 'http://tutsplus.com'); // set array of items in session $arraydata = array( 'author_name' => 'Sajal Soni', 'website' => 'http://ift.tt/1cWAN18', 'twitter_id' => '@sajalsoni', 'interests' => array('tennis', 'travelling') ); $this->session->set_userdata($arraydata); /**** GET SESSION DATA ****/ // get data from session echo "Favourite Website: ". $this->session->userdata('favourite_website'); echo "<br>"; echo "Author Name: ". $this->session->userdata('author_name'); echo "<br>"; echo "Interest (Array Example): " . $this->session->userdata('interests')[0]; echo "<br>"; // get e'thing stored in session at once echo '<pre>'; print_r($this->session->userdata()); /**** REMOVE SESSION DATA ****/ // unset specific key from session $this->session->unset_userdata('favourite_website'); // unset multiple items at once $keys = array('twitter_id', 'interests'); $this->session->unset_userdata($keys); echo '<pre>'; print_r($this->session->userdata()); } public function setflash() { // set flash data $this->session->set_flashdata('flash_welcome', 'Hey, welcome to the site!'); // mark existing data as flash data $this->session->set_userdata('flash_message', 'I am flash message!'); $this->session->mark_as_flash('flash_message'); redirect('example/getflash'); } public function getflash() { // get flash data echo "Flash welcome message: ". $this->session->flashdata('flash_welcome'); echo '<pre>'; print_r($this->session->flashdata()); } public function tempdata() { // set temp data $this->session->set_tempdata('coupon_code', 'XYEceQ!', 300); // mark existing data as temp data $this->session->set_userdata('coupon_code', 'XYEceQ!'); $this->session->mark_as_temp('coupon_code', 300); // get temp data echo $this->session->tempdata('coupon_code'); } public function destroy() { $this->session->set_userdata('favourite_website', 'http://tutsplus.com'); // destory session $this->session->sess_destroy(); } }
That's a very basic controller file you should be familiar with as a CodeIgniter developer. Now, we're ready to move to the next couple of sections that provide insight into the session handling concepts.
How to Add, Retrieve and Remove Session Data
To start with, let's fetch the code of our __construct
method.
public function __construct() { parent::__construct(); // load Session Library $this->load->library('session'); // load url helper $this->load->helper('url'); }
Just in case you haven't autoloaded the session library, it'll do that in the first place. Apart from that, we've also loaded the url
helper that allows us to use certain utility methods that we'll see later.
Next, grab the code of the index
method.
public function index() { /**** SET SESSION DATA ****/ // set single item in session $this->session->set_userdata('favourite_website', 'http://tutsplus.com'); // set array of items in session $arraydata = array( 'author_name' => 'Sajal Soni', 'website' => 'http://ift.tt/1cWAN18', 'twitter_id' => '@sajalsoni', 'interests' => array('tennis', 'travelling') ); $this->session->set_userdata($arraydata); /**** GET SESSION DATA ****/ // get data from session echo "Favourite Website: ". $this->session->userdata('favourite_website'); echo "<br>"; echo "Author Name: ". $this->session->userdata('author_name'); echo "<br>"; echo "Interest (Array Example): " . $this->session->userdata('interests')[0]; echo "<br>"; // get e'thing stored in session at once echo '<pre>'; print_r($this->session->userdata()); /**** REMOVE SESSION DATA ****/ // unset specific key from session $this->session->unset_userdata('favourite_website'); // unset multiple items at once $keys = array('twitter_id', 'interests'); $this->session->unset_userdata($keys); echo '<pre>'; print_r($this->session->userdata()); }
As you've loaded the session library already, you can use $this->session
to access the session object and access the methods that are supported. The set_userdata
method is used to create a new session variable, and generally it takes two arguments—key and value.
$this->session->set_userdata('favourite_website', 'http://tutsplus.com');
You can also use the set_userdata
method to create multiple variables in a single call. In that case, you just need to provide one argument, and it should be an array as shown below.
// set array of items in session $arraydata = array( 'author_name' => 'Sajal Soni', 'website' => 'http://ift.tt/1cWAN18', 'twitter_id' => '@sajalsoni', 'interests' => array('tennis', 'travelling') ); $this->session->set_userdata($arraydata);
Users with sharp eyes would have noticed that you could also assign an array as a value of any session variable, as shown above in the interests
example.
Now, let's see how to retrieve the value of any session variable. The userdata
method is used to retrieve the value of any session variable, and usually it needs the key of the session variable that you're looking for as the first argument.
echo "Favourite Website: ". $this->session->userdata('favourite_website');
If you're looking for one of the array entries, you can use the following:
echo "Interest (Array Example): " . $this->session->userdata('interests')[0];
More often than not, you want to know how many variables in total are stored in an active session for debugging purposes, and you can do that as well.
// get e'thing stored in session at once echo '<pre>'; print_r($this->session->userdata());
Yes, the same userdata
method comes to our rescue! If you don't pass any argument to the userdata
method, it'll return all the session variables.
Finally, let's see how you can remove variables from the session. It's the unset_userdata
method that you can use should you want to remove any session entries.
// unset specific key from session $this->session->unset_userdata('favourite_website');
And here's the variation of the same method that shows how to remove multiple entries in a single go.
// unset multiple items at once $keys = array('twitter_id', 'interests'); $this->session->unset_userdata($keys);
And that should delete the twitter_id
and interests
entries from the session.
Go ahead and test the index
method to see things in action.
Useful Goodies: Flashdata and Tempdata
In the last section, we discussed the basics of session handling in CodeIgniter. In this section, we'll discuss couple of other utility methods provided by the session library.
In your day-to-day development, you often need to display messages in response to certain user actions. As an example, you want to display a success message when someone posts a comment on your site, and the message should be displayed only once. The set_flashdata
method is a perfect candidate for this kind of use case.
In fact, set_flashdata
is very similar to the set_userdata
method in that it allows you to save a value in session. The only exception is that the session value set by the flashdata
method is available for the next request only. In subsequent requests, you won't be able to access these variables anymore as they were cleared.
Grab the code of the setflash
method.
public function setflash() { // set flash data $this->session->set_flashdata('flash_welcome', 'Hey, welcome to the site!'); // mark existing data as flash data $this->session->set_userdata('flash_message', 'I am flash message!'); $this->session->mark_as_flash('flash_message'); redirect('example/getflash'); }
You can create a new flashdata
variable in the same way you would have created a regular session variable using the set_userdata
method. On the other hand, you can also mark an existing session variable as a flashdata
variable. In that case, you need to use the mark_as_flash
method, as shown in the above code.
Finally, we redirect the user to the getflash
method that shows how to use flashdata variables that were set in the setflash
method. Let's have a quick look at the getflash
method.
public function getflash() { // get flash data echo "Flash welcome message: ". $this->session->flashdata('flash_welcome'); echo '<pre>'; print_r($this->session->flashdata()); }
As expected, there's a flashdata
method that allows you to fetch session variables stored as flashdata. If you call the flashdata
method without any arguments, it'll return all flashdata variables similar to that of the userdata
method.
Go ahead and test the setflash
method. You'll be redirected to the getflash URL, and you'll see the message. If you refresh the getflash page, you won't see that message again!
Next, there’s another variation provided by the session library in this category—the tempdata session variables. If you want to create session variables for a specific time period, the set_tempdata
method is the one you’re looking for.
For example, if you want to create a session variable that should be automatically deleted after a certain time period, you can use the set_tempdata
method to create such a variable, as shown in the following method.
public function tempdata() { // set temp data $this->session->set_tempdata('coupon_code', 'XYEceQ!', 300); // mark existing data as temp data $this->session->set_userdata('coupon_code', 'XYEceQ!'); $this->session->mark_as_temp('coupon_code', 300); // get temp data echo $this->session->tempdata('coupon_code'); }
The third argument in the set_tempdata
method indicates the number of seconds after which the variable will be deleted from the session.
You can also mark an existing session variable as tempdata using the mark_as_temp
method provided that you’ve already created a session variable using the set_userdata
method.
Finally, you can use the tempdata
method to fetch the value of any tempdata variable.
So it’s nice to have such utility methods at your disposal in your day-to-day development lifecycle!
What You Should Not Forget: Session Destroy
Destroying the session is probably the last thing you would like to do when the user logs out. It makes sure that the session variables set so far are deleted from the active session and are no longer available for subsequent requests.
Let’s pull in the code of the destroy
method and go through it.
public function destroy() { $this->session->set_userdata('favourite_website', 'http://tutsplus.com'); // destroy session $this->session->sess_destroy(); }
It’s the sess_destroy
method that helps us to destroy the active session. Of course, it’ll also delete tempdata and flashdata variables that were set in the active session.
Make sure you get yourself into the habit of destroying a session once it’s no longer useful in the current user context.
Cherry on the Top: Session Drivers
We’re into the last section of this article—the session drivers. More often than not, you don’t bother about configuring the session driver in your application as the default session driver, the file system, is configured already with the default setup.
So it’s the file system that holds all session-related data, and it’s widely used and is the accepted standard for session handling. Having said that, CodeIgniter also supports other session drivers that you could use should you wish to switch from the default file system session driver.
Here’s a list of all supported session drivers in CodeIgniter:
- Files
- Database
- Redis
- Memcached
The database session driver, as the name suggests, stores the session data in the database that you’ve configured for your CodeIgniter application.
On the other hand, the other two session drivers are in-memory storage mechanisms preferred for high-performance websites.
In the application/config/config.php
file, you can configure the session driver that you would like to use in your application.
$config['sess_driver'] = 'database'; $config['sess_save_path'] = 'custom_sessions';
It tells CodeIgniter to use the database session driver, and the session data will be saved in the custom_sessions
MySQL table.
Discussion of each and every session driver is beyond the scope of this article, but you can go through the official site documentation that provides an in-depth guide for each driver.
Conclusion
Session handling in CodeIgniter was the topic of today's tutorial, and we discussed it thoroughly by looking at every aspect of the subject.
Starting with basic session operations, we also went through those cool flashdata and tempdata methods, and it was the discussion of session drivers that concluded our article.
As always, you could shout out your queries and suggestions using the feed below!