I am having trouble linking my framework with code that takes advantage of that framework. Specifically, the linker isn't able to find the symbols for extensions for generics structs.
This is what one of the extensions looks like for Optional:
extension Optional {
/// Unwrap the value returning 'defaultValue' if the value is currently nil
func or(defaultValue: T) -> T {
switch(self) {
case .None:
return defaultValue
case .Some(let value):
return value
}
}
}
This method works great in a playground or in an app if the code is compiled within the main part of the app. However, when I try to compile this into a Framework, apps (and even the tests for the framework) produce the following linker error:
Undefined symbols for architecture i386: "__TFSq2orU__fGSqQ__FQQ",
referenced from:
__TFC18SwiftPlusPlusTests27Optional_SwiftPlusPlusTests13testOrWithNilfS0_FT_T_
in Optional+SwiftPlusPlusTests.o
Similar methods like the one following, link fine (notice, it is not on a generic)
extension String {
/// Returns a string by repeating it 'times' times
func repeat(times: Int) -> String {
var result = ""
for i in 0..times {
result += self
}
return result
}
}
There are two other extensions within my repository on github: SwiftPlusPlus that also do not link (both on generic strucs). You will reproduce the errors if you pull the latest commit, build the framework, and then try to run the unit tests.
So far I have tried to run "strings" on the outputted framework and intermediate files and I do not see the symbols for these extensions but I do see the symbols for the repeat method extension on String. So it doesn't even seem to be compiling them into the library.
Does anyone know why the symbols are not defined in the framework?
Edit
Here is a link to my Optional Extension
Here is a link to the test file that causes the linker error when trying to compile the test target
I posted on the Apple Developer forums and an Apple employee responded that this is a known bug.
It looks like the compiler gets the mangled symbol names of methods in generic extensions wrong when they live in a different framework.
In case you are looking for a temporary fix, you can wrap the extension in a class method:
// In your framework
public class OptionalOperator {
public class func or<T>(optional:Optional<T>,defaultValue:T) ->T {
return optional.or(defaultValue)
}
}
// Outside the framework
var maybeText:String?
let text = OptionalOperator.or(maybeText, defaultValue: "Apple, please fix this")
Of course, this is not ideal and defeats the purpose of extensions. So if you plan on calling this method frequently, we could overload/define an operator.
// In your framework
infix operator ||| {}
public func |||<T>(left:Optional<T>, right:T) -> T {
return left.or(right)
}
// Outside the framework
var maybeText:String?
let text = maybeText ||| "Apple, please fix this"
In my case, I have multiple applications using the framework, so I'd like to keep the method implementation inside the framework. However, overloading an operator (or just using a global function) would be awkward, so I have to go with the first option until that bug is fixed.
Hope this helps.
UPDATE
Funny thing is that Swift already has an operator for that (??).
var maybeText:String?
let text = maybeText ?? "Nice!"
It's called - Nil Coalescing Operator
Related
I found this code in one of the old projects:
guard let `self` = self else {
return .empty()
}
static let `default`: LayoutParameters = { ..some code.. }
I assume `` was used in older versions of the language. But I would like to know why it is used/was used. Also, I would like to know if there are any problems if do not change the code and "leave it as is" in the latest versions of Swift and Xcode. Does it work correctly? Should I replace this with
guard let self = self else ......
self is a keyword and normally you cannot use keywords and reserved words in places outside their context. However, that sometimes creates problems. For that reason there is a special syntax to make the keyword to be a normal identifier, e.g.:
enum MyEnum: String {
case `default`
}
(also see Swift variable name with ` (backtick))
Historically self was not allowed as as a constant name inside guard-let-else and therefore backticks were commonly (ab)used.
They are no longer needed since Swift 4.2.
The code will still work correctly since you can wrap any identifier in backticks and it will just be a normal identifier.
The Xcode IDE suggestion you using `` to help to use the same default key in Foundation SDK.
Example: default is a constant name in Foundation, if you want using default to create new variable name is default you need add ``.
But you using SwiftLint with default rules, Using a default contants name is a code smell.
I am currently struggling figuring out this problem.
I have a dynamic framework which implements a Network API f.e lets use GitHub.
The dynamic framework implements a class named GitHub with the following method:
func repos() -> Promise<[Repository]>
The promise implementation is my own implementation which is shared with the app target and the dynamic framework target.
In the app target I define a protocol Client.
protocol Client {
func repositories() -> Promise<[RepositoryWrapper]>
}
Then I let GitHub extend from this protocol in order to transform [Repository] to [RepositoryWrapper].
extension GitHub: Client {
func repos() -> Promise<[RepositoryWrapper]> {
return repositories().map({ repos in
repos.map { transform(repository: $0) }
})
}
private func transform(repository: Repository) -> RepositoryWrapper {
return RepositoryWrapper(id: repository.id, nameWithOwner: repository.nameWithOwner)
}
}
However I am currently getting the error message:
Cannot convert return expression of type 'Promise<[RepositoryWrapper]>' to return type 'Promise<[RepositoryWrapper]>'
I am not quite sure how to solve this since the error message states that the types are the same. If the implementation of map on Promise is needed I am happy to extend this question.
Seems like the issue was that I used the Promise implementation by sharing it to both targets. Once I added it only to the framework target and made it public to use it in the app the above implementation started working.
Trying to figure out how to make an app with RxSwift and exploring multiple open source projects (namely CleanArchitectureRxSwift and SwiftHub) I often find usage of
extension ObservableType {
func asDriverOnErrorJustComplete() -> Driver<E> {
return asDriver { error in
return Driver.empty()
}
}
}
Given that this method is useful in many situation and literally copied in mentioned projects I wonder why is it not a part of some utility library (like for example RxSwiftExt) or even RxSwift itself.
I find it really suspicious that given how many Rx pods there are in SwiftHub Podfile none of them actually contain this function.
My question is that are there any real reasons behind that? Does asDriverOnErrorJustComplete somehow violates come Rx contracts or considered bad practice etc?
Am I biased in sense that those two projects are most likely copied architecture from each other and are not representative? If so, are there any good open source projects that demonstrate RxSwift+MVVM and maybe avoid asDriverOnErrorJustComplete or approach problems solved by asDriverOnErrorJustComplete differently?
I wouldn't call the method bad practice per se but it allows for an error that will get silently ignored which I don't particularly like. Using such a construct is rather pernicious in that your chain will silently fail without any notice at all. It could be a problem if your QA department (you with a different hat on?) doesn't notice the fact that the label isn't updating anymore.
I'm also not a big fan of the particular GitHub repos you call out because they add a lot of IMHO unnecessary boilerplate. I prefer code that is more direct.
In my sample app RxEarthquake, I use the following:
public func asDriverLogError(_ file: StaticString = #file, _ line: UInt = #line) -> SharedSequence<DriverSharingStrategy, E> {
return asDriver(onErrorRecover: { print("Error:", $0, " in file:", file, " atLine:", line); return .empty() })
}
So at least a record of the error is made in debug.
I also think the following is an excellent alternative:
public func asDriverOrAbort(_ file: StaticString = #file, _ line: UInt = #line) -> SharedSequence<DriverSharingStrategy, E> {
return asDriver(onErrorRecover: { fatalError("Error: \($0) in file: \(file) atLine: \(line)") })
}
By using such a method, you are making it clear to the reader that you are absolutely sure that the chain won't produce an error.
I think asDriverOnErrorJustComplete is not included in the standard library, because with any type of observable except void, the application will be crashed when receiving an error.
When I started writing SwiftHub, I couldn’t understand why the application crashes when I got an error from the server :)
I'm using the GRDB library to integrate SQLite with my iOS application project. I declared a DatabaseQueue object in AppDelegate.swift like so:
var DB : DatabaseQueue!
In the same file, I had provided a function for connecting the above object to a SQLite database which is called when the app starts running. I had been able to use this in one of my controllers without problems (as in, the app doesn't have problems running using the database I connected to it), like so:
var building : Building?
do {
try DB.write { db in
let building = Building.fetchOne(db, "SELECT * FROM Building WHERE number = ?", arguments: [bldgNumber])
}
} catch {
print(error)
}
However, in another controller, the same construct is met with an error,
Value of optional type 'DatabaseQueue?' must be unwrapped to refer to member 'write' of wrapped base type 'DatabaseQueue'
with the only difference (aside from the code, of course) being that there are return statements inside the do-catch block, as the latter is inside a function (tableView for numberOfRowsInSection) that is supposed to return an integer. The erroneous section of code is shown below.
var locsCountInFloor : Int
do {
try DB.write { db in
if currentBuilding!.hasLGF == true {
locsCountInFloor = IndoorLocation.filter(bldg == currentBuilding! && level == floor).fetchCount(db)
} else {
locsCountInFloor = IndoorLocation.filter(bldg == currentBuilding! && level == floor + 1).fetchCount(db)
}
return locsCountInFloor
}
} catch {
return 0
}
Any help would be greatly appreciated!
As is often the case when you have a problem with a generic type in Swift, the error message is not helpful.
Here’s the real problem:
DB.write is generic in its argument and return type. It has a type parameter T. The closure argument’s return type is T, and the write method itself returns T.
The closure you’re passing is more than a single expression. It is a multi-statement closure. Swift does not deduce the type of a multi-statement closure from the statements in the closure. This is just a limitation of the compiler, for practical reasons.
Your program doesn’t specify the type T explicitly or otherwise provide constraints that would let Swift deduce the concrete type.
These characteristics of your program mean Swift doesn’t know concrete type to use for T. So the compiler’s type checker/deducer fails. You would expect to get an error message about this problem. (Possibly an inscrutable message, but presumably at least relevant).
But that’s not what you get, because you declared DB as DatabaseQueue!.
Since DB is an implicitly-unwrapped optional, the type checker handles it specially by (as you might guess) automatically unwrapping it if doing so makes the statement type-check when the statement would otherwise not type-check. In all other ways, the type of DB is just plain DatabaseQueue?, a regular Optional.
In this case, the statement won’t type-check even with automatic unwrapping, because of the error I described above: Swift can’t deduce the concrete type to substitute for T. Since the statement doesn’t type-check either way, Swift doesn’t insert the unwrapping for you. Then it carries on as if DB were declared DatabaseQueue?.
Since DatabaseQueue? doesn’t have a write method (because Optional doesn’t have a write method), the call DB.write is erroneous. So Swift wants to print an error message. But it “helpfully” sees that the wrapped type, DatabaseQueue, does have a write method. And by this point it has completely forgotten that DB was declared implicitly-unwrapped. So it tells you to unwrap DB to get to the write method, even though it would have done that automatically if it hadn’t encountered another error in this statement.
So anyway, you need to tell Swift what type to use for T. I suspect you meant to say this:
var locsCountInFloor: Int
do {
locsCountInFloor = try DB.write { db in
...
Assigning the result of the DB.write call to the outer locsCountInFloor is sufficient to fix the error, because you already explicitly defined the type of locsCountInFloor. From that, Swift can deduce the return type of this call to DB.write, and from that the type of the closure.
Is there any way to conditionally import libraries / code based on environment flags or target platforms in Dart? I'm trying to switch out between dart:io's ZLibDecoder / ZLibEncoder classes and zlib.js based on the target platform.
There is an article that describes how to create a unified interface, but I'm unable to visualize that technique not creating duplicate code and redundant tests to test that duplicate code. game_loop employs this technique, but uses separate classes (GameLoopHtml and GameLoopIsolate) that don't seem to share anything.
My code looks a bit like this:
class Parser {
Layer parse(String data) {
List<int> rawBytes = /* ... */;
/* stuff you don't care about */
return new Layer(_inflateBytes(rawBytes));
}
String _inflateBytes(List<int> bytes) {
// Uses ZLibEncoder on dartvm, zlib.js in browser
}
}
I'd like to avoid duplicating code by having two separate classes -- ParserHtml and ParserServer -- that implement everything identically except for _inflateBytes.
EDIT: concrete example here: https://github.com/radicaled/citadel/blob/master/lib/tilemap/parser.dart. It's a TMX (Tile Map XML) parser.
You could use mirrors (reflection) to solve this problem. The pub package path is using reflection to access dart:io on the standalone VM or dart:html in the browser.
The source is located here. The good thing is, that they use #MirrorsUsed, so only the required classes are included for the mirrors api. In my opinion the code is documented very good, it should be easy to adopt the solution for your code.
Start at the getters _io and _html (stating at line 72), they show that you can load a library without that they are available on your type of the VM. Loading just returns false if the library it isn't available.
/// If we're running in the server-side Dart VM, this will return a
/// [LibraryMirror] that gives access to the `dart:io` library.
///
/// If `dart:io` is not available, this returns null.
LibraryMirror get _io => currentMirrorSystem().libraries[Uri.parse('dart:io')];
// TODO(nweiz): when issue 6490 or 6943 are fixed, make this work under dart2js.
/// If we're running in Dartium, this will return a [LibraryMirror] that gives
/// access to the `dart:html` library.
///
/// If `dart:html` is not available, this returns null.
LibraryMirror get _html =>
currentMirrorSystem().libraries[Uri.parse('dart:html')];
Later you can use mirrors to invoke methods or getters. See the getter current (starting at line 86) for an example implementation.
/// Gets the path to the current working directory.
///
/// In the browser, this means the current URL. When using dart2js, this
/// currently returns `.` due to technical constraints. In the future, it will
/// return the current URL.
String get current {
if (_io != null) {
return _io.classes[#Directory].getField(#current).reflectee.path;
} else if (_html != null) {
return _html.getField(#window).reflectee.location.href;
} else {
return '.';
}
}
As you see in the comments, this only works in the Dart VM at the moment. After issue 6490 is solved, it should work in Dart2Js, too. This may means that this solution isn't applicable for you at the moment, but would be a solution later.
The issue 6943 could also be helpful, but describes another solution that is not implemented yet.
Conditional imports are possible based on the presence of dart:html or dart:io, see for example the import statements of resource_loader.dart in package:resource.
I'm not yet sure how to do an import conditional on being on the Flutter platform.