I have a weird crash that I managed to reproduce in skeleton app.
I have a framework which uses RxSwift az a cocoapod dependency.
It has a simple class defined:
public final class FWSupplier {
public let psubject = PublishSubject<Int>()
public let bsubject = BehaviorSubject<Int>(value: 0)
public init() { }
public func triggerBehaviour() {
self.bsubject.onNext(1)
}
public func triggerPublish() {
self.psubject.onNext(1)
}
}
I build the framework as an XCFramework and import it into a host app.
In the app I simply instantiate the FWSupplier in a view controller and call triggerPublish and it crashes.
class ViewController: UIViewController {
#IBOutlet var label: UILabel!
let supplier = FWSupplier()
override func viewDidLoad() {
super.viewDidLoad()
supplier.triggerPublish()
}
}
Anyone have any idea what I'm doing wrong?
Screenshot of error
Screenshot of stack
Do not mix release and debug. When you build XCFramework, it depends on the release version rxswift. When you build the host app, it uses the debug version rxswift. There is some #if Debug in the rxswift source code,mixing release and debug may cause a crash. Instead, you should build the release version rxswift framework and add it to the podspec.vendored_frameworks.
Related
How do I implement the Linea Pro SDK when building an app with SwiftUI that has no AppDelegate and no ViewController?
I integrated the SDK as described here How do I use the Linea-Pro SDK for IOS? and I used the DTDevices.h and libdtdev.a files that can be found here https://github.com/matheuscmpm/lineaswift.
Now the class I wrote to try and work with the SDK looks something like this:
import Foundation
class LineaDevice: DTDeviceDelegate {
private let oScanner: DTDevices
init() {
self.oScanner = DTDevices()
self.oScanner.delegate = self
self.oScanner.connect()
}
func barcodeData(barcode: String!, type: Int32) {
print("Barcode: \(barcode!)")
}
public func getConnectionState() -> Int32 {
return self.oScanner.connstate
}
}
In the global scope, right above #main, I initialize this class like so: let oLineaScanner = LineaDevice().
So far, so good. The method oLineaScanner.getConnectionState() returns 2, which means the iOS device successfully connects to the scanner and when I scan a barcode, the device beeps.
However, the method barcodeData - which I assumed should now be getting called by the SDK - does not get called.
Any documentation I could find so far assumes that there is an AppDelegate and a ViewController, which doesn't exist in my SwiftUI project. I assume that that's the issue here. I am relatively new to developing for iOS so I am kinda clueless on how to proceed from this point on.
Is there any way I can make it work like this and if not then how do I make it work?
After some more experimenting, I finally figured it out myself. A ViewController was necessary after all to get it to work.
So I created a new File that looks little something like this:
struct LineaView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> LineaViewController { return LineaViewController() }
func updateUIViewController(_ uiViewController: LineaViewController, context: Context) { }
func makeCoordinator() -> LineaView.Coordinator { return Coordinator(self) }
}
extension LineaView {
class Coordinator {
var parent: LineaView
init(_ parent: LineaView) {
self.parent = parent
}
}
}
class LineaViewController: UIViewController, DTDeviceDelegate {
// function barcodeData() is implemented here and other related logic for that matter. Don't forget to implement viewDidLoad() where you connect to the device
}
Then I just add this LineaView to the View that contains everything else (a TabView in my case) like this:
LineaView().hidden()
That did the trick for me. The App now responds to a barcode scan as intended.
For the unlikely event that anybody else faces this issue, here you go. Hope I spared you the suffering.
Lets say I created swift frameworks on Xcode like below
Wireframe
Entity
I kept all frameworks "Frameworks and Libraries" section on Xcode empty.
However I'm still able to access both from Wireframe -> Entity and Entity -> Wireframe.
// A class belongs to Wireframe
import Entity // <- This does not raise error
public class MyRouter: NSObject {
public func hoge() {
let board = Book()
}
}
// A class belongs to Entity
import Wireframe // <- This does not raise error
public class Book: NSObject {
public var name: String = ""
public override init() {
super.init()
let router = Wireframe.MyRouter()
}
}
Is it possible to prevent importing specific framework to another framework(like showing compile error if tried by writing "import ") that I can assure architecture is not messed up.
Xcode automatically links to frameworks that Swift imports, as described here. But you can disable this with the “Link Frameworks Automatically” build setting.
I encountered some kind of a problem while using a Swift Playground and trying to set up a simple Delegate Design.
The problem seems to be the fact, that protocols can not be marked as public but the source folder in a Playground is considered as a new Module. Therefore I can't find a solution for this problem.
Here is my code so far (IN THE SOURCE FOLDER, THE VIEWCONTROLLER IS IN THE PLAYGROUND FILE)
//MARK: - Imports
import UIKit
//MARK: - Protocols
protocol UIManagerDelegate {
func didChangePage(forward: Bool)
}
//MARK: - Properties & Initialisers
public class UIManager {
// Properties
private let parentView : UIView
private var delegate: UIManagerDelegate
// Initialisers
public init(for view: UIView, delegate: UIManagerDelegate) {
self.parentView = view
self.delegate = delegate
}
}
The error message I get is the following: Initializer cannot be declared public because its parameter uses an internal type. And when trying to mark the protocol as public, this also produces an error.
Do you guys have any idea on how to fix this issue?
Thanks a lot for your help in advance.
make the delegate public
public protocol UIManagerDelegate
iOS 11 recently added a new feature I would like to use but I still need to support older versions of iOS. Is there a way to write the same class twice and have newer versions of iOS use one version of the class and older versions of iOS use the other?
(Note: Originally I used if #available(iOS 11, *) but I had to use it in so many places that I thought it would just be cleaner to have 2 versions of the class if possible. Maybe there's a way of using #availble somehow? I was focused on using #available rather than pre-compiler #IFDEF stuff because it seems like the "available" tags are the preferred way to do it in Swift now?)
protocol Wrapper {
}
extension Wrapper {
static func instantiate(parametersAB: Any*) -> Wrapper{
if #available(iOS 11.0, *) {
return A(parametersA)
} else {
return B(parametersB)
}
}
}
#available(iOS 11.0,*)
class A: Wrapper {
//Use new feature of iOS 11
init(parametersA: Any*) {
...
}
}
class B: Wrapper {
//Fallback
init(parametersB: Any*) {
...
}
}
Now you get your instance by calling Wrapper.instationate(params) and with this you forget about the check in the rest of the code, and uses the new feature if it possible.
This solution is only possible if you can establish the same interface for both classes (even if it is a dummy version of the method).
High quality code follows several principles. One would be the Single Responsible Principle, that states that a class should have only one responsibility, or — as Uncle Bob says — there should be just one reason for a class to change.
Another principle is the Dependency Inversion Principle: A class should not depend on lower level classes, but on abstractions (protocols) that these lower level classes implements. This also means that all dependences must be passed into the class that uses them.
Applied on your question one solution could be:
A view controller has a datasource property that is defined as a protocol.
Several classes implement this protocol, each for different iOS versions.
A class exists which only preps is to select the right version. This version selection can be done in many ways, I stick with #available
The datasource protocol:
protocol ViewControllerDataSourcing: class {
var text:String { get }
}
and it's implementations:
class ViewControllerDataSourceIOS10: ViewControllerDataSourcing {
var text: String {
return "This is iOS 10"
}
}
class ViewControllerDataSourceIOS11: ViewControllerDataSourcing {
var text: String {
return "This is iOS 11"
}
}
class ViewControllerDataSourceIOSUnknown: ViewControllerDataSourcing {
var text: String {
return "This is iOS Unknown"
}
}
The class that selects the right version class:
class DataSourceSelector{
class func dataSource() -> ViewControllerDataSourcing {
if #available(iOS 11, *) {
return ViewControllerDataSourceIOS11()
}
if #available(iOS 10, *) {
return ViewControllerDataSourceIOS10()
}
return ViewControllerDataSourceIOSUnknown()
}
}
and finally the view controller
class ViewController: UIViewController {
var dataSource: ViewControllerDataSourcing?
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = DataSourceSelector.dataSource()
label.text = dataSource?.text
}
}
This is a very simple example that should highlight the different components in charge.
"Two versions of a class" sounds like a base class and two subclasses. You can instantiate the desired subclass based on the system version at runtime by interrogating e.g. ProcessInfo.
I ran into this same problem. I ended up solving this problem by adding #available above the methods that I only wanted to support in a particular version of iOS:
#available(iOS 11.3, *)
func myMethod() { .. }
According to Apple documentation initialize() method Initializes the class before it receives its first message.
Can somebody explain why initialize() is not working in Release build configuration?
For example:
class Test: NSObject {
override class func initialize() {
print("initialize")
}
class func test() {
print("test")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Test.test()
}
}
Output in Debug configuration:
initialize
test
Output in Release configuration:
test
I did a quick test and it looks like in Release configuration + initialize is not called unless you create an instance of the class. However in Debug calling a class method is enough to trigger +initialize. Looks like an undocumented caveat.
Edit:
Even more interesting fact is that for Objective-C project in both Debug and Release configurations calling a class method is enough to trigger + initialize. I would say this is a bug. You might want to file a radar for it.