Understanding Optionals in Swift when creating AVAudioPlayers - ios

I'm new to Swift, making the transition from Obj-C and not having much experience of other languages.
I've read up about optionals, understand the overall premise and sort of get why they might be a good thing. But I'm struggling to grasp how they work in practice. The following code works fine, but I'd like an explanation of why it is the way it is.
I'm making an app that plays audio from a file using AVAudioPlayer. I'm firstly creating an instance of the AVAudioPlayer class:
var player: AVAudioPlayer?
Q1. Why does this have to be an optional type? Presumably because it has yet to be initialised / has yet to contain any data?
The code I am then using to load audio into player is as follows:
guard let audioFile = Bundle.main.path(forResource: "sample-audio", ofType: "caf") else {
print("sample-audio.caf does not exist in the bundle.")
return
}
let audioURL = URL(fileURLWithPath: audioFile)
do {
player = try AVAudioPlayer(contentsOf: audioURL)
player?.delegate = self
player?.prepareToPlay()
} catch {
print(error)
}
Q2. If player is an optional type, why is the ? symbol omitted here when assigning the URL to it using the try/catch statement?
Q3. If an optional is like a "box you have to unwrap", containing either a value or nothing/nil, then how is it possible to directly call properties and functions on it such as player?.delegate and player?.prepareToPlay()
Sorry, three questions, but interrelated…
Answers will really help me get my head around this fundamental aspect of Swift.

Q1: It doesn't have to be optional, but yes it hasn't been initialized at that point. You have several options: ? and initialize it when you need to, ! and you must initialize it before you try to use it or else your program will crash, or initialize it at the point of declaration. What you do depends entirely on the context. In this particular case it's probably good practice to have it optional.
Q2: There's no need for a ? on that line. Where would it go? The compiler knows player is optional, and nothing is being unwrapped, you're just assigning a value to your optional variable.
Q3: Optional chaining allows you to call things on optional objects without actually unwrapping them first. It just implicitly does that work for you so your code can be much more concise. If the object is nil nothing happens, otherwise it will run the call. Note that it cascades the optionality down, so even if the property etc is not optional, it will inherit the optionality in a statement like that.

do-catch process always related to optional suppose you declared the player like this
var player: AVAudioPlayer!
and the do block fails , it's may be other parts of the VC uses player , so once it's referenced the app will crash because of ! , that's why it's declared ?
//
what does it means ? here
player?.delegate = self
simply if player is nil the statement will not run , and if not then the delegate will be set
//
You don't do
player? = //////
because it meaningless and will cause the statement not to be run , as the key behind it is calling properties and methods of player if it's not nil and here there is no thing to attach or call

A1: Yes because it has yet to be initialised / has yet to contain any data. But in this case – the property is going to be initialized always before being used – you can declare it as implicit unwrapped optional AVAudioPlayer! and get rid of the question marks. In this particular case you can even use a non-optional with the default initializer of AVAudioPlayer but this does not work with any type.
var player = AVAudioPlayer()
A2: You can assign both non-optional and optional values to an optional variable. The ? is only relevant when you are sending messages from the variable.
A3: player?.delegate = self is called Optional chaining. If player is nil then the rest of the line is being ignored.
Since the audio file must exist because the application bundle is not mutable at runtime you can omit all guards and catchs. The code must not crash. If it does you made a design mistake.
var player = AVAudioPlayer()
let audioURL = Bundle.main.url(forResource: "sample-audio", withExtension: "caf")!
player = try! AVAudioPlayer(contentsOf: audioURL)
player.prepareToPlay()

Related

Override Local Data in Siesta?

I'm having trouble successfully setting local data for Siesta in Swift. My goal is to set a UIImage for a URL locally, so that this local image can be displayed with no download time.
To do this, I'm setting the image data for the URL as such:
let resource = CustomRemoteImageView.imageCache.resource(myPhoto.url.absoluteString)
let imageData = UIImagePNGRepresentation(image)! // I've also tried putting the UIImage directly in there, because the transformation chain doesn't apply to local data, right?
let entity: Entity<Any> = Entity(content: imageData, contentType: "*/*") // I've played around with the content type too!
resource.overrideLocalData(with: entity)
I'm then using a custom Service that always tries to parse content as an Image:
private let imageTransformer =
ResponseContentTransformer
{ Image(data: $0.content)}
convenience init() {
self.init(standardTransformers: [])
configure {
$0.pipeline[PipelineStageKey.parsing].add(self.imageTransformer, contentTypes: ["*/*"])
}
}
This system is working great for all remote images, but it always seems to fail to parse this overridden local image. It seems like it's trying to parse but just fails every time.
i.e. I'm getting a Siesta.ResourceEvent of
(Siesta.ResourceEvent) $R20 = newData {
newData = network
}
but the actual .typedContent is nil.
overrideLocalData and overrideLocalContent do not interact with the pipeline at all. Siesta won’t try to parse what you pass; what you override is what your resource gets.
Furthermore, overrideLocalData and overrideLocalContent don’t fail. They always update the resource’s content. If you call those methods, the resource content will match what you passed.
So … the problem isn’t parsing. What might it be?
Entity.typedContent is a shortcut for applying as? to a resource’s entity’s content. If you're getting nil, it means that either (1) the content of the entity you passed to the overrideLocalData was nil or (2) the contextual type in which you’re calling typedContent doesn’t match the content’s actual runtime type.
What do you see if you print resource.latestData.content? That will show you what’s actually there, and will rule out type conversion issues with typedContent.
If it’s not nil, compare its value from a network request and get the types to match.
If it is nil, then either something else cleared the content or you passed nil content in the first place. Try SiestaLog.Category.enabled = .common and see if you can spot where it is or isn’t getting set to the right thing.

How to avoid crashes in tableView(_:cellForRowAt)?

In the Fabric, I see a lot of crashes in the "tableView(_:cellForRowAt)" section. There is not a certain scenario for this exceptions. Anytime and in the any screen it can occur.There is no data for analysing the crashes. Only I know there are crashes in "tableView(_:cellForRowAt)".
I want to prevent this kind of exceptions although I do not know the root cause. Can I use a method like preventing NullPointer Exception (if (!null)) ?
Below two crashes in the different code sections ;
let XXX = Constants.sharedInstance.url+"/service/photo/"+userdas[(indexPath as NSIndexPath).row].id!+"/"+userdas[(indexPath as NSIndexPath).row].photo!+"/2"
and
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView = image
From your code, it's clear that you're making a couple of explicit force unwraps that could lead you to crash.
userdas[(indexPath as NSIndexPath).row].id!
userdas[(indexPath as NSIndexPath).row].photo!
self.notificationModel[indexPath.row].userNot.XXX?.XXXImageView
I guess that in the third case XXXImageView is implicitly unwrapped UIImageView that also might be nil.
To avoid the crash in your first section you can use a guard
guard let id = userdas[indexPath.row].id,
let photo = userdas[indexPath.row].photo else {
return
}
let XXX = Constants.sharedInstance.url+"/service/photo/"+id+"/"+photo+"/2"
I'm not sure what you're doing in the second section, but you just need to check that you unwrapped parameters aren't nil as well

How to tell using a guard statement if AVAudioPlayer has a file currently imported?

I am looking for a statement like AVPlayer has available like audioPlayer.currentItem in an AVAudioPlayer, or at least a statement which can give me the same results - whether or not there is an audio file in the audioPlayer.
There is always "an audio file in the audio player". All of AVAudioPlayer's initializers require that you supply something for the audio player to play. This audio player couldn't exist, therefore, unless you had done that.
Perhaps the issue here is that you don't know whether you have in fact already initialized this reference to an audio player. The way to handle that is to make the reference an Optional and start it at nil:
var audioPlayer : AVAudioPlayer? = nil
When the time comes to configure the AVAudioPlayer, you do so, and now the reference is no longer nil:
self.audioPlayer = AVAudioPlayer(...)
The test you are looking for then boils down to self.audioPlayer == nil.

Initializer for conditional binding must have Optional type, not '[AVCaptureDevice]'

I am working on giving my app camera capabilities. I was following a tutorial (AppCoda: https://www.appcoda.com/avfoundation-swift-guide/). I have looked at other S.O. answers and have learned that the right side of the statement(below) must be an optional for the error to be solved. How would I make it an optional and fix the error?
Optional allows you to have a nil in a variable. Then the compiler forces you to check if there is a value before you can use a variable. However, if you don't have an optional, that's a good thing for you - you don't have to check for the value cause it is ensured that there always will be one.
Therefore in your case you don't want an optional there, rewrite your code as follows:
let cameras = session.devices.flatMap { $0 }
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
Because obviously the call session.devices.flatMap { $0 } return an array [AVCaptureDevice], not an optional array [AVCaptureDevice]?. Therefore the compiler can guarantee that the cameras will always have an array value, and never a nil. Thus you can just guard against the empty array which would mean that there are no cameras (cause the list of cameras is empty).

Not using unwrapping in guard statements

I understand the use of optionals enough to know when its necessary to unwrap an optional using the exclamation point. Why is it that the exclamation point isn't needed in a guard statement?
This code works and compiles but doesn't use exclamation points:
struct Blog{
var author:String?
var name: String?
}
func blogInfo2(blog:Blog?){
guard let blog = blog else {
print("Blog is nil")
return
}
guard let author = blog.author, name = blog.name else {
print("Author or name is nil")
return
}
print("BLOG:")
print(" Author: \(author)")
print(" name: \(name)")
}
This code also works if you do put the exclamation points:
struct Blog{
var author:String?
var name: String?
}
func blogInfo2(blog:Blog?){
guard let blog = blog! else {
print("Blog is nil")
return
}
guard let author = blog.author!, name = blog.name! else {
print("Author or name is nil")
return
}
print("BLOG:")
print(" Author: \(author)")
print(" name: \(name)")
}
Isn't this a little contradictory or can someone clearly explain why the exclamation point isn't needed?
guard let unwrapped = optional is an Optional Binding (no link available directly to the correct book section, unfortunately). It safely tries to unwrap the value in the optional. If there is a value, the unwrap succeeds, and the value is assigned to the given name.
You should heavily favor the use of optional bindings, with either guard or if (the difference is the scope of the "unwrapped" name) over the use of forced unwrapping with !. A failed force unwrap is a fatal error; your program will simply crash.
I understand the use of optionals enough to know when its necessary to unwrap an optional using the exclamation point.
I have the feeling that you don't understand Swift Optionals enough if you make that assertion.
The contract behind an Optional is that it may or may not be nil; you don't know it and therefore you have to Unwrap it (like opening a box and see what's inside) before you can tell.
The box may be empty (nil Optional) or it may contain a value.
There are very few reasons to use the Forced Unwrap (!). Very Few, and it's generally considered a bad practice in most (but not all) cases.
To continue the analogy, by force unwrapping stuff, you're saying there is something in this box, and I want you to trust me and don't check.
Knowing that an Empty box will crash your application, this is a very dangerous thing to do, considering Optionals were introduced in Swift to protect you from these sort of crashes to begin with.
The if let/guard let statements basically peek inside the box and if there is something, they give it to you, but they also give you the chance to do something else in case the box is empty.
Xcode does force unwrapping when using IBOutlets because by design the contract is that those objects will be available by the time you can use them in your view controller, but unless you're 100% sure a value is not going to be nil then it's almost always better (and future proof) to use a guard statement (or an if).
In my experience, even when you know for sure, it's still safer to toss a guard and forget about the future problems that may arise.
The exclamation "force" unwraps an optional. guard let or if let unwraps it without forcing anything, so no exclamation point is used.

Resources