Open Source Project Analysis (SwiftHub) Rxswift + MVVM + Moya Architecture Analysis (II) Use of third-party frameworks (IN)
@[TOC]
2. Third-party library SwiftHub uses
This blog is the sequel of SwiftHub Rxswift + MVVM + Moya Architecture Analysis (a) Third party framework Usage. Due to the length of the process, it is divided into several parts.
Let’s review the third-party framework:
2.1 Rxswift family library
2.1.1 RxAnimated
Source code: RxAnimated
RxAnimated
It provides some predefined animation bindings and provides a flexible mechanism for you to add your own predefined animations and use them when binding with RxCocoa.
2.1.1.1 RxAnimatedBasic animation use
Built-in animations provided by RxAnimated:
When binding values to RxCocoa, you can write:
textObservable
.bind(to: labelFlip.rx.text)
Copy the code
Every time an Observable issues a new string value, it updates the text of the tag. But it happened all of a sudden, without any transition. With RxAnimated, you can use the Animated extension to bind values and animations like this:
textObservable
.bind(animated: labelFlip.rx.animated.flip(.top, duration: 0.33).text)
Copy the code
The “difference is” that you use bind(animated:) instead of bind(to:), and then you insert either animated.flip(.top, duration: 0.33)(or one of the other provided or custom animation methods) between the RX and the property receiver you want to use.
You can easily add custom binding animations to match the visual style of your application.
Step 1 :(optional) if you are activating a new bind receiver without dynamic bindings (such as uiimageview.rx). Image, UILabel. Rx. Text and more are already included, but you’ll need another attribute)
// This is your class `UILabel`extensionAnimatedSinkwhereBase: UILabel{
// This is your property name `text` and value type `String`publicvar text: Binder<String> {
let animation = self.type!
returnBinder(self.base) { label, text in
animation.animate(view: label, block: {
guardlet label = label as? UILabelelse { return }
// Here you update the property
label.text = text
})
}
}
}
Copy the code
Step 2: Add new animation methods
// This is your class `UIView`extensionAnimatedSinkwhereBase: UIView{
// This is your animation name `tick`publicfunctick(_ direction: FlipDirection = .right, duration: TimeInterval) -> AnimatedSink<Base> {
// use one of the animation types and provide `setup` and `animation` blockslet type = AnimationType<Base>(type: RxAnimationType.spring(damping: 0.33, velocity: 0), duration: duration, setup: { view in
view.alpha = 0
view.transform = CGAffineTransform(rotationAngle: direction == .right ? -0.3 : 0.3)
}, animations: { view in
view.alpha = 1
view.transform = CGAffineTransform.identity
})
//return AnimatedSinkreturnAnimatedSink<Base>(base: self.base, type: type)
}
}
Copy the code
Step 3: Now you can subscribe using the new animation binding. It is usually bound this wayUIImageView.rx.imageThe following
imageObservable
.bind(to: imageView.rx.image)
Copy the code
The result is a non-animated binding effect:
If you use your new custom animation binding like this:
imageObservable
.bind(to: imageView.rx.animated.tick(.right, duration: 0.33).image)
Copy the code
The result looks like this:
If you use the same animation on UILabel:
textObservable
.bind(to: labelCustom.rx.animated.tick(.left, duration: 0.75).text)
Copy the code
The effect is as follows:
2.1.1.2 RxAnimatedThe installation
RxAnimated relies on RxSwift 5+.
RxAnimated is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "RxAnimated"Copy the code
2.2 Image processing library
2.2.1 Kingfisher
KingfisherDownload the source code
KingfisherSwift is a powerful, pure swift library for downloading and caching images from the web. It gives you an opportunity to use a purely fast way to process remote images in your next application.
2.2.1.1 KingfisherThe characteristics of
Asynchronous image download and caching.
Based onurlSession of network or locally supplied data loaded images.
Provides useful image processors and filters.
Multi-tier hybrid cache for memory and disk.
Fine control of cache behavior. Customizable expiration date and size limits.
Cancelable downloads and automatic reuse of previously downloaded content to improve performance.
Separate components. Use the downloader, cache system, and image processor separately as needed.
Pre-grab images and display them from the cache to improve your application.
UIImageView.NSImageView.NSButtonandUIButtonView extension to directly fromURLSet the image.
Built-in transition animation when setting images.
Customizable placeholders and indicators for loading images.
Easy to expand image processing and image formats.
SwiftUISupport.
2.2.1.2 KingfisherSimple to use
2.2.1.2.1 Basic Usage
The simplest use case is to use the UIImageView extension to set an image to an image view:
let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)
Copy the code
Kingfisher will download the image from the URL, send it to the in-memory cache and disk cache, and display it in imageView. When you use the same URL Settings later, the image is retrieved from the cache and displayed immediately.
You can also write this if you use SwiftUI:
import KingfisherSwiftUI
var body: some View {
KFImage(URL(string: "https://example.com/image.png")! }Copy the code
In addition, Kingfisher provides advanced usage for solving complex problems, and with these powerful options, you can do difficult tasks with Kingfisher in an easy way. For example, the following code:
let url = URL(string: "https://example.com/high_resolution_image.png")
let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
>> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.indicatorType = .activity
imageView.kf.setImage(
with: url,
placeholder: UIImage(named: "placeholderImage"),
options: [
.processor(processor),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(1)),
.cacheOriginalImage
])
{
result inswitch result {
case .success(let value):
print("Task done for: \(value.source.url? .absoluteString ?? "")")
case .failure(let error):
print("Job failed: \(error.localizedDescription)")}}Copy the code
The code above does this:
Download high resolution images.
Samples down to match the size of the image view.
Make it a rounded corner within a given radius.
Display system indicator and placeholder images at download time.
When ready, it animates the small thumbnails with the fade in and fade out effect.
The original large image is also cached to disk for later use to avoid downloading it again in the detail view.
The console log is printed when the task completes, whether it succeeds or fails.
2.2.1.2.2 Clearing the Cache
funcclearCache(a) {
KingfisherManager.shared.cache.clearMemoryCache()
KingfisherManager.shared.cache.clearDiskCache()
}
Copy the code
2.2.1.2.3 Downloading images to add UI display
Loading pictures shows progress
// Display chrysanthemum
imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: url, placeholder: nil, options: [.transition(ImageTransition.fade(1))], progressBlock: { (receviveeSize, totalSize) inprint("\(receviveeSize)/\(totalSize)")
}) { (image, error, cacheType, imageURL) inprint("Finished")
// Load the completed callback// image: Image? `nil` means failed// error: NSError? non-`nil` means failed// cacheType: CacheType// .none - Just downloaded// .memory - Got from memory cache// .disk - Got from disk cache// imageUrl: URL of the image
}
Copy the code
Set the chrysanthemum style during download
imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: url)
// Use your own GIF image as a download indicatorlet path = Bundle.main.path(forResource: "loader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path)) imageView.kf.indicatorType = .image(imageData: data)
imageView.kf.setImage(with: url)
Copy the code
// Set the disk cache size// Default value is 0, which means no limit.// 50 MBImageCache.default.maxDiskCacheSize = 50 * 1024 * 1024Copy the code
Set cache expiration time (default is 3 days)
// Set the cache expiration time// Default value is 60 * 60 * 24 * 7, which means 1 week.// 3 daysImageCache.default.maxCachePeriodInSecond = 60 * 60 * 24 * 3Copy the code
Set the timeout (default is 15 seconds)
// Default value is 15.// 30 secondImageDownloader.default.downloadTimeout = 30.0Copy the code
Other Settings related
// Set the disk cache size// Default value is 0, which means no limit.// 50 MBImageCache.default.maxDiskCacheSize = 50 * 1024 * 1024// Get the cache disk usageImageCache.default.calculateDiskCacheSize { size inprint("Used disk size by bytes: \(size)")}// Set the cache expiration time// Default value is 60 * 60 * 24 * 7, which means 1 week.// 3 daysImageCache.default.maxCachePeriodInSecond = 60 * 60 * 24 * 3// Set the timeout period// Default value is 15.// 30 secondImageDownloader.default.downloadTimeout = 30.0// Clear cache manually// Clear memory cache right away.
cache.clearMemoryCache()
// Clear disk cache. This is an async operation.
cache.clearDiskCache()
// Clean expired or size exceeded disk cache. This is an async operation.
cache.cleanExpiredDiskCache()
Copy the code
2.2.1.3.2 Custom Usage
Skip cache and force re-download:
imageView.kf.setImage(with: url, options: [.forceRefresh])
Copy the code
Use custom key caching instead of urls
let resource = ImageResource(downloadURL: url! , cacheKey:"kyl_cache_key")
imageView.kf.setImage(with: resource)
Copy the code
Cache and download are used separately:
Kingfisher consists of two main parts, ImageDownloader for managing downloads; ImageCache is used to manage the cache, and you can use one of these separately.
// Use ImageDownloader to download imagesImageDownloader.default.downloadImage(with: url! , options: [], progressBlock:nil) { (image, error, url, data) inprint("Downloaded Image: \(image)")}// Use ImageCache to cache imageslet image: UIImage = UIImage(named: "xx.png")!
ImageCache.default.store(image, forKey: "key_for_image")
// Remove a cached image// From both memory and diskImageCache.default.removeImage(forKey: "key_for_image")
// Only from memoryImageCache.default.removeImage(forKey: "key_for_image",fromDisk: false)
Copy the code
// MARK:- Download images
imageView.kf.indicatorType = .activity
let cachePath = ImageCache.default.cachePath(forKey: PhotoConfig.init().cachePath)
guardlet path = (try? ImageCache.init(name: "cameraPath", cacheDirectoryURL: URL(fileURLWithPath: cachePath))) ?? nilelse { return }
imageView.kf.setImage(with: URL(string: smallUrlStr), placeholder:UIImage(named: "PhotoRectangle") , options: [.targetCache(path)], progressBlock: { (receivedData, totolData) in// Use the progress bar or draw the view, and then use percentage% to indicate the progress//let percentage = (Float(receivedData)/Float(totolData)) * 100.0//print("downloading progress is: \(percentage)%")
}) { result in// switch result {// // case .success(let imageResult):// print(imageResult)// // case .failure(let aError):// print(aError)/ /}
}
Copy the code
Get images to display in advance and add them directly when needed
let urls = ["http://www.baidu.com/image1.jpg"."http://www.baidu.com/image2.jpg"].map { URL(string: $0)! }
let prefetcher = ImagePrefetcher(urls: urls) {
skippedResources, failedResources, completedResources inprint("These resources are prefetched: \(completedResources)")
}
prefetcher.start()
// Later when you need to display these images:
imageView.kf.setImage(with: urls[0])
anotherImageView.kf.setImage(with: urls[1])
Copy the code
In development, we may use HTTPS for the server. If the server is using a certificate authority, we can download the image without special processing. However, if the server is using a self-signed certificate instead of a certificate authority, using Kingfisher to download images requires some certificate authentication.
For those of you who are not familiar with the HTTPS handshake process, it is necessary to understand the HTTPS handshake process to understand the certificate authentication mechanism:
An SSL/TLS handshake is performed before sending an HTTPS request. The handshake process is as follows:
The client initiates a handshake request with parameters such as a random number and a list of supported algorithms.
After receiving the request, the server selects an appropriate algorithm and delivers the public key certificate and random number.
The client verifies the server certificate and sends random number information, which is encrypted using the public key.
The server obtains random number information through the private key.
The session ticket generated by both parties is used as the encryption key for subsequent data transmission of the connection.
In Step 3, the client needs to verify the certificate issued by the server. The verification process includes the following two points:
The client uses the locally saved root certificate to unlock the certificate chain and verify that the certificate issued by the server is issued by a trusted authority.
The client needs to check the domain and extension domains of the certificate to see if they contain the host for this request. If both of the above parameters pass, the current server is trusted; otherwise, it is untrusted and the current connection should be broken.
When a client initiates a request using an IP address, the host in the REQUEST URL is replaced by the IP resolved by the HTTP DNS. Therefore, domain mismatch occurs in step 2 of certificate verification, resulting in an SSL/TLS handshake failure.
For more details, please refer to my previous blog post about HTTPS self-signed certificates uploading and downloading files:
IOS Audio and Video (45) HTTPS self-signed certificates implement side play
HTTPS SSL encryption process for establishing a connection
The diagram below:
Process details:
① The browser of the client sends a request to the server, and transmits the version number of the SSL protocol of the client, the type of encryption algorithm, the generated random number, and other information needed for communication between the server and the client.
② The server sends the SSL protocol version number, encryption algorithm type, random number, and other related information to the client. At the same time, the server also sends its certificate to the client.
(3) the client use the server to get the information of the legality of the authentication server, the server includes: the legality of certificate has expired, release the server certificate CA is reliable, the issuer public key certificate can correctly solve the server certificate of “digital signatures” issuers, domain name whether on the server’s certificate and match the actual domain name server. If the validity verification fails, the communication is disconnected. If the validity is verified, the fourth step will be continued.
④ The client randomly generates a symmetric password for communication, encrypts it with the public key of the server (obtained from the server certificate in Step 2), and sends the encrypted pre-master password to the server.
⑤ If the server requires the customer’s identity authentication (optional during the handshake), the user can create a random number and then sign the data, and send the random number containing the signature to the server together with the customer’s own certificate and the encrypted “pre-master password”.
⑥ If the server requires customer identity authentication, the server must verify the validity of the customer certificate and signature random number. The specific validity verification process includes: Check whether the customer’s certificate use date is valid, whether the CA that provides the certificate is reliable, whether the issuing CA’s public key can unlock the digital signature of the issuing CA, and whether the customer’s certificate is in the Certificate Repeal list (CRL). If the inspection does not pass, communication immediately interrupted; If verified, the server unlocks the encrypted “pre-master password” with its own private key, and then performs a series of steps to generate the master communication password (the client generates the same master communication password in the same way).
⑦ The server and client use the same master password that is “call password”, a symmetric key for SSL protocol encryption and decryption communication of secure data communication. At the same time, the integrity of data communication should be completed during SSL communication to prevent any changes in data communication.
The client sends a message to the server, indicating the following steps of data communication. The master password in ⑦ is a symmetric key, and at the same time notifies the server client that the handshake process is over.
⑨ The server sends a message to the client indicating that the master password in step 7 is a symmetric key and notifies the client that the server-side handshake process is over.
⑩ After the SSL handshake is complete, data communication over the SSL secure channel starts. The client and server use the same symmetric key for data communication and check the communication integrity.
Kingfisher authentication is actually quite simple, with a few lines of code:
// Fetch the downloader singletonlet downloader = KingfisherManager.shared.downloader
// Trust the Server with IP 106
downloader.trustedHosts = Set(["192.168.1.106"])
// Assign a web image to the ImageView using KingFisher
iconView.kf.setImage(with: iconUrl)
Copy the code
R.swift helps us to manage all the icon resources in the project with the same local language. You don’t need to look up the icon name in the resource file every time, and don’t need to look up the key in the multi-language file every time. You can call it as a function, and there is a help prompt.
R. Swift gets strongly typed, autocomplete resources such as images, fonts, and segues in the Swift project.
R.swift gives your code usage resources the following features:
What will the full type, less type conversion and guess method return
Compile-time checks that no more incorrect strings cause your application to crash at run time
Autocomplete, never have to guess the name of the image again
For example, before using R.swift you might write your code like this:
let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let color = UIColor(named: "indicator highlight")
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.current, "Arthur Dent")
Copy the code
With R.swift, you can write code like this:
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let color = R.color.indicatorHighlight()
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")
Copy the code
Here are the official Demo: Examples for use in Realm
See how cool the autofill looks:
Autocomplete pictures:
2.3.1.1.1 R.swift Features:
After installing R.swift into your project, you can use r-struct to access resources. If the structure is outdated, just build and R.swift will correct any missing/changed/added resources.
R. Swift currently supports these types of resources:
Images
Fonts
Resource files
Colors
Localized strings
Storyboards
Segues
Nibs
Reusable cells
2.3.1.2 R.swift use
Images R.swift will find the Images of the asset directory and image files in your package.
R.swift is not used to access images in this way
let settingsIcon = UIImage(named: "settings-icon")
let gradientBackground = UIImage(named: "gradient.jpg")
Copy the code
After using R.swift, access like this:
let settingsIcon = R.image.settingsIcon()
let gradientBackground = R.image.gradientJpg()
Copy the code
In addition, R.swift supports grouping within folders:
Select Provide namespace to group assets result:
let image = R.image.menu.icons.first()
Copy the code
Fonts
Without using r.swift to access:
let lightFontTitle = UIFont(name: "Acme-Light", size: 22)
Copy the code
After using R.swift, access like this:
let lightFontTitle = R.font.acmeLight(size: 22)
Copy the code
Tip: Do system fonts also need this? Take a look at the UIFontComplete library, which has a similar solution for fonts released by Apple for iOS.
Resource files
Without using r.swift to access:
let jsonURL = Bundle.main.url(forResource: "seed-data", withExtension: "json")
let jsonPath = Bundle.main.path(forResource: "seed-data", ofType: "json")
Copy the code
After using R.swift, access like this:
let jsonURL = R.file.seedDataJson()
let jsonPath = R.file.seedDataJson.path()
Copy the code
Colors
Without using r.swift to access:
view.backgroundColor = UIColor(named: "primary background")
Copy the code
After using R.swift, access like this:
view.backgroundColor = R.color.primaryBackground()
Copy the code
// Localized strings are grouped per table (.strings file)let welcomeMessage = R.string.localizable.welcomeMessage()
let settingsTitle = R.string.settings.title()
// Functions with parameters are generated for format stringslet welcomeName = R.string.localizable.welcomeWithName("Alice")
// Functions with named argument labels are generated for stringsdict keyslet progress = R.string.localizable.copyProgress(completed: 4, total: 23)
Copy the code
Storyboards
Without using r.swift to access:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialTabBarController = storyboard.instantiateInitialViewController() as? UITabBarControllerlet settingsController = storyboard.instantiateViewController(withIdentifier: "settingsController") as? SettingsControllerCopy the code
After using R.swift, access like this:
let storyboard = R.storyboard.main()
let initialTabBarController = R.storyboard.main.initialViewController()
let settingsController = R.storyboard.main.settingsController()
Copy the code
Tip: Take a look at the SegueManager library, which makes the Segues block based and compatible with R.FT.
Nibs
Without using r.swift to access:
let nameOfNib = "CustomView"let customViewNib = UINib(nibName: "CustomView", bundle: nil)
let rootViews = customViewNib.instantiate(withOwner: nil, options: nil)
let customView = rootViews[0] as? CustomViewlet viewControllerWithNib = CustomViewController(nibName: "CustomView", bundle: nil)
Copy the code
After using R.swift, access like this:
let nameOfNib = R.nib.customView.name
let customViewNib = R.nib.customView()
let rootViews = R.nib.customView.instantiate(withOwner: nil)
let customView = R.nib.customView.firstView(owner: nil)
let viewControllerWithNib = CustomViewController(nib: R.nib.customView)
Copy the code
On the Reusable Cell Interface Generator Properties check panel, set the cell Identifier field to the same value that you want to register and dequeue.
On the Reusable Cell Interface Generator Properties check panel, set the cell Identifier field to the same value that you want to register and dequeue.
CocoaPods is the recommended installation because it avoids including any binaries in your project.
Note :R.swift is a tool for building steps; it is not a dynamic library. Therefore, it is not possible to install Carthage.
Install CocoaPods(recommended).
addpod 'R.swift'Go to your Podfile and runpod install
In Xcode: Click the project in the file list, select the target under Target, click the Build Phase TAB, and add a new run script phase by clicking the small plus icon in the upper left corner
Drag the new run script stage above the compile source stage and under the Check POD listing. Lock, expand and paste the following script:"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/R.generated.swift"
will$TEMP_DIR/rswift-lastrunAdd to input File, will$SRCROOT/R.generated.swiftAdd to the Output file in the build phase
Set up your project and you’ll see one in FinderR.generated.swiftin$SRCROOTIn the folder, dragR.generated.swiftFile to your project, uncheck Copy if necessary
2.3.2 SwiftLint
SwiftLint
2.4 Key Management library
Against 2.4.1KeychainAccess
KeychainAccess
2.5 Automatic layout library
2.5.1 SnapKit
Source: SnapKit
2.6 UI libraries
2.6.1 NVActivityIndicatorView
Source: NVActivityIndicatorView
2.6.2 ImageSlidershow/Kingfisher
The source code download: ImageSlidershow/Kingfisher
2.6.3 DZNEmptyDataSet
DZNEmptyDataSet
2.6.4 Hero
Source code download: Hero
2.6.5 Localize-Swift
Source code: Localize-Swift
2.6.6 RAMAnimatedTabBarController
The source code download: RAMAnimatedTabBarController