I have a bit of a tale to tell regarding a homegrown Plex alternative which I've been working on for a year or two...
So, for years now, I've had a plex server in the garage, which I've used to entertain my girls and solve the problems of:
So far, so good: plex has helped keep the screaming-for-blue-murder down to a dull roar for years now. However, there have been many niggling issues with it that have driven me to think 'could I do better?', such as:
So, armed with a trusty text editor, I mucked around with ffmpeg, and Go[lang], and so on, until I came up with Gondola, which I'm now releasing as an open-source project here:
Gondola is the full suite: A transcoding-ahead-of-time media server which works from a Raspberry Pi or a fanless laptop (I recommend the latter); a native client app for your Apple TV, iPhone, and iPad; and a web client which will work on any Android device. I've put a small price on the apps in the app store (due to the hassle of getting them approved!) but you can compile and install them yourselves if you like.
Hope you like it!
Swift may be super-fast at runtime, but during compilation... well, it can be slow. Linkedin revealed in a blog post that they gave all their developers Mac Pros to get their compile times under control. I've experienced a project that takes 10mins each time you want to launch the simulator with 40KLOC. I had high hopes that Swift 3 would have an optimised compiler and solve this, however I found my compile times took around 30% longer after I migrated from 2 to 3. Also, after a codebase hits a certain size, the incremental compilation appears to give up, and any changes result in a full re-compile. And finally, code completion becomes unusably slow (or simply fails) after a certain codebase size too. Needless to say, these problems are disastrous to your productivity, so what can we do to solve them? Hopefully I can help you below.
My main recommendation is to split your app up into frameworks. Now I'm certainly not recommending creating your own cocoapods or carthage libraries or git submodules - I've done that before, it's way too much effort. However, with Xcode's nested projects, this solution can work with minimal ongoing maintenance.
The main architectural issue here is that you need to enforce careful separation of concerns, so that your code will split neatly into mostly-independent frameworks. Once you've done this, Xcode will only compile just the frameworks that have changed and your compile times + indexing + code completion should improve.
I recommend splitting your app up into frameworks like so:
How do we make these frameworks? Here's the process for adding to a workspace. If you're using a project only, it may be slightly different:
publicif things have to be subclassable. I recommend using public where you can because it's like java's
final, eg: no subclassing, which gives the compiler room for optimisations.
import Fooat the top of your swift file in the main/consumer project.
There really aren't too many downsides to this approach, but some things to watch out for:
Xcode will recompile the frameworks if you change them automatically before recompiling the main module. But if it can't compile a framework, it might try compiling the main project against the last compiled version of the framework, and give a bunch of weird errors. So sometimes you should select the subproject's scheme and try building that first.
Sometimes have to turn off SWIFT_WHOLE_MODULE_OPTIMIZATION to get useful errors if builds are hanging.
Sometimes when you have compile errors, it's worth looking in the Cmd+8 tab to see the build output, in case it's an earlier error in one of the frameworks that is snowballing to further things.
To load images that are stored in the main project's
xcassets from the subproject, you'll have to do this:
UIImage(named: "X", in: Bundle(for: type(of: self)), compatibleWith: nil). Alternatively you can put assets in a bundle in the subproject which is tidy.
Apparently if you have an app with too many frameworks, you can increase your app's startup time. For instance, it is common to see 5s startup time on iOS9 with ~20 frameworks. As of iOS9.3 onwards, this problem is largely mitigated, and it is more common to see a 0.3s delay with a dozen frameworks. Apple recommends only having 6 or less frameworks in your app. I haven't done any tests, and I cannot say for sure if these techniques would be any slower than having one monstrously large app module. But this is something to keep in mind. You can read more here: useyourloaf.com/blog/slow-app-startup-times
As of writing (Xcode 8.1) you can often get a significant compile speedup by adding the following user-defined build setting, it may be worth a try for you:
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES
Thanks for reading, I hope this helps!
Are you looking down the barrel of a necessary Swift 3 migration and groaning? Well, having recently reached the other side of that journey with a large codebase (40KLOC of Swift, excluding pods/carthage/etc), I found it to be difficult enough that it deserves a write-up, to hopefully spare the reader a landmine or two.
Firstly, you'll want to migrate to Swift 2.3. This is presented as an option when you open your project in Xcode 8 for the first time. Don't do what I did and migrate from 2.2 (Xcode 7) to 3 in one step - its simpler to take it in smaller steps.
2.3 will give you access to all the newest iOS10 APIs, and is as such a good stop-gap for now. Please note that Xcode 8.2 is the last to support 2.3 and after that, you'll need to migrate to Swift 3 syntax.
Xcode 8.0 is quite buggy - I recommend skipping it. In fact, as of writing, 8.1 just came out so this information is possibly redundant (sorry, this article took a while to write). 8.1 still has the overly-verbose logging issue which renders the simulator console almost unusable however - hopefully in 8.2 this will be resolved. However there are no longer blocking issues in 8.1 so I recommend using it.
If you are part of a multiple-member team, I recommend getting one person's entire time set aside solely for a migration, in order to migrate as quickly as possible so that you don't have a nightmare of merge conflicts. I also recommend choosing a week when other team members aren't making large refactors/additions to the codebase too, again for the sake of merge conflicts.
Alright, jump in and run Xcode automatic migrator on your codebase. I recommend eyeballing all the migrations it makes - in my experience it did a poor job and many simple mistakes were made. This will take you quite a while unfortunately.
Once you've done this, try building your project. Don't be upset if you get many errors, this is simply a result of Xcode's migrator which isn't fantastic. I found that the error view in Xcode was often misleading regarding where the code wasn't compiling, and had to resort to the Xcode error logs (command+8) to find the true culprit of these problems. I recommend you do the same if you find any errors that don't make sense when looking at the code.
In the process of you fixing many errors, compiling, then finding more errors, and so on for many hours, you'll find that Xcode's indexing slows you down more than it helps. At this stage of the process, I found that disabling indexing really sped up the workflow. Run this from the terminal and restart Xcode:
defaults write com.apple.dt.XCode IDEIndexDisable 1
To re-enable later on, run the same command with
0 at the end.
Tests might not work upon first migrating - feel free to postpone solving this issue until after the main git merge to master has succeeded. The aim of the game is to get merged quickly to avoid conflicts with your other team members.
If you have a really large codebase with many team members and are concerned about merge conflicts, you may want to consider doing a 'dry run' of the conversion to find parts that convert poorly. You can then refactor code such as this on your main (swift 2.3) branch of code such that it'll convert more easily when the time comes to jump in and do the full conversion. For instance, closures that have named arguments don't port across easily, and can be changed in the Swift 2 branch easily.
Another advantage of this technique is that when it comes time to do the full conversion, you can pull the latest from your master branch, then do the migration more quickly the second time around, and limit the length of time your Swift 3 branch exists for, thus minimising merge conflicts.
In the Swift 2.3 step in your migration, I recommend upgrading to the very-latest-but-prior-to-swift-3 version of all your dependencies/pods/carthage frameworks. Well managed open source projects, such as ReactiveCocoa, have subtle changes for the Swift 3 versions, and use of incompatible APIs in the prior version is marked as deprecated. Check all such warnings and migrate to the newest code style so that when you jump to the Swift 3 versions of such APIs, you will have fewer updates to make.
Also take stock of which libraries will need upgrading. Pure Objective-C frameworks will not need upgrading at all. For each of the Swift ones, check to see if there is a version for Swift 3 that has been released. If there hasn't been a Swift 3 version released, you have a few options:
You may consider letting one of your team members perform the final migration over a weekend to minimise merge conflicts, and allow them to take a couple days off in lieu the following week. Of course, do all you can to prepare for this ahead of time, such as migrating to 2.3 beforehand and preparing your strategy for your libraries.
In no particular order, some issues to be aware of (some of these may be resolved in Xcode 8.1):
Xcode 8 is buggy: communication between Xcode and the simulator are unreliable. Don't be concerned when this occurs, it's not just you.
Incremental compilation hasn't improved: usually you'll find that once your codebase gets to a certain size, it breaks down and any change will do a full recompile.
If you cast an NSObject to AnyObject via
foo as? AnyObject, it returns a double-wrapped optional, so do
foo as AnyObject instead.
var foo: NSObject? = nil; let bar = foo as AnyObject returns a non-optional AnyObject that is actually nil, causing runtime crashes.
Casting NSDictionary to AnyObject and back fails if you use
as. Solution: Don't use NSDictionary anywhere, use
[AnyHashable: Any] instead.
Any ObjC code that takes a NSDictionary will take a
[AnyHashable: Any] from Swift. This will automatically bridge across from Int/Double/etc to NSNumber, String to NSString, etc. However any Swift-only types such as enums will also be happily passed across and cause runtime crashes. And the migrator will happily migrate some things attribute dictionaries across to using modern enums and trigger these issues.
UIControlState.Normal migrates to becomes UIControlState() often.
Swift 3 segfaults often - look in Report Navigator command+8 to find errors in code
Our biggest motivation for migrating from Swift 2 ASAP was our crippling compilation speed, and unfortunately compilation is still slow in Swift 3: on the latest rMBP15, it takes 7 mins for 40KLOC. However, there is a clever trick you can use:
Add the user-defined build setting
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES and magically our compiles are now down to 65s. This has been verified to work with other friends of mine with similar sized codebases. I have no idea if this takes precedence over the normal whole module optimisation setting, however it does seem to work.
One drawback I've noticed is that a subproject doesn't compile properly (if you have these), with
SWIFT_WHOLE_MODULE_OPTIMIZATION enabled, the compile will simply never complete, as though it has gone into a
while(true);. So if that happens to you, temporarily disable the build setting. It's unfortunate that we're at version 3 of the language and still having teething problems like these.
Hope this helps you get over the hump!
You can see older posts in the right panel, under 'archive'.
Gondola 26 Feb 2017
Scalable Swift 22 Nov 2016
Swift 3 Migration 6 Nov 2016
Enum-Driven View Controllers 3 Jan 2016
Status bar colours: Everything there is to know 30 Dec 2015
Android server 20 Dec 2015
Generating heightmap terrain with Swift 8 Nov 2015
Swift Education Screencasts 27 Oct 2015
Swift Image Cache 24 Sep 2015
Don't be slack 13 Sep 2015
Swift KVO alternative 23 Jul 2015
Swift Keychain wrapper 21 Jun 2015
Swift NSURLSession wrapper 12 Jun 2015
iOS8 View Controller transitioning bug 17 Apr 2015
IB Designable 18 Mar 2015
iOS App Architecture 2 Mar 2015
Video Course Launch 14 Feb 2015
Video Course Pre-launch 8 Feb 2015
Blogging Platforms 13 Jan 2015
Mobile in 2014 - Year in Review 11 Jan 2015
Secret Keys talk 16 Nov 2014
Dimmi 11 Nov 2014
Project setup in Xcode6 22 Oct 2014
Uploading to an S3 bucket from iOS 15 Oct 2014
iOS8 App Testing Roundup 28 Sep 2014
Storing obfuscated secret keys in your iOS app 16 Sep 2014
New season 1 Aug 2014
House finished 17 Jun 2014
WebP decoding on iOS 9 Feb 2014
Moving on again 22 Jan 2014
Lossy images for retina iPads - JPEG vs WebP 30 Nov 2013
Positivity and your friends 7 Oct 2013
Tactility 26 Jul 2013
WWDC-induced narcolepsy 15 Jul 2013
Back on rails 31 May 2013
Full circle 6 May 2013
Programmatic UI on iOS 3 May 2013
Screencasts and positivity 8 Apr 2013
Year of positivity 14 Mar 2013
iOS Dev State of the Union 6 Feb 2013
Adventures with IAPs 3 Feb 2013
No longer a Googler 23 Dec 2012
Fight back (app biz update 13) 12 Nov 2012
Sent to the backburner (app biz update 12) 25 Oct 2012
Lisi Schappi 7 Oct 2012
Today's happy plateau (app biz update 11) 26 Aug 2012
First week's sales of Today (app biz update 10) 19 Aug 2012
Approved! (app biz update 8) 5 Aug 2012
Hurry up and wait (app biz update 7) 30 Jul 2012
Today app marketing site 27 Jul 2012
Today app submitted 25 Jul 2012
UIAlertView input wrapper 24 Jul 2012
Mentoring 23 Jul 2012
This is too hard! (app biz update 6) 20 Jul 2012
Perspectives (app biz update 5) 9 Jul 2012
4th starting-my-own-biz update 1 Jul 2012
ScrumFox landing page 28 Jun 2012
Server Scope landing page 27 Jun 2012
Telstra Calls and Data Usage 26 Jun 2012
Service History + Dropbox 26 Jun 2012
Impromptu Presenter 26 Jun 2012
Fertility Tracker 26 Jun 2012
Baby Allergy Tracker 26 Jun 2012
Starting my own business, update 3 22 Jun 2012
Starting my own business, update 2 17 Jun 2012
Starting my own business - First update 10 Jun 2012
I must be crazy 6 Jun 2012
Finding your location on an iPhone 7 May 2012
A generous career 4 May 2012
Skeleton Key Cocoaheads presentation 3 May 2012
That book about that Steve Jobs guy 30 Apr 2012
Another app marketing idea 23 Apr 2012
Sweet grouped tables on the iPhone 17 Apr 2012
Skeleton Key App 11 Apr 2012
Another app marketing idea... 5 Apr 2012
Skeleton Key Password Manager with Dropbox 2 Apr 2012
RC Boat motor finally mounted 2 Apr 2012
Promoting apps presentation slides 1 Apr 2012
The Finishing Line 20 Mar 2012
Indie iPhone app marketing, part 2 19 Feb 2012
Indie iPhone App Marketing part 1 7 Feb 2012
Perspectives 2 Feb 2012
Accountability and Free Will 1 Feb 2012
Badassery 31 Jan 2012
Sacrifice 30 Jan 2012
Lead Yourself First 29 Jan 2012
How to ping a server in Objective-C / iPhone 26 Jan 2012
iOS Automated Builds with Xcode4 16 Jan 2012
Xcode 4 - Command line builds of iPhone apps 15 Jan 2012
Guest post by Jason McDougall 13 Jan 2012
Scouts, Games and Motivation 10 Jan 2012
2011 Re-cap 8 Jan 2012
Ruby script to increment a build number 4 Jan 2012
Turning 30? All ideas, no execution? 18 Dec 2011
Memory management in Objective-C 4 Dec 2011
Starting small 29 Nov 2011
Dictionary Types Helper 29 Nov 2011
Observer Pattern in Objective-C 16 Nov 2011
Why you should give presentations 13 Nov 2011
Custom nav bar / toolbar backgrounds in iOS5 8 Nov 2011
Stuck 27 Oct 2011
Dead easy singletons in Obj-C 19 Oct 2011
JSON vs OCON (Objective-C Object Notation) 18 Oct 2011
In defence of Objective-C 16 Oct 2011
Icons 11 Oct 2011
Markdown Presentations 1 Oct 2011
More MegaComet testing: Ruling out keepalives 15 Sep 2011
MegaComet test #4 - This time with more kernel 14 Sep 2011
Building People 10 Sep 2011
Hacker News Reader 20 Aug 2011
MegaComet testing part 2 3 Aug 2011
Australian Baby Colours 28 Jul 2011
Boat prop shaft 25 Jul 2011
Megacomet with 1 million queued messages 24 Jul 2011
Installed the strut and rudder 18 Jul 2011
Painted the inside of the boat 17 Jul 2011
My 3 Data and Calls Usage 11 Jul 2011
Reading a line from the console in node.js 10 Jul 2011
Final finish 9 Jul 2011
MessagePack parser for Objective-C / iPhone 30 Jun 2011
Lacquering the starboard side 25 Jun 2011
Lacquering the hull 23 Jun 2011
Staining the boat 22 Jun 2011
NSMutableSet with weak references in objective-c 20 Jun 2011
Baby Allergy Tracker 12 Jun 2011
Power sanding the deck 10 Jun 2011
Planing the edge of the deck 2 Jun 2011
Figured out the deck 2 Jun 2011
Boat bulkheads 2 Jun 2011
Simulating iOS memory warnings 31 May 2011
Putting a UIButton in a UIToolbar 29 May 2011
Random Chef 17 May 2011
Little Orchard 13 May 2011
Boat update 13 May 2011
Design portfolio 10 May 2011
Centered section headers on a UITableView 5 May 2011
Christmas in may 4 May 2011
Git on the Mac 19 Apr 2011
Skinning the port side of the boat 8 Apr 2011
Skinning the side of the boat 5 Apr 2011
Skinning the bottom of the boat 31 Mar 2011
Extremely simple python threading 29 Mar 2011
New rescue boat 26 Mar 2011
Simple C# Wiki engine 30 Sep 2010
How to use sessions with Struts 2 30 Jul 2010
Using Oracle XE with Hibernate 20 Jul 2010
Ruby hex dumper 4 May 2010
Using Java libraries in a C# app with IKVM 16 Dec 2009
Learning Java tutorial 27 Nov 2009
Using generic database providers with C# 17 Nov 2009
Scheduled task executable batch babysitter 29 Oct 2009
Still fighting with String.Format? 9 Sep 2009
How I'd build the next Google 24 Aug 2009
C# Cryptography - Encrypting a bunch of bytes 14 Jul 2009
Reconciling/comparing huge data sets with C# 9 Jul 2009
Some keyboard-friendly DHTML tricks 10 Jun 2009
Adding a column to a massive Sql server table 16 Mar 2009
Multithreading using Delegates in C# 10 Mar 2009
Using threads and lock in C# 3 Feb 2009
MS Sql Server 2005 locking 17 Dec 2008
Simple Comet demo for Ruby on Rails 19 Nov 2008
Converting HSV to RGB colour using C# 14 Aug 2008
A simple server: DigitalMars' D + Libev 6 Jun 2008
Travelling from Rails 1 to Rails 2 9 Apr 2008
Online Rostering System 9 Apr 2008
DanceInforma 9 Apr 2008