How to make simple tracker via swizzling in Swift
Issue #568
Code
Swizzle viewDidAppear
1 | import UIKit |
Track in a declarative way
1 | track(ListController.self) { |
Issue #568
1 | import UIKit |
1 | track(ListController.self) { |
Issue #567
1 | import UIKit |
1 | let sections: [Section] = [ |
1 | extension ProfileViewController: AdapterDelegate { |
1 | class AccordionManager<T>: Manager<T> { |
Issue #566
1 | public class HUDContainer: UIVisualEffectView, AnimationAware { |
1 | import UIKit |
1 | import UIKit |
1 | import UIKit |
Issue #564
Step 1: Create executable
1 | swift package init --type executable |
Step 2: Edit package
1 | // swift-tools-version:5.1 |
Step 3: Double click Package.swift, Xcode opens that in a generated project
Step 4: Declare website. Go to Sources/main.swift
1 | import Publish |
Step 5: Create Content folder
Step 6: swift run
Step 7: Copy Output to root and push to GitHub
Issue #562
1 | Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { _ in |
Issue #560
After adding bot to workspace, we’ll get OAuth Access Token and Bot User OAuth Access Token. Use Bot User OAuth Access Token to test drive bot message sending
https://api.slack.com/methods/chat.postMessage/test
The request url is like
1 | https://slack.com/api/chat.postMessage?token=xoxb-7212342835698-890815481123-abcdGgDEFfm2joQs1Vj5mABC&channel=random&text=hello&pretty=1 |
Code from Puma
1 | import Foundation |
Issue #559
1 | public class GetDestinations { |
Issue #558
1 | public class GetDestinations { |
Issue #556
Instead of learning XMLParser, we can make a lightweight version
1 | import Foundation |
Issue #554
A class must inherit from NSObject
, and we have 3 ways to trigger property change
Use setValue(value: AnyObject?, forKey key: String)
from NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
Use willChangeValueForKey
and didChangeValueForKey
from NSKeyValueObserving
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
willChangeValueForKey("myDate")
myDate = NSDate()
didChangeValueForKey("myDate")
}
}
Use dynamic
. See Swift Type Compatibility
You can also use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime if you’re using APIs like key–value observing that dynamically replace the implementation of a method.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
And property getter and setter is called when used. You can verify when working with KVO. This is an example of computed property
class MyObjectToObserve: NSObject {
var backing: NSDate = NSDate()
dynamic var myDate: NSDate {
set {
print("setter is called")
backing = newValue
}
get {
print("getter is called")
return backing
}
}
}
Issue #553
Read Swift asserts - the missing manual
1 | debug release release |
And from Interesting discussions on Swift Evolution
– assert: checking your own code for internal errors
– precondition: for checking that your clients have given you valid arguments.
Issue #551
1 | import Foundation |
1 | let string: String |
1 | let string: String |
1 | #"\((\d+\.)?(\d+\.)?(\*|\d+)\)"# |
Issue #549
1 | public class Sequence: Task { |
Issue #547
1 | func sync<T>(_ work: (@escaping ([T]) -> Void) -> Void) -> [T] { |
Issue #542
See code Puma
1 | Build is UsesXcodeBuild is UsesCommandLine |
1 | /// Any task that uses command line |
1 | Build has Xcodebuild has CommandLine |
1 | public struct Xcodebuild { |
Issue #533
1 | struct ContentView: View { |
Issue #532
1 | func flag(from country: String) -> String { |
Issue #528
A lens is a first-class reference to a subpart of some data type. For instance, we have _1 which is the lens that “focuses on” the first element of a pair. Given a lens there are essentially three things you might want to do
View the subpart
Modify the whole by changing the subpart
Combine this lens with another lens to look even deeper
http://chris.eidhof.nl/post/lenses-in-swift/
1 | struct Person { |
https://iankeen.tech/2018/06/05/type-safe-temporary-models/
https://swiftbysundell.com/articles/defining-testing-data-in-swift/
Use KeyPath to modify struct data
1 | protocol Stubbable: Identifiable { |
Issue #527
1 | import Foundation |
Issue #526
1 | public class Build: UsesXcodeBuild { |
1 | public class Workflow { |
Issue #525
Example Puma
Puma.xcodeproj
as a sub project of our test projectLink Binary with Libraries
, select Puma frameworkPuma has dependencies on PumaCore and PumaiOS, but in Xcode we only need to select Puma framework
In code, we need to explicitly import PumaiOS framework if we use any of its classes
1 | import Foundation |
Instead of dragging Puma as a subproject of TestPuma, we can use workspace, and link Puma frameworks
To avoid signing issue, we need to select a Team for all frameworks
not valid for use in process using Library Validation: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?
Need to set runpath search path, read https://stackoverflow.com/questions/28577692/macos-command-line-tool-with-swift-cocoa-framework-library-not-loaded
Specify LD_RUNPATH_SEARCH_PATHS = @executable_path
in Build Settings
Take a look at Puma -> SPMLibc, there’s header search path
1 | $(SRCROOT)/.build/checkouts/swift-package-manager/Sources/clibc/include |
which is at the .build
folder inside root
So for our TestPuma target, we need to add this header search path with the correct path
1 | $(SRCROOT)/../../.build/checkouts/swift-package-manager/Sources/clibc/include |
Issue #524
1 | /// Any task that uses command line |
Issue #523
In Puma I want to make build tools for iOS and Android, which should share some common infrastructure. So we can organize dependencies like.
Puma -> PumaAndroid, PumaiOS -> PumaCore -> xcbeautify, Files, Colorizer
1 | // swift-tools-version:5.1 |
Issue #522
Sometimes ago I created Puma, which is a thin wrapper around Xcode commandline tools, for example xcodebuild
There’s lots of arguments to pass in xcodebuild, and there are many tasks like build, test and archive that all uses this command.
To avoid passing many parameters into a class, I tend to make an Options
struct to encapsulate all passing parameters. I also use composition, where Build.Options
and Test.Options
contains Xcodebuild.Options
This ensures that the caller must provide all needed parameters, when you can compile you are ensured that all required parameters are provided.
This is OK, but a bit rigid in a way that there are many more parameters we can pass into xcodebuild
command, so we must provide a way for user to alter or add more parameters.
1 | let xcodebuildOptions = Xcodebuild.Options( |
Here is how to convert from Options to arguments to pass to our command. Because each parameter has different specifiers, like with double hyphens --flag=true
, single hyphen -flag=true
or just hyphen with a space between parameter key and value -flag true
, we need to manually specify that, and concat them with string. Luckily, the order of parameters is not important
1 | public struct Xcodebuild { |
Another way is to have a Set<String>
as a container of parameters, and provide common method via protocol extension
1 | /// Any task that uses command line |
Now the call site looks like this
1 | run { |
Issue #518
1 | let userDefaults = UserDefaults(suiteName: suiteName) |
https://developer.apple.com/documentation/foundation/userdefaults/1417339-removepersistentdomain
Calling this method is equivalent to initializing a user defaults object with init(suiteName:) passing domainName, and calling the removeObject(forKey:) method on each of its keys.
Issue #510
Use Dictionary(grouping:by:)
1 | func groups(countries: [Country]) -> [Group] { |
Issue #506
When a function expects AnyPublisher<[Book], Error>
but in mock, we have Just
1 | func getBooks() -> AnyPublisher<[Book], Error> { |
There will be a mismatch, hence compile error
Cannot convert return expression of type ‘AnyPublisher<[Book], Just
The reason is because Just produces Never
, not Error
. The workaround is to introduce Error
1 | enum AppError: Error { |
1 | func getBooks() -> AnyPublisher<[Book], Error> { |
Updated at 2020-11-07 20:30:01
Issue #504
https://twitter.com/NeoNacho/status/1181245484867801088?s=20
There’s no way to have platform specific sources or targets today, so you’ll have to take a different approach. I would recommend wrapping all OS specific files in #if os and just having one target. For tests, you could do something similar, one test target, but conditional tests
Every files are in Sources
folder, so we can use platform and version checks. For example Omnia is a Swift Package Manager that supports iOS, tvOS, watchOS, macOS and Catalyst.
For macOS only code, need to check for AppKit and Catalyst
https://github.com/onmyway133/Omnia/blob/master/Sources/macOS/ClickedCollectionView.swift
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) |
For SwiftUI feature, need to check for iOS 13 and macOS 10.15
https://github.com/onmyway133/Omnia/blob/master/Sources/SwiftUI/Utils/ImageLoader.swift
1 | 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) (iOS |