Yup, that is a good point. I think the memory leak issue really scared me wrt memory usage. The other uncertainty that led to that decision (5 sec buffer) was that I'm not sure how (or how well) garbage collection works in Swift. I don't yet understand what makes an object eligible for GC in Swift (I need to learn) and how quickly it is cleaned up. I'm scared of the possibility of GC not being able to keep up with allocation. Also, I have noticed some remnants of C-like behavior, which, frankly, terrifies me. That uncertainty definitely pushed me towards the side of using less memory. So, it's not so much the footprint under normal circumstances, but the uncertainties and potential for a runaway/leak, that drove my decision.
Unlike Java, Swift isn't garbage collected, it uses Automatic Reference Counting (ARC) instead. So there isn't a collector that runs occasionally. If you are familiar with reference counting, or C++'s shared pointers, it's similar to that, but Swift emits the retain/release calls on your behalf. So things will get deallocated the
moment the ref count goes to zero.
It has different concerns when handling performance compared to garbage collection, so it's worth looking it up. You won't see heavy surges in memory usage from allocations that wind up waiting. What you will see is performance issues if you allocate a bunch of objects in tight loops that are loop scoped as you run afoul of the implicit retain/release on those objects.
The only reason for me extending NSObject was that it was necessary for some of my selectors to work.
Yeah, with Swift, I try to avoid using selectors, and use closures instead, since Swift closures are compatible with Obj-C/GCD blocks. I was more curious why Track and related types were NSObjects, since the separation of concerns made it difficult to understand why you'd need to interact with them from Obj-C directly.
As for doing track IO in the Track initializer, that comes to the subject of separation of concerns, and I think this has to do with my background in Java. All my life, I've been taught that, for the most part, model objects should be as "dumb" as possible - only concerned with their data fields and operations on those data fields. So, reading track info from a file doesn't agree with that paradigm. That's why I created TrackIO. I noticed the kind of thing you're suggesting is present in the Swift API, in String for example ... you can construct a String from the contents of a file ! Wow, I never would have guessed that ! There are very few instances of this kind of behavior in Java, from what I remember ... java.util.Properties can be read from a file using a constructor, but that's about it. So, yeah, in short, I wanted to keep the IO logic separate from the "dumb" Track model object.
Yeah, it's something where there are probably a couple different ways to go, and I would shrug and say either one could be the "right" one. I tend to skew more towards the fact that related code should be nearby and accessible so that the scope of a thing can be determined when reading the code. Separation of concerns is still compatible with that model, but it does take a bit of finesse to strike the right balance.
This is another area where type extensions come in to help strike the balance, for two reasons:
- The extension only has access to public members/initializers.
- The extension can live in a separate file, or even module.
So what I tend to do is put convenience initializers like these in extensions. That way the concern of how to create the type itself remains encapsulated away from the code that knows how to read the file. In the case of Track, how I'd personally do it is something like this:
Code:
// Oh hey, AVMetadataItem kinda already conforms to this.
protocol TrackMetadataItem {
var keyName : String? { get }
var stringValue : String? { get }
var numberValue : String? { get }
var dateValue : String? { get }
}
struct Track {
// Some stuff
init(metadataItems : [TrackMetadataItem]) {
// Initialize myself with the metadata items, I can check for specific key names and the like with switches/etc.
}
}
// In some other file, or not, doesn't matter
extension AVMetadataItem : TrackMetadataItem {
var keyName : String? {
get {
return self.commonKey as? String
}
}
}
extension Track {
// May need to use NSURL, not sure when URL stabilized in Swift. But it is bridgeable with NSURL
init?(fileUrl : URL) {
let asset = AVAsset(...)
// And some other things
guard (thisAllWorked) else { return nil }
self = Track(metadataItems: asset.commonMetadata)
}
}
It's not a perfect example, but it does show how you can still separate the concerns of interacting with the AVAsset to extract the metadata from the ownership of the Track. While providing a way to construct a Track without having to know anything more than the metadata items being passed in. So the Track doesn't even really know anything about where the metadata came from. And the fileUrl initializer doesn't actually have to know how the Track wants to use the actual metadata items (something your code fails to separate out cleanly).
But note the benefits over the class factory style. Typing "Track(" will offer up the fileUrl version of the init, making it easier as a programmer to know how they can create a track. I also cheat a bit here and make it possible for AVMetadataItem to conform to TrackMetadataItem very simply, so the metadata items from the AVAsset can be passed in as-is since it already is an array of things Track wants when initializing. So not only am I keeping the two parts separated, I also manage to avoid too much boilerplate to glue the two together.
A more interesting thing would be to turn the array into a dictionary of [String: TrackMetadataItem]. The trick here is to do something a little goofy. Extend "Array where Element: TrackMetadataItem", and create a convenience function that returns the dictionary by using keyName as the key, and the TrackMetadataItem as the value. You can get more specific here, but it does get more complicated to get the compiler happy with it. But the idea being that now the version of init that takes a fileUrl can easily create the dictionary from an array of AVMetadataItems, while the Track initializer can now just lookup keys directly.
I didn't yet absorb what you said about mutable structs, let, etc. I think it's because I just woke up

I will need to read that again.
Short reason: Java and Swift are actually somewhat similar here. They both have value types and reference types which have different semantics. Where it gets interesting in Swift is that struct vs class gives you different semantics (I think Java structs are value types too, maybe?), and with copy-on-write, value semantics for complex types (like Track) are a lot cheaper than they would be in Java. It doesn't copy on assign/pass, but rather on mutation (to a point). And passing them between threads via closures is safe. So there's fewer reasons to use classes exclusively, since structs actually provide you a bit more flexibility in some cases. My Swift code tends to skew more structs than classes, depending on what the type represents. But it is a habit that's hard to break from C++/Java/Obj-C.
And Swift has better mechanisms to prevent mutation on value types, which avoids the need for String + StringBuilder/StringBuffer where "String" is immutable, while "StringBuilder" is mutable. Instead, String itself can be made mutable or immutable using let vs var. Fewer types are needed to represent the same set of concepts, since you can declare a particular copy of a struct mutable or not.
When I wrote my enums, I looked up "How to convert enum to String" and came across rawValue(), but then discovered that it was not (apparently) available for me in Swift 2.0. I tried using XCode to suggest a rawValue (String) code completion for my enum, but it didn't. So, I assumed that it was a newer Swift feature, and wrote my own toString function. I will look into Int-based enums, though. I didn't know Swift had "Comparable" !!! I'm sure you can appreciate why it piques my curiosity
I like your idea of writing extensions (as opposed to wrapper classes). I don't yet know how they work, but have seen them everywhere in code samples, and will give them a try
Can I ask how long you've been programming ? What kind of stuff have you programmed ?
See
this on raw values. You mostly have to declare the enum correctly and things light up. Because once the enum is declared correctly, it winds up getting bound to a few different protocol extensions that Swift defines in the library. That's where the ability to set and get via a raw value is defined.
Far, far too long for someone my age (more than 20 years, not even 40 yet). Picked it up early, but I'll say I was pretty rubbish for the longest time in high school and even college. The main thing was how many languages I got exposed to. I could produce usable code out of college, but a decade later and I'm not sure I can say I'm proud of some of the things I wrote back then. I do have a couple contributions in Handbrake and libavcodec from that timeframe though (under a different alias). Still use the Handbrake changes I wrote today (in some form anyways, not sure how much they've modified them for MKV input and the like). I currently work on a project you've probably heard of, and before that, worked on another project you've probably heard of.
Honestly, I'd say that my specialties are in Obj-C, Swift, and C. I know enough C# not to be completely rubbish in it, but they keep changing things drastically for me to realistically keep up. I'm still learning some of the bizarre undocumented C++ behavior (things you don't learn until you work with multiple compilers). But I've dabbled in so many languages beyond these that it kinda makes up for my lack of depth.