Category: Swift

Swift: how to create a Singleton pattern

What is a Singleton?

A singleton pattern guarantees that only one instance of a class is initialized and available from different points of an app.

Some examples are already available in Apple’s frameworks:

// Shared URL Session
let sharedURLSession = URLSession.shared

// Default File Manager
let defaultFileManager = FileManager.default

// Standard User Defaults
let standardUserDefaults = UserDefaults.standard

How to define a Singleton

Often a static constant is used to adopt the Singleton pattern. To do that the reference to the shared instance is stored inside a static constant. In addition, the initializer is private and therefore hidden:

class NetworkManager {
    static let sharedInstance = NetworkManager()

    private init() { }

    func doSomething() {
        // ...
    }
}

By making the initializer method private, it’s guaranteed that no other instance of this class can be created. It would also be possible to move the static constant outside the class, but then the initializer might be accessible and that’s not what we want.

To access our NetworkManager instance just call:

NetworkManager.sharedInstance.doSomething()
NetworkManager.sharedInstance.doSomething()

This approach allows to use always the same instance, even in different points of the app.

 

Xcode fails to generate source files from intent definition files when using the Legacy Build System

The workaround for this problem is the following: First, add a Run Script phase before the Compile Sources phase of your target:

xcrun intentbuilderc generate -input ${SRCROOT}/PATH/TO/Intents.intentdefinition -output ${SRCROOT}/Intents -classPrefix "" -language Swift -swiftVersion 5.0

Then, add all of the generated files from the output path specified in the command above to all required targets in your project.

Source: https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_release_notes

 

Background tasks in iOS

As already discussed in Background task in iOS action extension, it sometimes becomes necessary to perform time consuming tasks in the background. This is really important, if such a task would block the user interaction. Especially for action and share extensions, this might lead to some waiting time before a task completes and the extension disappears. After several attempts to realise a working solution, the following code help to extends an App’s background execution time.

This code was described in the article Extending Your App’s Background Execution Time.

Extending the background execution time in an app

func performTask()
{
   // Perform the task on a background queue.
   DispatchQueue.global().async {
      // Request the task assertion and save the ID.
      self.backgroundTaskID = UIApplication.shared.
                 beginBackgroundTask (withName: "Perform Long Task") {
         // End the task if time expires.
         UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
         self.backgroundTaskID = UIBackgroundTaskInvalid
      }
            
      // Run task synchronously.
      self.performLongTask()
            
      // End the task assertion.
      UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
      self.backgroundTaskID = UIBackgroundTaskInvalid
   }
}

Extending the background execution time in an extension

func performTask()
{
   // Perform the task in background.
   let processinfo = ProcessInfo()
   processinfo.performExpiringActivity(withReason: "Long task") { (expired) in
      if (!expired) {
         // Run task synchronously.
         self.performLongTask()
      }
      else {
         // Cancel task.
         self.cancelLongTask()
      }
   }
}

As mentioned in the documentation, the ProcessInfo code block is called a second time if the system need to suspend the process:

If your block is still executing and the system need to suspend the process, the system executes your block a second time with the expired parameter set to true. Your block must be prepared to handle this case. When the expired parameter is true, stop any in-progress tasks as quickly as possible.

Source: https://developer.apple.com/documentation/foundation/processinfo/1617030-performexpiringactivity

Important: it’s important that the tasks are executed synchronously. When the end of the block is reached, the thread will terminate and end the execution of your task. If your tasks are asynchron, you can use a loop like shown below to wait inside the background thread until the asynchron task is finished:

// Keep background thread alive until asynchron task ends.
repeat {
    sleep(1)
} while(taskIsRunning)

General thoughts on using background tasks in iOS

A good summary of background tasks is available at https://forums.developer.apple.com/thread/85066


Photo by Jonathan Borba on Unsplash.

Swift: Audioaufnahmen mit AVAudioRecorder

In Swift lassen sich Audioaufnahmen sehr komfortabel und einfach über die Klasse AVAudioRecorder realisieren. Beim Initialisieren der Klasse lassen sich bereits Codec, Samplerate, Anzahl der Kanäle und einige andere wichtige Einstellungen festlegen. Hier ein einfaches Code-Beispiel:

class AudioViewController : UIViewController, AVAudioRecorderDelegate
{
    // [...]
 
    var audioRecorder: AVAudioRecorder!
     
    func initAudioRecorder()
    {
        let settings: [String : Any] = [
            AVSampleRateKey: 16000.0,
            AVFormatIDKey: kAudioFormatMPEG4AAC,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.low.rawValue
        ]
         
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(AVAudioSessionCategoryRecord)
            try audioRecorder = AVAudioRecorder(url: getFileurl()!, settings: settings)
            try audioSession.setActive(true)
        }
        catch let error as NSError {
            print("AVAudioRecorder error: \(error.localizedDescription)")
        }
     
        audioRecorder.delegate = self
        audioRecorder.isMeteringEnabled = true
        audioRecorder.prepareToRecord()
         
        let displayLink:CADisplayLink = CADisplayLink(target: self, selector: #selector(updateMeters))
        displayLink.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
    }
     
    func updateMeters()
    {
        audioRecorder.updateMeters()
         
        if audioRecorder.isRecording
        {
            let time = NSInteger(audioRecorder.currentTime)
            // ...
        }
 
        // TOOD: do any updates e.g. for the UI (update displayed record time, ...)
    }
 
    @IBAction func startRecord(_ sender: AnyObject)
    {
        audioRecorder.record()
    }
 
    @IBAction func stopRecord(_ sender: AnyObject)
    {
        audioRecorder.stop()
    }
     
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)
    {
        // Add audio to attachments
        let audioData = try? Data(contentsOf: getFileurl()!)
        if audioData != nil
        {
            // TOOD: handle audio data
        }
    }
     
    func getFileurl() -> URL?
    {
        let fileManager = FileManager.default
        let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
        let documentDirectory = urls[0] as URL
        let audioURL = documentDirectory.appendingPathComponent("audio.m4a")
         
        return audioURL
    }
}