2

I'm new to swift and have a lot of repeating code. For example how would you change the following code into different functions:

Example 1

if let button = view.viewWithTag(12) as? UIButton {
            // change any properties of the button as you would normally do
            button.isHidden = true
        }

Example 2

var oneAPlayer = AVAudioPlayer()
var oneBPlayer = AVAudioPlayer()
var oneCPlayer = AVAudioPlayer()
var oneDPlayer = AVAudioPlayer()
var twoAPlayer = AVAudioPlayer()
var threeAPlayer = AVAudioPlayer()
var fourAPlayer = AVAudioPlayer()
let oneASound = Bundle.main.path(forResource: "1-a", ofType: "mp3")
let oneBSound = Bundle.main.path(forResource: "1-b", ofType: "mp3")
let oneCSound = Bundle.main.path(forResource: "1-c", ofType: "mp3")
let oneDSound = Bundle.main.path(forResource: "1-d", ofType: "mp3")
let twoASound = Bundle.main.path(forResource: "2-a", ofType: "mp3")
let threeASound = Bundle.main.path(forResource: "3-a", ofType: "mp3")
let fourASound = Bundle.main.path(forResource: "4-a", ofType: "mp3")
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Alex Bailey
  • 793
  • 9
  • 20
  • I can't give a good answer about what to do with the first section (all the `AVAudioPlayer` instances), but I wrote an answer to handle the second case. Let me know if you need me to explain any parts of it – Alexander Dec 16 '16 at 05:05
  • This is a VERY good question. IMNSHO – Confused Dec 16 '16 at 07:19

2 Answers2

3

I wouldn't necessarily use a new function for this. I would write it like so:

let soundNames = ["1-a", "1-b", "1-c", "1-d", "2-a", "3-a", "4-a"]
let sounds = soundNames.map{ Bundle.main.path(forResource: $0, ofType: "mp3") }
Nirav D
  • 71,513
  • 12
  • 161
  • 183
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • this is good, but maybe an array example would be a bit easier for a newcomer – Fluidity Dec 16 '16 at 05:05
  • @Fluidity I'll answer questions from OP as they come up, but I'm not a replacement for the language guide. – Alexander Dec 16 '16 at 05:06
  • 2
    yes, the docsets do a great job of explaining FP and declarative paradigm.. especially to newcomers ;) Recursion and monads would be great starting material too. – Fluidity Dec 16 '16 at 05:43
  • 2
    @Fluidity I have no bearing on what OP's knowledge is like, and to start from first principles would be futile, my post would be no better (likely worse) than existing, popular, beginner resources. The whole upside to SO is that it's tailored information. In my answers I try to give the key information necessary in order for OP to know where to go next (what keywords to search for, for example), and elaborate as requested. But again, not a replacement for docs/tutorials. – Alexander Dec 16 '16 at 05:46
  • 1
    @AlexanderMomchliov Use `flatMap` instead of `map` so that it will prevent `nil` object and you can also use `url(forResource:withExtension:)` instead of using `path(forResource:ofType:)` and latter make `URL` instance from it. – Nirav D Dec 16 '16 at 05:47
  • I understand Alex, and it's a good philosophy to have. My only concern was that his / her first sentence was "I'm new to swift and have a lot of repeating code". since the declarative approach is literally one line here, it could have been tacked on to something without trailing closures and closure syntax xD but yes, it is the best way to do it. Still have my upvote – Fluidity Dec 16 '16 at 05:49
  • 1
    @NiravD I think force unwrapping would be more appropriate. I think it's better to crash to find bugs like misspelt file names, missing files, etc. than to silently ignore, and later wonder why they're missing. – Alexander Dec 16 '16 at 05:50
  • I agree, force unwrapping is good for learning, probably bad for shipping code though :P – Fluidity Dec 16 '16 at 05:51
  • 1
    @Fluidity I'm of the opinion that newbies should *not* be handed forced unwrapped optionals, because they'd be prone for abuse. But tasteful, justified use is perfectly valid. Having a missing file or incorrect filename is an unrecoverable error, it's probably that it fails loudly than be ignored silently – Alexander Dec 16 '16 at 05:55
  • @Fluidity IBOutlets are IUOs by the same logic. There's nothing you can do about an IBOutlet not being hooked up properly, and carrying on without one is probably never a reasonable idea, so it's better to force a crash – Alexander Dec 16 '16 at 05:55
  • 1
    @AlexanderMomchliov That was your thinking, I would follow optional wrapping to prevent app crashing. – Nirav D Dec 16 '16 at 06:08
  • @NiravD App crashing isn't always a bad thing. In this case, what's the alternative? How could you recover from an error like this? – Alexander Dec 16 '16 at 06:27
  • @AlexanderMomchliov I know app crashing isn't a bad thing, I'm saying is preventing app crashing is also a good habit. Anyway let it be every one has different thinking on this one. – Nirav D Dec 16 '16 at 06:45
  • @NiravD What would you do, in this situation? – Alexander Dec 16 '16 at 07:07
  • Could you explain what this nesting of map and $0 do to achieve the "loop" over the collection? – Confused Dec 16 '16 at 07:20
  • @Confused `$0` is an "anonymous closure argument". That is to say, because the compiler knows that this closure takes 1 argument, rather than giving it a name, I just refer to it at the 0th argument. See this post for more info http://stackoverflow.com/a/40390414/3141234 – Alexander Dec 16 '16 at 07:32
  • @Confused `map` iterates over an array of inputs, and maps them into an array of outputs, according to the closure given. In this case, the closure takes an input (one of the soundNames), and produces an output (the sound's path) – Alexander Dec 16 '16 at 07:37
  • @Confused For example, `[1, 2, 3].map{ $0 * 2 }` produces `[2, 4, 6]` – Alexander Dec 16 '16 at 07:37
  • so `sounds` is a new, filled array? – Confused Dec 16 '16 at 08:37
  • @Confused Yup. Play around with it! – Alexander Dec 16 '16 at 08:39
  • Ok. That's fine, I grok that, i have a similar function I learnt from rickster or someone else around here to sort some textures for an app I'm working on. But, given this is an array, he misses out on what he wants, which is that the variables be named `oneASound`, `oneBSound`, `oneCSound`, `oneDSound`. These may well be notes of a scale, so he might really want those sounds named in this way. Is it possible to use this kind of functional trickery to create a dictionary with the right names for each element? – Confused Dec 16 '16 at 08:41
  • Or maybe push them into an array with these names, as variables? – Confused Dec 16 '16 at 08:42
  • Sorry, map and $0 and $1 are VERY new to me, and I'm much impressed with their power. – Confused Dec 16 '16 at 08:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/130766/discussion-between-alexander-momchliov-and-confused). – Alexander Dec 16 '16 at 08:50
  • Good way to teach @AlexanderMomchliov swift .map is really powerful than for statement. +1 – Federico Malagoni Dec 16 '16 at 09:56
3

Alex answer is good, but it's pretty complicated for someone new. I would suggest learning the basics of functions before attempting functional programming (usage of .map, .filter, and other fun stuff).

Here is a simple example that you can modify in numerous ways to be more flexible if desired:

var sounds: [String:String] = [:]

func addSound(_ num: Int,_ letter: Character) {

  let key = String(num) + "-" + String(letter)
  sounds[key] = Bundle.main.path(forResource: key, ofType: "mp3")
}

// Usage:
addSound(1,"a")

print(sounds["1-a"])

What we are doing is using just one variable to hold the sounds in... You access your different sounds by typing in the string, as demonstrated in the usage section.

This is a dictionary, and they are very powerful. They work based on Key and Value.

So here we have key of "1-a" and the value will be the bundle that we added in the function.

The function basically just converts the input parameters, an integer and a Character, and turns it into a string that we can use with our Bundle and Dictionary.



If you want to add many sounds at one time, you can convert this to an array:

func addSounds(_ keys: [String]) {

  for key in keys {
    sounds[key] = Bundle.main.path(forResource: key, ofType: "mp3")
  }
}

// Usage:
addSounds( ["1-a", "1-b"])
print( sounds["1-b"])

This second example is exactly what Alex did, his version used .map which is actually a function that does basically the same thing as the for loop. It's a great feature, but it focuses on what's called declarative programming --which is great--but generally difficult to learn when just starting out.

Fluidity
  • 3,985
  • 1
  • 13
  • 34