Author: Zhe Jian Wang

After one and a half months of technical reconstruction, several months of iteration and volume increase, the new product evaluation list of Handtao will finally finish the whole process with 100% flow stably on the Singles’ Day in 2021. We have not only made a definite improvement in business, but also made a lot of technical exploration, such as settling the light mode R&D framework based on DinamicX + event chain arrangement, promoting the upgrade of native language to Swift/Kotlin, and finally making a big improvement in overall r&d efficiency and stability. (Note: DinamicX is internally developed dynamic UI framework)

In this article, I will focus on Swift. If you want to know how Swift improves the efficiency/quality of research and development, whether existing projects/modules need Swift as the native language, how to select the type, what problems we have encountered in the process of commodity evaluation landing in Swift, and what benefits and conclusions we have at last, I hope this article can bring you some help.

First of all, why do I choose to learn Swift?

Technological change, the future is here

Because I firmly believe that COMPARED with OC, Swift is more capable of carrying the future.

Strong backing

The main reason is that it has a strong backing. As the most important development language of Apple in the future, Swift has exported up to 73 WWDC contents, including but not limited to syntax, design, performance, development tool chain, etc. The specific contents are shown in the figure:

Looking back at the development of Swift in recent years, it has been seven years since its official release in 2014. During the whole process, Apple has invested a lot of energy in the construction of Swift, especially the emergence of Swift Only framework. It also means that Apple is actively encouraging you to get involved in Swift development.

The three major advantages

Second, Swift has three distinct advantages: it’s faster, it’s more secure, and it’s more expressive.

Faster means that Swift has made a lot of improvements in execution efficiency. For example, the Swift system library itself has adopted many basic types that do not require reference counting, and the problems of memory allocation size, reference counting loss, method distribution static analysis and so on have been effectively improved. The specific details will not be analyzed here. If you are interested, you can refer to Understanding Swift Performance for details.

The so-called security does not mean that there is no Crash, but that any input has a relatively clear performance definition. Swift was designed to allow developers to write code without any insecure data structures, so Swift has a robust type system that allows developers to do almost all of their development work without having to worry about Pointers. It also offers a variety of types and functions prefixed with Unsafe, for high-performance interactions with Unsafe languages such as C, and for relatively Unsafe operations such as manipulating raw memory. Another aspect is to distinguish types so that most development scenarios use safe types.

I can share a data here. An App project I participated in was written in Pure Swift (99%+), and our online crash rate has been around 8 out of 100,000 all year round, which is a very impressive result for a small team (4 people on one end). With virtually no use of Unsafe apis, most of our problems were avoided at compile time, and the design of optional types and optional bindings forced developers to think about how to handle null scenarios, preventing developers from making mistakes before the software was even released.

More expressive in short, a complete piece of logic can be expressed in less code. There have been 330 proposals in the Swift Evolution project to enhance the expressibility of Swift. Thanks to these features, Swift has approximately 30-50% less code than OC. Let’s take a few practical examples

Builder Pattern

When we define a complex Model with many attributes, we do not want the attributes of the model to be changed after initialization. We need to solve through the Builder mode, the code is as follows:

// OCDemoModelBuilder.h
@interface OCDemoModelBuilder : NSObject

@property (nonatomic, copy, nonnull) NSString *a;
@property (nonatomic, copy, nonnull) NSString *b;
@property (nonatomic, copy, nonnull) NSString *c;
@property (nonatomic, copy, nonnull) NSString *d;
@property (nonatomic, copy, nonnull) NSString *e;
@property (nonatomic, copy, nonnull) NSString *f;
@property (nonatomic, copy, nonnull) NSString *g;
@property (nonatomic, copy, nonnull) NSString *h;

@end

// OCDemoModelBuilder.m

@implementation OCDemoModelBuilder

- (instancetype)init {
    if (self = [super init]) {
        _a = @"a";
        _b = @"b";
        _c = @"c";
        _d = @"d";
        _e = @"e";
        _f = @"f";
        _g = @"g";
        _h = @"h";
    }
    return self;
}

@end

// OCDemoModel.h

@interface OCDemoModel : NSObject

@property (nonatomic, readonly, nonnull) NSString *a;
@property (nonatomic, readonly, nonnull) NSString *b;
@property (nonatomic, readonly, nonnull) NSString *c;
@property (nonatomic, readonly, nonnull) NSString *d;
@property (nonatomic, readonly, nonnull) NSString *e;
@property (nonatomic, readonly, nonnull) NSString *f;
@property (nonatomic, readonly, nonnull) NSString *g;
@property (nonatomic, readonly, nonnull) NSString *h;

- (instancetype)initWithBuilder:(void(^)(OCDemoModelBuilder *builder))builderBlock;

@end

// OCDemoModel.m

@implementation OCDemoModel

- (instancetype)initWithBuilder:(void(^)(OCDemoModelBuilder *builder))builderBlock {
    if (self = [super init]) {
        OCDemoModelBuilder * builder = [[OCDemoModelBuilder alloc] init];
        if (builderBlock) {
            builderBlock(builder);
        }
        _a = builder.a;
        _b = builder.b;
        _c = builder.c;
        _d = builder.d;
        _e = builder.e;
        _f = builder.f;
        _g = builder.g;
        _h = builder.h;
    }
    return self;
}

@end

// Usage

OCDemoModel *ret = [[OCDemoModel alloc] initWithBuilder:^(OCDemoModelBuilder * _Nonnull builder) {
    builder.b = @"b1";
}];

// ret = a,b1,c,d,e,f,g
Copy the code

However, Swift Struct supports attribute defaults and initialization constructors, making the builder pattern meaning is not very large, the code is as follows:

struct SwiftDemoModel {
    var a = "a"
    var b = "b"
    var c = "c"
    var d = "d"
    var e = "e"
    var f = "f"
    var g = "g"
    var h = "h"
}

// Usage

let ret = SwiftDemoModel(b: "b1")

// ret = a,b1,c,d,e,f,g
Copy the code

State Pattern

When the execution result of a function may have many different states, we usually use the state pattern to solve the problem.

For example, if we define a function execution result, there may be three states: Finish, Failure, and None. Because there are some associated values, we cannot use enumeration to solve the problem. Three different concrete types need to be defined as follows:

/// Executable.h @protocol Executable <NSObject> - (nullable NSDictionary *)toFormattedData; @end /// OCDemoExecutedResult.h @interface OCDemoExecutedResult: NSObject<Executable> // construct a null return value + (OCDemoNoneResult *) None; + (OCDemoFinishedResult *)finishedWithData:(nullable NSDictionary *)data type:(nullable NSString *)type; // construct an error return value + (OCDemoFailureResult *)failureWithErrorCode:(nonnull NSString *)errorCode errorMsg:(nonnull NSString * *)errorMsg userInfo:(nullable NSDictionary *)userInfo; @implementation OCDemoExecutedResult + (OCDemoNoneResult *) None {return [OCDemoNoneResult new]; } + (OCDemoFinishedResult *)finishedWithData:(nullable NSDictionary *)data type:(nullable NSString *)type { return [[OCDemoFinishedResult alloc] initWithData:data type:type]; } + (OCDemoFailureResult *)failureWithErrorCode:(nonnull NSString *)errorCode errorMsg:(nonnull NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo { return [[OCDemoFailureResult alloc] initWithErrorCode:errorCode errorMsg:errorMsg userInfo:userInfo]; } - (nullable NSDictionary *)toFormattedData { return nil; } @end /// OCDemoNoneResult.h @interface OCDemoNoneResult : OCDemoExecutedResult @end /// OCDemoNoneResult.m @implementation OCDemoNoneResult @end /// OCDemoFinishedResult.h @interface OCDemoFinishedResult: OCDemoExecutedResult @property (nonatomic, Copy, nonNULL) NSString *type; @property (nonatomic, copy, nullable) NSDictionary *data; // init method - (instancetype)initWithData:(nullable NSDictionary *)data type:(nullable NSString *)type; @end /// OCDemoFinishedResult.h @implementation OCDemoFinishedResult - (instancetype)initWithData:(nullable NSDictionary  *)data type:(nullable NSString *)type { if (self = [super init]) { _data = [data copy]; _type = [(type ?:@"result") copy]; } return self; } - (NSDictionary *)toFormattedData { return @{ @"type": self.type, @"data": self.data ? : [NSNull null] }; } @end /// OCDemoFailureResult.h @interface OCDemoFailureResult: OCDemoExecutedResult @Property (nonAtomic, Copy, ReadOnly, nonNULL) NSString *errorCode; @property (nonatomic, copy, readOnly, nonNULL) NSString *errorMsg; @property (nonatomic, copy, readOnly, nullable) NSDictionary *userInfo; /// initialization method - (instancetype)initWithErrorCode:(NSString *)errorCode errorMsg:(NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo; @end /// OCDemoFailureResult.m @implementation OCDemoFailureResult - (OCDemoFailureResult *)initWithErrorCode:(NSString *)errorCode errorMsg:(NSString *)errorMsg userInfo:(nullable NSDictionary *)userInfo { if (self = [super init]) { _errorCode = [errorCode copy]; _errorMsg = [errorMsg copy]; _userInfo = [userInfo copy]; } return self; } - (NSDictionary *)toFormattedData { return @{ @"code": self.errorCode, @"msg": self.errorMsg, @"data": self.userInfo ? : [NSNull null] }; } @endCopy the code

But if we use Swift’s enum feature, the code is much, much cleaner:

Public enum SwiftDemoExecutedResult {/// Correct Returned value Case Finished (type: String, result: [String: Any]?) Case failure(errorCode: String, errorMsg: String, userInfo: [String: Any]?) Func toFormattedData() -> [String: Any]? { switch self { case .finished(type: let type, result: let result): var ret: [String: Any] = [:] ret["type"] = type ret["data"] = result return ret case .failure(errorCode: let errorCode, errorMsg: let errorMsg, userInfo: let userInfo): var ret: [String: Any] = [:] ret["code"] = errorCode ret["msg"] = errorMsg ret["data"] = userInfo return ret case .none: return nil } } }Copy the code

Facade Pattern

When we define an input parameter that needs to conform to multiple protocol types, we often use Facade Pattern to solve the problem.

For example, we have four protocols JSONDecodable, JSONEncodable, XMLDecodable, XMLEncodable and one method with two inputs Input parameter 1 is JSON that meets both JSONDecodable and JSONEncodable. Input parameter 2 is XML that meets both XMLDecodable and XMLEncodable. When we use OC to solve a problem, we usually write:

@protocol JSONDecodable <NSObject>
@end

@protocol JSONEncodable <NSObject>
@end

@protocol XMLDecodable <NSObject>
@end

@protocol XMLEncodable <NSObject>
@end

@protocol JSONCodable <JSONDecodable, JSONEncodable>
@end

@protocol XMLCodable <XMLDecodable, XMLEncodable>
@end

- (void)decodeJSON:(id<JSONCodable>)json xml:(id<XMLCodable>)xml {

}
Copy the code

Two additional protocols are defined JSONCodable and XMLCodable to solve this problem. But in Swift we can use & to solve this problem, without defining additional types, as follows:

protocol JSONDecodable {}
protocol JSONEncodable {}
protocol XMLDecodable {}
protocol XMLEncodable {}

func decode(json: JSONDecodable & JSONEncodable, xml: XMLDecodable & XMLEncodable) {
}
Copy the code

The above are some of the more expressive aspects of Swift, of course, there are far more advantages than these, but I won’t go into them here because of space limitations.

All in all, thanks to Swift’s high expressiveness, developers can express a complete piece of logic with less code, reducing development costs to a certain extent, and also reducing problems.

overwhelming

In addition to a strong backing and three advantages, Swift also has a good development trend in recent years.

First of all, according to Githut, Swift’s Pull request on Github has surpassed OC, as shown in the figure below :(data as of 2021/10/25)

At the same time, the domestic Top 100 Swift mixed programming applications have also increased significantly, rising from 22% in 2013 to 59% (data as of 2021/04/22)

On the one hand, many domestic first-tier Internet companies have started to layout, on the other hand, WidgetKit and other Swift Only frameworks are also prompting everyone to start building Swift infrastructure.

Of course, the foreign data is more impressive, 91%, almost all of them have been used, why say so? That’s because eight of the top 100 Google apps in the US don’t use Swift.

Here is another data to share with you. In our spare time, when we organized the author recruitment of WWDC Internal Reference, we collected the author’s technology stack and interest points, and finally found that more than half of the authors had rich experience in Swift development. Two-thirds were also interested in Swift (a total of 180 people). It can be seen that the community is still very enthusiastic about Swift. In the long run, whether to use Swift for development will also be one of the reasons why people choose to work.

Why choose the product review list?

Maybe a lot of people, after seeing part 1, will feel like I need to use Swift in our project right away. So you don’t have to pay for your impulse, here’s how I went to Swift.

At first, I joined the Mobile Infrastructure group, and one of the main responsibilities was to build Swift infrastructure. But later, due to the needs of the organization, I joined a new business architecture group, and the focus of my work changed from upgrading the Swift base to driving the business. Transform into business pilot drive basic technology upgrade. During this process, we went through three major technical decisions:

  • 1. The project the team took over at the beginning: The mobile shopping order agreement was upgraded to New Ultron
  • 2. Based on the domain understanding of business research and development, the team proposed a new event chain arrangement capability, which was co-built with DX
  • 3. Reconstruction of product evaluation, including evaluation list and interaction, etc

At each stage, I have considered whether I should use Swift or not, but I finally gave up using Swift for the first two times, mainly due to the following considerations:

The prerequisite to use Swift is required

Swift was not adopted as the main development language for the ORDER NEOgenesis project. The biggest problem was that the basic infrastructure was not complete at that time. Most modules rely on almost do not support modules. It is almost impossible to adopt Swift, which will increase a lot of workload, and it is not a wise move for a project with a tight schedule. Therefore, I give up the idea of using Swift temporarily.

What business is better suited for Swift refactoring

With all the basics in place, Swift is a better choice for a business refactoring project. It is no longer appropriate to use OC to reconstruct a business module, either due to the general trend or Swift’s unique advantages.

For large projects that want to try Swift, it is suggested that businesses with small burden and small involvement should be given priority as pilots. Another important reason why we gave up Swift when ordering new Ultron projects at that time was that the overall structure of Ultron was relatively complex, the construction and data mixed together, and the high cost of partial modification would lead to the problem of affecting the whole body at the same time, and the overall openness and inclusiveness of end-to-end new technology interaction were limited. However, there is no such problem in the product evaluation of mobile shopping, and there are more choices. Therefore, we firmly choose Swift as the main development language on the side.

Both to adapt to local conditions, and to obtain support

When the project has the conditions to use Swift, it must take a comprehensive consideration based on the current situation of its team.

First of all, the team needs to train or equip someone with Swift development experience in advance to ensure that complex problems are tackled and code quality is controlled. In particular, code quality. Most people who first contact Swift from OC will experience a period of “discomfort”, during which it is easy to write Swift code with “OC taste”, so it is particularly necessary for someone with enthusiasm, relevant experience and technical ability to practice and lead by example.

At the same time, we also need to get the support of the supervisor, which is very important. It is difficult to keep a project going with technical love alone. I need to keep communicating with the supervisor based on the situation of the project, and constantly upgrade my thinking on a technology in the process of communication. It is also a very interesting process to make the supervisor question the technology from the initial to the final support.

Need to have certain technical foundation support

First of all, in terms of infrastructure completeness, we conducted a large-scale Module adaptation, which solved the core problem of integration. At the same time, DevOps was upgraded, package management tool TPOD was upgraded to 1.9.1, static library version framework project was supported at source level, tPOdedit mode was also provided to solve the problem of header file dependency, and some core bayonet checks were added in the release link to prevent project deterioration.

Second, we based on hand for the existing technical scheme, the performance and efficiency issues such as, at the end of the day, we combine the pain points understanding of business development, research and develop the arranged based on event chain model upgrade to explore, and from the early stages of cost considerations in DX, and output to the new Ultron internal project, and the overall framework is as follows:

At the UI layer, we use XML as a DSL to ensure two-end consistency while reducing the cost of two-end development.

In terms of logical orchestration, we designed the event chain technology scheme to atomize each end-to-end base capability as much as possible, so as to ensure that end-to-end capability developers can focus on the development of capabilities.

With the support of the above framework, developers can decide which development language to use for a single basic capability, and the cost of getting started with Swift can be reduced by a level for beginners, without having to struggle with complex environments.

What are the problems?

To be frank, although we did a lot of deep thinking when making technical decisions, we still encountered a lot of problems when actually implementing them.

The base library API is not adapted to Swift

Although Xcode provides the ability to “automatically” generate bridge files, most automatically generated Swift apis do not follow the “API Design Guidelines” due to the large syntax difference between OC and Swift. This results in a lot of unreadable and unmaintainable code from the Swift business library that is currently accessed.

At the same time, due to the optional value design of Swift, OC SDK needs to sort out the optional Settings of each external API input and output parameter when it is provided to Swift. A basic SDK that relied heavily on product reviews didn’t do this very well, and we ran into a lot of problems.

Unnecessary compatibility due to incorrect derivation

Let’s start with the following code:

// democonfig.h@interface DemoConfig: NSObject /* Useless code is omitted */ - (instanceType)initWithBizType:(NSString *)bizType; @end // DemoConfig.m @implementation DemoConfig - (instancetype)initWithBizType:(NSString *)bizType { if (self = [super init]) { _bizType = bizType; } return self; } /* Useless code is omitted here */ @endCopy the code

Because the DemoConfig class does not specify whether the initialization method return value is optional, Xcode’s default derived API becomes.

// Swift API open class DemoConfig: NSObject {/* this is omitted */ public init! (bizType: String!) }Copy the code

Developers then have to figure out how to solve scenarios that are initially empty, which is obviously redundant.

In addition to the SDK’s optional semantic adaptation, we can also add a new class that provides an OC method with a non-null return value, as follows:

/// DemoConfig+SwiftyRateKit.h

NS_ASSUME_NONNULL_BEGIN

@interface DemoConfig (SwiftyRateKit)

- (instancetype)initWithType:(NSString *)bizType;

@end

NS_ASSUME_NONNULL_END

/// DemoConfig+SwiftyRateKit.m
#import <SwiftyRateKit/DemoConfig+SwiftyRateKit.h>

@implementation DemoConfig (SwiftyRateKit)

- (instancetype)initWithType:(NSString *)bizType {
    return [self initWithBizType:bizType];
}

@end
Copy the code

Unsafe API

OC apis that are not explicitly written are inherently insecure if they are bridged to Swift. Why do you say that?

Let’s take a real case of online Crash as an example, where the stack is as follows:

Thread 0 Crashed:
0x0000000000000012 Swift runtime failure: Unexpectedly found nil while implicitly unwrapping an Optional value DemoEventHandler.swift
0x0000000000000011 handle DemoEventHandler.swift
0x0000000000000010 handle <compiler-generated>
0x0000000000000009 -[XXXXXXXXXX XXXXXXXXXX:XXXXXXXXXX:XXXXXXXXXX:] XXXXXXXXXX.m
0x0000000000000008 -[XXXXXXXX XXXXXXXX:XXXXXXXX:XXXXXXXX:] XXXXXXXX.m
0x0000000000000007 +[XXXXXXX XXXXXXX:XXXXXXX:XXXXXXX:] XXXXXXX.m
0x0000000000000006 -[XXXXXX XXXXXX:] XXXXXX.m
0x0000000000000005 -[XXXXX XXXXX:] XXXXX.m
0x0000000000000004 -[XXXX XXXX:] XXXX.m
0x0000000000000003 -[XXX XXX:XXX:] XXX.m
0x0000000000000002 -[XX XX:]
0x0000000000000001 -[X X:]
Copy the code

The client implementation code is as follows:

class DemoEventHandler: SwiftyEventHandler { override func handle(event: DemoEvent? , args: [Any], context: DemoContext?) { guard let ret = context? .democtx.engine. value else {return} /// omit useless code}}Copy the code

What caused the Crash was context, right? .democtx.engine. value this code.

The essential reason is that demoCtx does not indicate optional semantics, resulting in implicit unpacking by default when OC bridge connects to Swift. Note the implicitly found nil while wrapping an Optional value Crash occurs during read without a value due to forced unpacking.

To solve this problem, in addition to the SDK’s optional semantic adaptation, we can also make the calling code optional to avoid forced unpacking:

Destructive inheritance

The biggest problem with the basic SDK above is the destructive inheritance of DemoArray.

DemoArray inherits from NSArray and overrides a number of methods, including objectAtIndex:.

It’s clearly defined in the NSArray header file

ObjectAtIndex: this method must return nil, but the SDK returns nil when implementing objectAtIndex: in the DemoArray subclass: objectAtIndex:

This makes it impossible to develop a custom EventHandler using the Swift SDK.

The core reason is that implementing an SDK custom EventHandler first conforms to the DemoEventHandler protocol, Protocol compliance must be implemented – (void)handleEvent:(DemoEvent *)event args:(NSArray *)args context:(DemoContext *)context; This method, because of the NSArray protocol, converts to Swift API ARgs to [Any], as shown in the following figure:

But the type that the SDK passes to DemoEventHandler is essentially a DemoArray type:

Attempt to Insert nil object from Objects [0] will Crash if [Null Null] is present in DemoArray, as shown in the following figure:

The reason is that when handleEvent(_:args: Context 🙂 is called, Swift internal calls a static Array. UnconditionallyBridgeFromObjectiveC (:) to put the args parameter by NSArray into Swift Array, and at the time of call bridge function, NSArray copy -[__NSPlaceholderArray initWithObjects:count:] Because DemoArray’s NSNull is converted to nil, initialization will fail and Crash.

To avoid this problem, it is obviously not practical for the SDK to modify DemoArray because there are so many callers that neither the impact surface nor the cost of regression testing can be assessed in the short term. So you have to add an intermediate layer to solve this problem. We first designed an OC class called DemoEventHandlerBox for wrapping and bridging. The code is as follows:

/// DemoEventHandlerBox.h @class SwiftyEventHandler; NS_ASSUME_NONNULL_BEGIN @interface DemoEventHandlerBox : NSObject<DemoEventHandler> -(instancetype)initWithHandler:(SwiftyEventHandler *)eventHandler; @end NS_ASSUME_NONNULL_END /// DemoEventHandlerBox.m #import <SwiftyRateKit/DemoEventHandlerBox.h> #import < SwiftyRateKit/SwiftyRateKit - Swift. H > @ interface DXEventHandlerBox () / / / event object @ property (nonatomic, strong) SwiftyEventHandler *eventHandler; @end @implementation DemoEventHandlerBox -(instancetype)initWithHandler:(SwiftyEventHandler *)eventHandler { self = [super init]; if (self) { _eventHandler = eventHandler; } return self; } - (void)handleEvent:(DemoEvent *)event args:(NSArray *)args context:(DemoContext *)context { [self.eventHandler handle:event args:args context:context]; return; } @endCopy the code

DemoEventHandlerBox has a SwiftyEventHandler class of type SwiftyEventHandler for logical processing. The code is as follows:

@objcMembers public class SwiftyEventHandler: NSObject { @objc public final func handle(_ event: DemoEvent? , args: NSArray? , context: DemoContext?) { var ret: [Any] = [] if let value = args as? DemoArray { ret = value.origin } else { ret = args as? [Any] ?? [] } return handle(event: event, args: ret, context: context) } func handle(event: DemoEvent? , args: [Any], context: DemoContext?) { return } }Copy the code

SwiftyEventHandler exposes the OC method to final and is logically compatible with switching DemoArray back to NSArray. Finally, all EventHandler implementation classes on the Swift side inherit from SwiftyEventHandler and rewrite the Handle (Event: Args: Context) method. This perfectly avoids the problems caused by destructive inheritance.

Clang Module construction error

The second category of problems is mainly related to dependence. Although the basic infrastructure is complete as mentioned above, there are still some problems.

Dependency updates are not timely

Could not build an Objective-C module. This is usually because the module you are relying on has no adapter module. However, the mobile shopping infrastructure is almost complete. Most libraries are already module-adapted, so you might just need to update Module dependencies to solve this problem.

The STD library, for example, currently relies on version 1.6.3.2, but when your Swift module relies on STD, using 1.6.3.2 will cause it to fail to compile. Your Swift module may need to be upgraded to 1.6.3.3 to fix this problem. The difference between 1.6.3.3 and 1.6.3.2 is essentially a modular adaptation, so you don’t have to worry about side effects.

Dependency issues caused by remixing

Although the Module adaptation mentioned above has solved most of the problems, there are still some abnormal cases, which will be expanded here.

In the process of product evaluation reconstruction, in order to ensure the gradual volume of the project, we made physical isolation of the code and created a new module called SwiftyRateKit, which is a Swift module. But the entry classes for the rating list are all in an OC module called TBRatedisplay. So in order to do tangent flow, TBRatedisplay needs to rely on some implementation of SwiftyRateKit. However, when we started compiling TBRatedisplay with SwiftyRateKit, we ran into the following problem:

Xcode will be exposed to the OC Swift class header file ExpandableFastTextViewWidgetNode statement written by SwiftyRateKit – Swift. H, ExpandableFastTextViewWidgetNode DXFastTextWidgetNode inherited from the TBDinamic’s class.

Because the Clang Module switch (CLANG_ENABLE_MODULES) is not enabled on TBRatedisplay, the following macro definition of SwiftyrateKit-swift. h is not in effect. So don’t know where ExpandableFastTextViewWidgetNode this class is defined:

But when we turn on the Clang Module switch for TBRatedisplay, something even scarier happens. Because the Clang Module switch was not enabled by TBDinamic, @import TBDinamic failed to compile and entered an “infinite loop”. Finally, all OC Module exports that did not support the Clang Module switch had to be removed temporarily.

Here is a more abstract concept, I use a diagram to show the dependency:

First, for a Swift module, dependencies can be imported by importing TBDinamic as long as the module has DEFINES_MODULE = YES turned on and Umbrella Header provided. So SwiftyRateKit can display dependencies without the Clang Module switch being turned on by TBDinamic and can be compiled.

But for an OC module, there are two cases of importing another module.

  • The first is a module with DEFINES_MODULE = YES enabled, which we can import by #import

    .
  • The second option is to import with @import TBDinamic when the Clang Module switch is enabled

Because TBRatedisplay relies on SwiftyRateKit, Xcode automatically generates the SwiftyrateKit-swift. h header file using @import TBDinamic to import the module, which causes the above problem.

Therefore, I personally suggest that at this stage, we should try to avoid or reduce providing the API of a Swift Module to OC, otherwise, all OC modules that this Swift relies on for external API need to be enabled with Clang Module. OC modules that depend on the Swift Module also need to enable Clang Module. Moreover, due to the unequal syntax between Swift and OC, the capability of the interface layer developed by Swift is very limited, thus causing the external API of Swift to become quite uncoordinated.

The class name is the same as Module

In theory there should be no problem with Swift modules calling each other. However, due to the large number of taobao modules and the heavy historical burden, we encountered a helpless and painful problem of “the class name is the same as Module” when we did commodity evaluation transformation.

We have an SDK called STDPop, the Module name of this SDK is also called STDPop, and there is also a tool class called STDPop. What problems does this cause? All Swift modules that rely on STDPop and cannot be used by another Swift module report a magical error: ‘XXX’ is not a member type of class ‘STDPop.STDPop’ Prefix of STDPop. For example, PopManager becomes STDPop.PopManager, but since STDPop is a class called STDPop, the compiler can’t understand whether STDPop is a Module name or a class name.

The only way to solve this problem is to remove or modify the STDPop module.

What are the specific benefits?

After a thorough consideration, we set foot on the path of Swift landing. Although we encountered many unprecedented challenges in the whole process, looking back now, our original technology selection is relatively correct. It is mainly reflected in the following aspects:

The number of codes is reduced, and the Coding efficiency is improved

Thanks to Swift’s strong expressiveness, we can use less code to realize a logic originally implemented with OC. As shown in the figure below, we do not need to write too much defensive programming code, so we can clearly express the logic we want to implement.

At the same time, we rewrote the original 13 expressions realized with OC by Swift, and the changes of the overall code amount are as follows:

Less code means less development time and less opportunity for bugs.

Significantly reduce the cost of cross-review

OC’s peculiar syntax makes it impossible for most other developers to understand the specific logic at all, resulting in the high cost of iOS and Android dual-end cross-review and the frequent two-end logic inconsistencies in many libraries.

When I was doing order migration in New Ultron, I was faced with many inconsistent two-end APIS and complicated taste of some code logic, and many temporary problem checks occurred in the project that affected the rhythm.

Therefore, we adopted Swift & Kotlin mode for development. Since Swift and Kotlin have very similar syntax, there is no pressure for us to cross-review.

At the same time, thanks to the scaffolding used for commodity evaluation, subsequent iterations of requirements were also significantly reduced. Let’s take “Comment Item new share button” as an example:

If you use OC & Java mode, because both ends of the code is unreadable. Therefore, one person from each side is required for requirements review, and about 0.5 people per day are required for various matters including discussion. After the two-end discussion, one person will develop the template, which needs about 1 person per day. In the end, it takes about 2 people per day for both ends to realize the original sharing atomic ability (one of them needs to investigate how to access the sharing SDK every day), so 2 * 0.5 + 1 + 2 * 2 = 6 people per day.

However, if the mode of Swift & Kotlin is adopted, we only need one person to participate in the requirement Review, 0.5 person days. Completed technical research and template development alone for about 3 days. Finally, the written code to see the other end, the other end can directly copy the code and according to the characteristics of their own end for about 1 person. Total 0.5 + 3 + 1 = 4.5 person-days. About 25% less time.

Project stability has been improved

Because there is no better quantitative index, can only talk about feelings.

Firstly, the test raising problems caused by coding problems are significantly reduced. Basically, abnormal branch flows have been considered clearly in the development stage thanks to the optional value design of Swift, and the overall test raising problems are significantly less than when using OC.

Secondly, online problems also decreased significantly, except for the Crash problem mentioned above. Commodity evaluation reconstruction project basically did not happen online problems.

Priority to enjoy technical bonus

Whether It is WidgetKit or DocC, it is obvious that Swift takes precedence over OC in the upgrade of new features and development tool chain inside Apple. Therefore, all students using Swift can quickly use all new features and tools developed by Apple.

At the same time, thanks to Swift’s open source, we can not only learn some good design patterns from the source code, but also locate some difficult problems, no longer need to struggle with unintelligible assembly code.

Summary and Prospect

The above is a summary of our exploration of Swift business landing, hoping to give you a little help in technology selection or exploring pit avoidance. Of course, this is only the beginning, and there are many things worth doing. First of all, we need to improve and standardize Swift coding specifications together, and even precipitate a series of best practices to guide the transformation from OC to Swift at a lower cost. Secondly, we also need to promote the basic SDK to build Swift Layer and continue to optimize the existing Swift toolchain for the aforementioned mixed programming problem. Finally, we need to introduce some good open source libraries to avoid reinventing the wheel, take advantage of the capabilities Apple provides (like DocC), and finally find a Swift best practice for scouring.

Finally, if you are interested in what we do, welcome to join us to build Swift/Kotlin ecology. My contact information is [email protected]. I look forward to your participation.

【 References 】

  • Mp.weixin.qq.com/s/pQiLyl572…
  • Github.com/apple/swift…
  • Mp.weixin.qq.com/s/5SXAozM2c…
  • www.swift.org/documentati…
  • www.jianshu.com/p/0d3db4422…
  • Developer.apple.com/videos/play…
  • madnight.github.io/githut/
  • Mp.weixin.qq.com/s/O5sVO5gVL…

Pay attention to [Alibaba mobile technology] wechat public number, every week 3 mobile technology practice & dry goods to give you thinking!