days since this article was written, please be aware of its timeliness
Explanation
Someone asked why not JavaScript—because it's a scripting language without a type system, making it completely incomparable to Swift.
As a front-end developer, I have only a beginner-level understanding of C, Java, and Python, but I’ve reached an advanced level (self-assessed) in TypeScript (hereafter referred to as TS) in terms of features and usage. Therefore, some things that shocked me might seem like “just ordinary language features” to others, or even prompt questions like, “Are the designers of TypeScript geniuses?”
I won’t necessarily feign shock at every difference between Swift and TS, because some differences stem from TS’s own limitations, while others are commonplace in programming languages (e.g., distinguishing between Int and Double instead of having just one Number type). It’s JavaScript’s design that’s truly shocking (after all, it was hastily designed in a week), not Swift’s.
My belief is: If a language has too many rules, too many exceptions, or too many reserved words, it’s not a good language.
Preface
This article follows the order of Swift’s official language introduction, focusing only on the parts that shocked me while omitting those that didn’t or that I didn’t understand, such as 附加宏.
Basics
Comments
Shocking discovery: Xcode doesn’t have a `/** */` block comment shortcut—only `cmd + /` for line comments.
VSCode’s block comment shortcut is also awkward to press: Alt + Shift + A. Do people not use this feature often?
Of course, in both VSCode and Xcode, you can automatically generate block comments by pressing /** and then Enter.
Optional Binding
Shocking discovery: While debugging, I wanted to write a value that’s always `true` in `if` for testing, but it’s not allowed—the optional binding in `if` statements must be an optional value.
1 | |
Switch’s Powerful Matching
Shocking point: switch has no implicit fallthrough.
This is understandable and more reasonable—who in their right mind would want case 1 to automatically spill into case 2 without explicitly using break?
Shocking point: switch can match object values!
This is actually considered feature, and intuitively more reasonable—truly an expert’s approach.
In TypeScript, since the switch case statement uses strict equality (===) for comparison, you generally wouldn’t pass an object inside your switch parentheses. This is because objects are compared by reference, and in TypeScript, there are rarely situations where you need to check object equality, let alone using a switch statement for such checks.
However, in Swift, you can “capture” the matched value (officially termed a “pattern”) within a case statement:
1 | |
It’s worth noting that TypeScript also supports “multiple matches,” but this is entirely different from Swift’s case-based multiple matching.
In Swift, for multiple matches separated by commas, the case statement executes as long as any one of the comma-separated items matches. In contrast, TypeScript’s comma-separated matching essentially only matches the last item, because comma-separated expressions in TypeScript only return the final value:
1 | |
Functions
Labeled Parameters for Functions
Shocking Point: Formal parameters (labeled parameters) can share the same name??? And the rule (here comes another restriction, sigh): parameters following a variadic parameter must have argument labels (if there's only one, the actual argument becomes the formal parameter) and cannot be omitted.
1 | |
Closures
N Abbreviated Forms of Closures
Shocking Point: There are too many syntactic sugars for closures to list here, but the most outrageous is the form that requires only a `>` symbol.
The essence of why this works is that String has a function called >. Yes, you read that right—a symbol can also be a function! More on this shocking detail later.
1 | |
Absolutely unbelievable.
Omitting Return Statements
Shocking Point: You might understand "single-line returns can omit `return`," but you'll never comprehend "multi-line returns can also omit `return`."
In TS, omitting the return statement works the same as in Swift’s ordinary syntax—single-line returns omit return:
1 | |
Ordinary Swift syntax:
1 | |
But! The SwiftUI way of writing it:
1 | |
You read that right—this is also a trailing closure function, where VStack is a function. The closure parameter, being the last and only parameter, allows omitting the parentheses of VStack. But! It returns two Text function calls without writing return. Why?
Because it uses ViewBuilder, which shares the same syntactic sugar as resultBuilder later.
Auto-Closures
Shocking Point: At first glance, assigning a plain closure seems unremarkable, but its lazy evaluation capability left me utterly shocked.
When I first encountered auto-closures, they were introduced with this example, touting their “lazy evaluation” capability:
1 | |
I thought, “Isn’t this just a plain closure, similar to TS, where the customerProvider variable is assigned a closure?” The same implementation in TS looks like this:
1 | |
Of course, the closure’s logic is only executed when it is called—what kind of lazy evaluation is this!
But its lazy evaluation truly shines when closures are passed as parameters:
A plain and straightforward explicit closure invocation, similar to how it’s done in TS, where the closure is passed as a parameter and written within a pair of curly braces:
1 | |
However, once you mark customerProvider as @autoclosuer, things become different. In this case, your serve function call can be written like this:
1 | |
Note the invocation of the serve function at the end. Its parameter customer is a statement customersInLine.remove(at: 0). In TS, regardless of the scenario, the call stack would first evaluate this value before invoking the serve function. But in Swift, this syntax is merely a different form of the above—the logic remains the same. That is, the serve function is executed first, and the statement is executed internally only when customerProvider is called. This is what they call 延迟计算, right?
If you write this often, you’ll encounter many cases like the following code:
1 | |
At this point, it becomes hard to tell whether the statements inside the parentheses or the outer function are executed first. As a result, the Swift official documentation also provides a warning:
Overusing autoclosure can make your code difficult to understand. The context and function name should clearly indicate that evaluation is being deferred.
This is really absurd—if it’s not recommended, why design it in the first place?!
Enums
Shocking fact: In other languages, enums are merely a convenient, optional type for state machine checks, but in Swift, they are a first-class type and one of the most commonly used features—even capable of replacing Structs in certain scenarios. Can you believe it?
Shocking fact 2: Enums are value types.
In TS, enums are just plain old “enums,” nothing more than listing values, mostly used to describe states in state machines:
1 | |
Of course, the tricks TS plays with runtime and compile-time differences are another story. In TS, you could easily replace an enum with an object:
1 | |
Associated Values
Shocking Point: Swift, What Were You Thinking Designing Enums Like This?!
In Swift, enums hold paramount importance. You can iterate through all their cases (requiring conformance to the CaseIterable protocol), treat a case as a function, and pass values during invocation for the enum instance to process—this is called “associated values”:
1 | |
Then, when using it, you can pass values:
1 | |
Enums are most commonly used in switch statements. Combined with Swift’s surprisingly powerful case matching, you can do this:
1 | |
The first time I saw this, I found it quite abstract and couldn’t think of any practical use cases (since I’d never done it this way).
Implicit Assignment
Shocking Point: The type declaration in enums doesn't specify the enum's own type (key-value pairs) but rather the type of its cases.
Implicit assignment in Swift is consistent with TypeScript or other C-like languages: the first value is n, and subsequent values are n + 1.
But Swift goes a step further—it performs implicit assignment based on the type you declare, which isn’t the default behavior. For example:
1 | |
In TypeScript, you can’t even specify the Int type that Swift allows (this type actually refers to the case’s type). Instead, you can only specify the enum’s type (usually key-value pairs Record elsewhere).
Structs and Classes
Shocking Point: Structs are actually value types?!
This seemingly straightforward struct is actually a value type (using Immutable optimizations for performance):
1 | |
Something wrapped in curly braces doesn’t look like a value at all! Bizarre! Shocking!
Properties
Various Property Wrappers/Observers
Shocking Point: Swift does way too much. Concepts like computed properties, stored properties, property wrappers (which TypeScript also has), and property observers (TypeScript has these too, bundled with wrappers) are typically framework-level features, such as Vue's `computed` and `watch`. Yet Swift implements them directly in structs and classes—unbelievable!
1 | |
Note: Property wrappers are not available for global variables.
Subscripts
Shocking Point: Honestly, the mere existence of the "subscript" concept is shocking enough. It provides a shortcut for accessing elements in collections, lists, or dictionaries. What's more, subscripts can even accept multiple values, just like functions.
Essentially, subscript calls can be thought of as function calls on data structures (like the aforementioned collections, lists, or dictionaries) to access values. The difference is that function calls use (), while subscripts use [].
Inheritance
Shocking Point: Similar to all the previous shocking points, Swift seems to casually add keywords for every possible purpose or effect. As a result, the number of reserved words (though they can't strictly be called "reserved" since Swift allows using any keyword by wrapping it in backticks—more on that later) related to inheritance is overwhelming. Examples include: `final`, `override`, `open`, `required`, and so on.
Initialization
Memberwise Initializers for Structs
Shocking Point: Because structs are value types, their initialization rules differ from those of traditional classes.
At first glance, structs and classes are just ordinary data structures, no different from classes in TypeScript. But once again, Swift complicates things here.
Since Struct is a value type, it’s special in that it has a constructor form called the “memberwise initializer.” This means when a Struct has no init at all, its properties can be initialized by passing parameters without explicitly writing them out.
1 | |
However, this rule doesn’t apply to Classes. A Class must explicitly have an init constructor method to initialize its properties.
Designated Initializers and Convenience Initializers for Classes
Shocking point: What? There are two types of initializers???
The designated initializer of a class functions just like a regular construct in TS—it’s a init method. But its convenience initializer… is marked by adding the convenience keyword before init (here we go again!):
1 | |
Actually, the Swift documentation doesn’t clearly explain to readers why convenience initializers are needed. Instead, it dives straight into class construction, inheritance, and initializer delegation. Here, I’ll explain to beginners why convenience initializers exist—for a very simple reason: 类中的多个指定构造器不允许互相调用。. That’s it! Can’t resist the urge to call it? Want to simplify the initialization process? Go ahead and use a convenience initializer!
Standard approach:
1 | |
Doesn’t it feel repetitive writing self.b twice like this? So, in the second init(), you might want to do this:
1 | |
Sorry, the compiler throws an error: Designated initializer for 'A' cannot delegate (with 'self.init'); did you mean this to be a convenience initializer?
In this case, you must use a convenience initializer:
1 | |
All for the sake of “convenience”!
Failable Initializers
Shocking point: Initializers can fail???
A so-called failable initializer simply means the initialization process (the init function call) might throw an error.
Therefore, if the initializer throws an error, you don’t need to wrap it in a try-catch like in TS. Instead, you can directly indicate it by adding a question mark after init, turning it into init?. Then, at the point where an error might occur, return nil, and when instantiating, check if it’s nil. That’s it!
In Swift, normal constructors don’t return values, which is the same as in TypeScript—but TypeScript constructors can return values. If the return value is an object type, it replaces the this object, which seems even more shocking.
Optional Chaining
Shocking point: Q: Under what circumstances does a function have parentheses written but not execute? A: In the context of optional chaining.
Without further ado, let’s look at an example:
1 | |
Error Handling
Shocking point: Like enums, Swift's try-catch (actually do-catch) is designed to be quite complex.
The catch statement can not only capture arbitrary errors but also specific error types. Using is or similar to a switch statement, there can be multiple catch branches for matching—absurd:
1 | |
Concurrency
Shocking point: While reading the documentation, I initially didn't realize that `withTaskGroup` is a built-in method for Group Task...
As for await, it’s used exactly the same way as await in TypeScript—so satisfying!
Extensions
Shocking point: Extensions might be the most powerful design in Swift. Any object, whether built-in or third-party, can be extended freely, at low cost, without any complex declarations or keywords.
Compared to TypeScript, where extending an existing class requires manipulating the prototype chain and then adjusting the object’s this to point to the class, in Swift, you just need a extension, and then you can boldly write any methods or properties you want—their this will point to the instance or the extended type (i.e., static methods or properties).
Absolutely mind-blowing.
Protocols
Shocking point: I believe protocols were introduced to address the abstraction issues of Structs, since in any language, classes themselves can be inherited (this might be an absolute statement), eliminating the need for an additional "protocol"—abstract classes would suffice. Incidentally, while Swift restricts classes to single inheritance, it allows protocols to play a more versatile role across classes, Structs, and enums.
Enough said—my thoughts are all above.
Generics
Shocking point: Generics introduced "associated types," which essentially serve the same purpose as generics declared upfront but are even more powerful.
Swift’s generic associated types look like this:
1 | |
As for why it wasn’t designed like this:
1 | |
Presumably because the Item might become too lengthy and inelegant, such as:
1 | |
If written after the name, generic declarations would often exceed the screen width, which is unbearable.
Opaque Types and Protocol Wrapping
Shocking point: You can achieve an effect where only the compiler knows the concrete type, while the caller only knows it conforms to a certain protocol.
Essentially, opaque types and protocol wrapping aim to hide implementation details from callers while allowing code refactoring with minimal changes. For example:
1 | |
Here, makeShape can return any type that conforms to the Shape protocol, without explicitly specifying the Circle type. This way, if new types like Struct that conform to Shape are added later, the function can still return them (a common practice in SwiftUI, such as some View).
Memory Safety
Shocking point: Since Swift allows passing values by reference, it can lead to simultaneous read-write operations on the same memory region (which makes sense, right?).
1 | |
The above code will crash at runtime, but the compiler won’t warn you. There are many similar cases where extra caution is needed. Since TypeScript doesn’t support pass-by-reference, such issues don’t arise—easy peasy.
Advanced Operators
Mind-blowing point: You can rewrite/customize the implementation of a function with a symbol as its name, thereby enabling operations between instances of the same class or structure.
For example:
1 | |
This design is brilliant—why didn’t I think of it? Amazing, truly amazing.
It’s worth noting that operator methods have their own attributes. For instance, + is a binary operator and also an infix operator, so it takes two functions. - can be either an infix operator (binary operator) or a prefix operator (unary operator, requiring prefix before func), making it overloadable.
However, Swift also specifies that the assignment operator = cannot be overloaded, nor can the ternary conditional operator (a ? b : c).
This operator is so common that you can find operator functions like == for equality checks in almost any Swift built-in object or Foundation object.
You can even implement your own operators!
1 | |
Absolutely incredible.
Result Builders
Mind-blowing point: Swift goes to extreme lengths for "elegance" and "reusability."
To make the following code look cleaner, Swift “invented” the magical @resultBuilder, as shown below:
Standard approach:
1 | |
Swift argues that the ternary conditional above is too cumbersome and becomes hard to read with complex logic, so it aims for elegance. Thus, result builders were born (if I understand correctly):
1 | |
Then, assign the c variable like this:
1 | |
Reusable, elegant, efficient—brilliant!
Lexical Structure
Mind-blowing point: Despite Swift having an overwhelming number of reserved words, it allows you to use them as identifiers.
The method is simple: enclose them in backticks:
1 | |
However, apart from keywords, x and x refer to the same variable.
-
Previous
How to Use the Same Shortcuts Across Dual Operating Systems -
Next
How to Develop iOS Apps with Cusor and Preview in Real Time
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.