neal

Zhao long joined Qunar in 2020. He is responsible for big front-end iOS development, rich in OC, SWIFT, C++, Dart and other skills. He likes to optimize the development process and study codes and development methods to increase efficiency.

** Shu-hui Lin **

Shuhui Lin joined Qunar in 2018, iOS and RN development engineer. At present, I am responsible for the development and maintenance of the home page, user center and other functions of public products of the big client. Continue to pay attention to the forefront of learning big front-end technology, praise the efficiency optimization and performance improvement brought by technological innovation. Now we are committed to the development and promotion of Native+DSL dynamic component solutions.

One, foreword

SwiftUI has been in existence for 2 years, but it has not been promoted on a large scale. It is limited to the iOS ecosystem, and the UI library is temporarily closed source, which needs iOS13 version to adapt. These factors prevent more people from using SwiftUI, but in fact, compared with other UI frameworks, SwiftUI has very high development and operation efficiency. It’s a complete framework upgrade compared to Objective-C+UIKit. The purpose of this article is to familiarize everyone with SwiftUI, to provide students with practical data and features to refer to when selecting technology, and to promote the Swift infrastructure construction of large clients.

SwiftUI code base

In iOS, SwiftUI is driven by two underlying frameworks SwiftUI. Framework and Combine. Framework, among which SwiftUI. For example, we want to implement a button with text in the center of the screen. In OC, we usually write:

- (void)viewDidLoad { CGRect screen = [[UIScreen mainScreen] bounds]; UIButton * centerBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [centerBtn setFrame:CGRectMake(screen.size.width / 2 - 30, screen.size.height / 2 - 15, 60, 30)]; [centerBtn setTitle: @ "test button" forState: UIControlStateNormal]; [self.view addSubview:centerBtn]; }Copy the code

In SwiftUI we write:

Var body: some View {Button(){Text(" test Button ").frame(width: 60, height: 30, alignment:.center)}}Copy the code

The Combinator. framework is responsible for generating data flow binding operations and interfaces based on the observer model, where all dependent objects are notified and automatically updated when an object’s state changes. However, these frameworks extend this pattern a bit by introducing an optional conversion operation (operator) between observed and observed.

Third, the rendering process

Take the example of rendering text at the bottom left of a page in the Let’s Go Where widget.

Text(self.title) .font(Font.system(size:14)) .fontWeight(.semibold) .foregroundColor(.white) .frame(alignment: .center). Offset (x: -3.5, y: 2).Copy the code

Each element (Text in the example) starts at the bottom of the code when entering the page, and each unit (each. It then tells its parent unit (the top line of the code) to take the view generated by the unit below as an argument, generate a new view, recursively form the entire rendering tree, and then go to the pipeline for rendering. When the state changes, SwiftUI will first compare the change of the View declaration before and after, and only submit the different parts of the declaration to the pipeline.

4. Life cycle

During WWDC 2020, Apple announced SwiftUI was getting a new app life cycle to get rid of UIKit’s old modes of AppDelegate and SceneDelegate. Based on this, the iOS 14 now provides an App agreement, a SceneBuilder, scenePhase enumerator wrappers UIApplicationDelegateAdaptor and a new attribute. Following the APP protocol, we can build the UI in a new way, where we have to implement SceneBuilder, which is essentially a function builder for building one or more scenes, using scenePhase to get the current state of the scene, UIApplicationDelegateAdaptor used to connect the old app lifecycle methods. The new lifecycle code is executed in the following order:

As opposed to the AppDelegate, which uses WindowGroup to wrap around scenarios that need to listen for life cycles, it makes it easy to access page life cycle methods, concise and decoupled between pages. The existing rendering process still follows the iOS Cocoa framework, using Core Animation as the rendering Core library.

5. Language features

SwiftUI is fundamentally not about building the UI, but describing it. What’s the difference between the two? When you build your UI, you will know the type of each control, even down to the platform. For example, in a native UI framework, you need an input field, and you have to choose whether to use UITextFiled (iOS) or NSTextFiled (macOS) based on the platform, and the two input fields have different properties, appearance, and features. With Flutter, you don’t need to consider the types of controls on different platforms, but you do need to consider the Style of the controls. Officially, There are Android and iOS controls that conform to the Material Design Style. Many of their features are also different. All of this leads to more complex logic and effort in UI building. SwiftUI is more like a template for the Web. It only describes the UI, and what the controls look like and what features they have. It is implemented automatically according to the platform on which they are compiled. It’s like giving the Web a template, a theme. Next I show you a control Picker, where the same code behaves differently on different platforms.

You’ll find that the same code presents a completely different UI, but the UI does say exactly the same thing. The Picker control, which translates to a selection page on iOS, has a wheel selection effect. On macOS, the Picker is translated into the drop-down list that is common on our desktop operating systems. It fully conforms to the platform design language and user habits, and greatly reduces the difficulty of development and adaptation. In this regard, SwiftUI and RN share more similar ideas and technologies. However, Facebook has almost no say in either iOS or Android, so there is no compatibility or consistency with SwiftUI or Google’s own cross-platform frameworks in the future.

SwiftUI data status and binding

SwiftUI binds data to interfaces in three ways.

1.@State & @Binding

Provides internal state storage for the View, which should be a simple value type marked private for internal use only.

2.ObservableObject & @ObservedObject

For state sharing across the View hierarchy, handle more complex data types and trigger an interface refresh when data changes.

3.@EnvironmentObject

For states that “jump” across multiple View hierarchies, ObservableObject is more readily used to simplify code. They are used by placing modifiers in front of the corresponding properties, using Propertywrapper wrapper to make the properties the observed, and notifying UI refreshes directly if they change. Apple has introduced responsive programming to SwiftUI, which has become the mainstream of front-end development. Developers can easily decouple view and logic processing based on native apis provided by Apple, which reduces the maintenance cost of code and allows developers to devote more energy to business implementation. In contrast, the objective-C +UIKit development mode that Qunar is currently using, if developers want to experience responsive programming, they will face a series of problems such as introducing a series of third-party frameworks (such as ReactiveCocoa) and code style limited by syntax. In this regard, SwiftUI’s advantage over Objective-C is clear.

7, Some grammatical features of SwiftUI

1.Opaque Result Type

Returns the object types in the static language must be determined at compile time, and some of the objects in your code and want to hide your type, class, external use agreement for this SwiftUI gave his solution, opaque return type, sum up a word, it is a generic type instantiation, do not rely on the caller specifies the type of a grammatical features. Look at an example:

func reverseGeneric() -> some Shape { return Rectangle(...) } let x = reverseGeneric() // Rectangle (of: x) == Rectangle //Copy the code
2.Propertywrapper

This feature can be used to wrap part of the reusable code around attributes Get and Set, using an @ character followed by a custom attribute name.

3. FunctionBuilder

This feature is a syntactic sugar that allows you to build compound views using method arguments that accept implicit closures. In other words, you can write what you want to build directly in component build arguments without the need for various semantic controls such as colons, commas, declarative parameter types, closure types, etc. Look at an example:

VStack(alignment: .leading) {
    Text("Hello, World")
    Text("Longzhao.zhao")
}
Copy the code

Using these features properly, the status display of login, hotel, air ticket and train ticket has been realized with about 600 lines of code in the qunar iOS widget.

SwiftUI compares other declarative UI frameworks

Declarative UI frameworks bring excellent experience to developers. Compared with building UI interfaces with imperative UI frameworks such as UIKit in iOS, declarative UI frameworks allow developers to describe their needs in natural language. From the point of view of code maintenance, a complex UI structure, if the use of imperative UI framework, it is difficult for developers to intuitively see a piece of code to achieve a UI effect, and standard declarative code can even give people a “WYSIWYG” feeling. To sum up, there are several reasons why declarative UI is generally considered to be more advanced than imperative UI: 1. Suitable for one development, a variety of different equipment types adaptive. 2. Display UI and control logic are bound with data through reactive thinking to achieve decoupling. 3. It is easier to implement the local refresh mechanism of UI controls. We have selected two other declarative UI frameworks — Flutter and RN, which are currently popular on mobile terminals, to compare them with SwiftUI, hoping to give you some inspiration. The comparison is divided into four dimensions:

About grammar:

SwiftUI benefits from Function Builder and Opaque Return Types added to Swift5.1 to enhance the ability to build built-in DSLS, making code concise and efficient. Use Swift features such as FuctionBuilder, Trailing closure, and ellipsis of return (when there is only a single expression in the function body, a return is automatically added to return the value of that expression), SwiftUI implements an intuitive code style that looks like this:

struct TripCardContentView: View { var cardData: CardData? var body: some View { switch cardData? .cardType { case CARD_TYPE_FLIGHT: FlightCard(cardData: self.cardData as? FlightData) case CARD_TYPE_TRAIN: TrainCard(cardData: self.cardData as? TrainData) case CARD_TYPE_HOTEL: HotelCard(cardData: self.cardData as? HotelData) default: Spacer() } } }Copy the code

The final effect of the actual business:

RN uses JSX to simulate HTML tags, which is more in line with the thinking of Web development. In my opinion, each has its own advantages. In contrast, Flutter does not have such a powerful internal DSL, so it will be inconvenient for the development experience. Although the Flutter team has made some improvements to the development experience through the prompt plugin in the editor, formatting when saving, etc., the overall development of Flutter is not as good as the previous two. Based on these language features, we have greatly accelerated the development of Flutter. Package platform support) took only 7 days to go live, the hotel and ticket display business was developed later, the code took 2pd.

About hot loading:

SwiftUI includes static preview and dynamic preview. Static preview is shown by default, which is fast and supports both code and visual writing. However, it does not have any response events and cannot scroll or jump to the page. If you need dynamic debugging, you need to switch to dynamic preview. Dynamic previews take some compile time and can be fully dynamic in real-time response to UI changes, and can be debugged in real time on a real machine. However, there are many limitations to dynamic preview. Flutter only needs to be compiled once, and then the whole App can be updated dynamically. RN implements Hot Reload based on HMR(Hot Module Replacement), which is a good experience. However, in actual use, there are often half-written code because the editor automatically saves and triggers the watchMan callback. Hit hot Reload logic, resulting in a red screen. Flutter is a hot reload implemented by injecting updated source code files into the running Dart Virtual machine (VM). After virtual machines update classes with new fields and functions, the Flutter framework automatically rebuilds the widget tree so that developers can quickly see the changes. However, due to Dart language features such as lazy initialization of static fields, hot overloading will not work in some scenarios, such as changing initializers of global variables and static fields, changing enumeration types to regular classes, or changing regular classes to enumeration types, and requiring a complete application restart. Overall, the three frameworks have similar experiences with hot reloading, and all achieve the goal of being able to preview styles in real time, which improves the efficiency of UI construction. But for some code changes that involve logic, it’s safer to reload completely.

Cross-platform:

SwiftUI currently supports APP development for iOS, watchOS, iOS14 widgets and MacOS platforms. Theoretically, it is possible to compile apps for multiple platforms using one set of code. However, due to user experience considerations, Apple does not recommend using one set of UI design for different devices. They prefer “Learn once, Apply anywhere”. While RN and Flutter can span OS and Android (the latest Release of Flutter also supports Windows, MacOS and The Web), SwiftUI is more focused on improving the coding uniformity and experience across Apple’s own platforms. Swift’s philosophy is a little different from the other two.

Performance:

By testing scenarios with extremely long list views, lots of animations simultaneously, and lots of view rotations and zooms, we found that SwiftUI did, unsurprisingly, perform best in the above scenarios. And then there’s Flutter and RN. However, if cross-platform needs must be considered and RN should be avoided in high CPU usage businesses, Flutter is better suited for this task.

Ix. Thinking on the landing scene of SwiftUI in Qunar big client

Above we compared the various declarative UI frameworks from different dimensions. We believe that all technologies have their own suitable scenarios, and making choices without scenarios will inevitably lead to inappropriate selection of technical solutions. Based on some specific scenarios of Qunar big client, we also think about the technologies mentioned above and summarize them as follows: 1. Flutter can be used to develop pages that require higher performance and prefer cross-platform development to save efficiency. However, because there is currently no official solution for dynamic thermal update of Flutter, the iteration of new features will depend on the release. As for the support of Flutter framework in big clients, the development colleagues of the company have made great progress and are looking forward to the comprehensive application of Flutter technology in the company in the future. In terms of application scenarios, IM chat page, which requires high UI consistency and ensures drawing efficiency, is the ideal scenario for Flutter landing. 2. RN is an option for pages requiring hot updates that require less performance and more efficient cross-platform development and rapid iteration. This is also the mainstream solution of Qunar at present. Its hot update and increasingly rich API have brought great efficiency improvement for client function iteration, but due to the mechanism of RN framework, some pages with stringent performance requirements have to consider other solutions. 3. For some pages with high requirements on performance, and can accept iOS and Android to invest manpower for development and update with the version, such as the home page and login page of large client, native is still an irreplaceable solution. UI implementation of native end can be developed using UIKit and SwiftUI framework. However, since the minimum supported iOS version of SwiftUI is iOS13.0, and the current minimum supported version of our large client is iOS10.0, So in the future iOS13+ has become an absolute high share of the system, these pages can consider the preferred transition to swiftUI framework implementation, it caters to several needs of the home page now: 4. Objective-c has fallen behind in both development and operation efficiency, and if the lowest version of iOS13 can be achieved, it will be able to have the highest frame rate performance, which can minimize the startup time of the home page, the most accurate crash and lag location, and maintain the fastest and most comprehensive support for new iOS features. SwiftUI are both the better choice and the older OC frameworks only have some advantages in docking requirements that have historical baggage 5. Because Swift and Objective-C can call each other via bridging, the most economical way to make Swift compatible with old OC, RN, and Flutter codes is to use Swift to bridge the necessary OC API. 6. At present, we have used SwiftUI framework to achieve the iOS14 widget of the large client. Since its launch in November, 20, until now, it has supported the high-frequency scene of the display of itinerary information on the iOS desktop.

Migrating Swift and SwiftUI in Objective-C

Swift language is the direction of iOS development, and we also investigated the feasibility of migrating to Swift and SwiftUI. Most OC codes can be directly translated into Swift by corresponding API. Some features need to be paid attention to, such as @synchronized. Dispatch_once has been removed from Swift. You need to write a similar function to implement it. Here’s an example of implementing these features yourself:

Give an example of implementing these features yourself

// func synchronized(_ lock: AnyObject, block: () -> Void) { objc_sync_enter(lock) block() objc_sync_exit(lock) } //swift dispatch_once public extension DispatchQueue { private static var _onceTokens = String() public class func once(token: String, block:()->Void) { objc_sync_enter(self) defer { objc_sync_exit(self) } if _onceTokens.contains(token) { return } _onceTokens.append(token) block() } }Copy the code

Is it now necessary to migrate all the large client code to Swift? Considering the cost and benefits of migration, it is not necessary. A feasible solution is to add some functions of Swift framework, such as network library, appInfo, universal service, etc. The existing OC framework is encapsulated by bridge inside the framework to provide Swift version API for business lines to use. In this way, the functions implemented by the subsequent native code will have a choice of Swift implementation, thus realizing gradual migration.

Xi. Conclusion

Swift as apple’s strategic language has grown stronger and stronger, and Apple has been fully supporting Swift since the Swift ABI became stable in 2019. Since iOS 13, Apple has added about 10+ pure Swift libraries, 10+ open Swift libraries, and 144 open frameworks. 57 Framework apis have been redesigned in Swift style. Based on the facts:

The first. Apple has stopped using objective-c for code demonstrations since WWDC2017; In the second. Developer.apple.com/ no longer updates objective-C related documentation; In the third. WidgetKit only supports Swift, not Objective-C; In the fourth. App Clips limits the pack size to 10M, and SwiftUI is the most suitable frame; The fifth. The proportion of Objecive -c in the open source community is declining and even obsolete, such as Lottie.

It can be judged that Swift is the only choice for Apple platform in the future. If we can get rid of the burden and use Swift in Native development as soon as possible, we can gain an advantage in the comparison of development efficiency and operation efficiency with other apps in the next step. In the development of Qunar big client, we can choose the most suitable technology according to the data and schemes listed above. Swift language or SwiftUI framework can also be used as the reference in this article or the implementation details here. Qunar iOS big client has supported Swift&OC mixed programming, and each business line can directly join Swift files to participate in local compilation and CM packaging. In the future, we will actively promote the establishment of a Swift basic framework for big client to support the company’s development towards Swift and SwiftUI.