1. Too many different launchd file locations. See for instance http://stackoverflow.com/questions/18502705/how-to-know-a-sp... -- this can make it difficult to troubleshoot a startup issue or get a handle on what is and isn't being started and when.
2. .plist files are a binary format, ostensibly to save space, which seems a little bit ridiculous on an operating system like recent versions of MacOS X. So you have to use a separate set of tools just to interact with them. This also means that it's harder to fix corruption of .plist files or troubleshoot strange values they may have; in a plain text format, you'd just open it up in vi or emacs or whatever, and if you know what you're doing, you could find and fix anything that doesn't make sense.
3. The XML format in general isn't a very nice way to handle configuration files. Unless you've got syntax highlighting, it's hard to read.
4. Complex declarative configuration files aren't my favorite thing regardless, because it's hard to know what your options are and what the right values for those options are. With simple configuration systems where there is are several sets of values, and each set of values has the same half-dozen or so options, it's not so bad. But, in cases where you can have hundreds of different options, and not all of them documented well, an optimal configuration can turn in to a bit of black magic.
I guess maybe #4 doesn't really apply directly to .plists, since they're not intended to be edited by the user, they're intended to just be for application developers. But I'm not sure that's a point in their favor either.
It mixes handling of many different service types, including daemons, so-called agents (which is just like a daemon except per-user and with muddled semantics, since they can all be per-login, per-user, GUI session or non-GUI session, but at the same time are the only category where direct user interaction is permitted), Mach services, XPC services and others. It also wrongly equivocates process types with scheduling policies, making for the really awkward and overlapping semantics of "Background", "Interactive", "Standard" and "Adaptive". Further complicating this is the LaunchOnlyOnce flag, for one-shot jobs. It's all inconsistent
Using its socket activation means you need to explicitly opt in to your daemon using liblaunch. It's not a generic mechanism like UCSPI is, for instance.
Whereas daemontools uses the actor model/Erlang philosophy of "let it crash" and restarting to known good states until dependencies are met, and systemd is based on a complicated dependency resolution/ordering mechanism... launchd is in this weird middle ground of neither. It instead expects OS services to resolve their sequences through IPC, but at the same time provides really racy and primitive ordering techniques through the KeepAlive key, evidently because users and sysadmins needed something. The result are inherently racy file system namespace mutexes like PathState, very limited network synchronization through NetworkState and a really easy to abuse poor man's dependency system through OtherJobEnabled (which actually violates launchd's own philosophy of IPC over dependencies).
In spite of its attempts to do load balancing and scheduling, the interfaces for doing so are mixed. You have standard rlimits, but also ambiguous ones like LowPriorityIO and LimitLoad<wildcard>, latter ones depending on sysctl variables.
It reportedly has weird interactions with cron because of it holding timers while sleeping: http://hea-www.harvard.edu/~fine/OSX/launchd_cron.html
WaitForDebugger is an odd key to have. Implies to me that launchd breaks standard ptrace(2) semantics.
And of course, XML plists.
In summary, even though launchd has good ideas, the entire architecture is just a mess of layering violations, semantic discrepancies, incorrect coupling and insufficient interfaces that make it really awkward to use. It tries to be the one-stop management interface for just about every unit of CPU time on the whole damned system, all of it in PID1 at that, and so naturally its abstractions become clumsy. Different tasks call for different systems to run and schedule them.