GraphQL Object mapping Apollo with different schema by target - ios

I have a project with multiple targets.
I have implemented Apollo to request database with GraphQL. Targets have slightly the same schema with some differences: +1 or 2 fields depending targets.
I can successfully build my app because I have changed my build phases to build only *common.graphql and *targetname.graphql
So when target A has AnnuaireFragment like that:
fragment AnnuaireFragment on Directory {
id
test
}
and target B has AnnuaireFragment like that:
fragment AnnuaireFragment on Directory {
id
}
Everything can build, fragments are separated.
I would like that when I use translater I can build too. Can I use only one translater for the two targets?
static func translateFromAnnuaireNode(annuaireNode: GetAnnuaireFromSearchQuery.Data.Directory.Edge.Node) -> ContactModel {
let contactModel = ContactModel()
contactModel._id = annuaireNode.id
// Here that doesn't build for target B because annuaireNode.test doesn't exist
contactModel.test = annuaireNode.test
return contactModel
}
How can I successfully build my app for target A and B ?

When building for target B, the compiler doesn't know about existence of different class definition of GetAnnuaireFromSearchQuery.Data.Directory.Edge.Node, which is specific to target A. Which means that this error is compile-time error, and the only possible way of solving it is addressing it in compile-time.
The possible solution I can come up with is to use different compilation conditions for different targets.
The setup is following:
Under build settings on one of your targets place TARGET_A and under another one place TARGET_B
Use #if to differentiate targets during compile-time
static func translateFromAnnuaireNode(annuaireNode: GetAnnuaireFromSearchQuery.Data.Directory.Edge.Node) -> ContactModel {
let contactModel = ContactModel()
contactModel._id = annuaireNode.id
#if TARGET_A
//The code here will only be compiled when building target A
contactModel.test = annuaireNode.test
#endif
return contactModel
}

Related

Make swift internal function unavailable for Test Target

I want to create a function or convenience init of a class that can not be available for TestTarget when import with #testable import, I am not sure it's possible but looking for any way to restrict it.
class A {
// Should not be accessible in Test Target
func foo() {
}
}
In Testing when #testable import it should not be available.
/**********
UPDATE
***********/
Problem statement
The Long param init method is used with convenience methods to provide default arguments but then in testing, I don't want to access that convenience method with the default argument because it's easy to forget to provide mock depedancy.
There are two solutions to do so.
You can add the following code in your init
#if DEBUG
if let _ = NSClassFromString("XCTest") {
fatalError("Should not be used in unit test.")
}
#endif
With this solution, you can still use this init in your unit test, but it will crash at runtime.
Add a new configuration Testing, you can do this by Select your debug config -> Click + -> Duplicate Debug, and rename it to Testing
then in your main target, add a new flag TESTING in the config Testing.
Next go to Product -> Scheme -> Edit Scheme Set build configuration to Testing for your Test
Finally add this around your init.
#if !TESTING
init() {}
#endif
Now, you should not be able to use this init in your unit test.
Mark that function as Private. Then it will not be available in test target.
You cannot access private entities from another module and this also applies to test targets. That is what access control is for.
You could just import the module as opposed to #testable import.

Xcode Swift 3 Consecutive declarations on line must be separated by ; build error

I have created an Xcode project which has 2 targets (iOS and tvOS).
I want to create a button class which I use on all the scenes throughout each target.
Outside of the two projects I create a myButton.swift file and check the target box for both.
Initially it builds correctly. However, when I add the following lines to the swift file I get the build error:
public class myButton : SKSpriteNode {
var j:Int = 0;
fileprivate class Record {
}
}
The build errors are:
/Users/jer_mac/Documents/WaitingOnAces2/myButton.swift:12:5: Expected declaration
/Users/jer_mac/Documents/WaitingOnAces2/myButton.swift:12:16: Consecutive declarations on a line must be separated by ';'
/Users/jer_mac/Documents/WaitingOnAces2/myButton.swift:9:25: Use of undeclared type 'SKSpriteNode'
Using only one of the targets (tvOS) works, but if I check the iOS target it does not build.
Any help would be appreciated. Thanks.
The error mentioned in your title (Consecutive declarations on line must be separated by ; differs from the error in your question (Use of undeclared type 'SKSpriteNode').
The later can be fixed by importing SpriteKit. The following code (yours) compiles fine:
import SpriteKit
public class myButton : SKSpriteNode {
var j = 0;
fileprivate class Record {
}
}

Enable iCloud on a Xcode project via script

i am trying to setup a build server for a continuous building on a iOS project.
Since that i need to recreate the Xcode project very often (it is a build from unity), the iCloud will be reset to OFF.
I can copy the entitlement file (with the iCloud key) via script but i still need to click on the actual checkbox to turn iCloud ON.
I managed to change the XC project manually but it is not very safe, due to possible changes on XC project structure.
Do you know a better way to do this?
Cheers!
Apparently the TO solved the problem but since there seem to be more people interested in this, here a possible solution.
You can make a script to edit the project.pbxproj which is inside your xcodeproj file (can be viewed e.g. with "show package contents" options in finder).
In this file there's a section for project settings called PBXProject section. There you can add the capabilities for the targets. You probably need to write custom parsing logic, since this file is written in XCode config format, no anything popular like XML or JSON.
The part which you want to update looks like this:
/* Begin PBXProject section */
EB1DDE9C1A3334EC00D778DE /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0610;
/* ...more settings */
TargetAttributes = {
EB1DDEA31A3334EC00D778DE = {/* this is one target */
CreatedOnToolsVersion = 6.1.1;
DevelopmentTeam = 37QAPDY2PR;
/* ...more settings */
};
EB9F6CE11A8812550038355B = {/* another possible target */
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = 37QAPDY2PR;
/* ...more settings */
};
};
};
/* ...more settings */
};
/* End PBXProject section */
You want to add the iCloud capabilities to the targets. This looks like this:
SystemCapabilities = {
com.apple.iCloud = {
enabled = 1;
};
};
So let's say you want to add iCloud capability to the target EB1DDEA31A3334EC00D778DE, then the TargetAttributes entries will look like this:
TargetAttributes = {
EB1DDEA31A3334EC00D778DE = {/* this is one target */
CreatedOnToolsVersion = 6.1.1;
DevelopmentTeam = 37QAPDY2PR;
SystemCapabilities = {
com.apple.iCloud = {
enabled = 1;
};
};
};
EB9F6CE11A8812550038355B = {/* another possible target */
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = 37QAPDY2PR;
/* ...more settings */
};
};
Now there are some things you need to determine in order to do this:
1. Identify the section
I'd make the script look for the string "Begin PBXProject section" and then for "TargetAttributes" both of which are unique in the file. Theoretically "TargetAttributes" should be enough but better to be safe... and remember to add proper logging to the script and verify the results, because these strings could easily change in future XCode versions (I have seen them unchanged, though, a while already).
2. Identify the target
There are multiple parts in this file where you can see the id of the target associated with the name. I would just look this up myself and hardcode it in the script, as this id will not change unless you re-create the target. If you really need it you can also automate this... by looking for your target's name and the format where it appears associated with the id. There should be also other config files where this association appears (in this file the name just appears as a comment).
3. Handle situation that there is already a SystemCapabilities entry for the target, and also that there is already iCloud entry.
If your target has other capabilities this entry may already exist. Also if you already have iCloud enabled or if you had once iCloud enabled and disabled it, the entry will also exist (with a 0 value). This has to be handled in the script (latest should not be a problem if the project file is new though).
Besides of that, you also probably have to add a reference to the entitlements file. You have to add this to the build configurations of the respective targets. For this:
4. Find the build configuration id(s)
Your target has probably multiple build configurations, e.g. debug and release. You have to find the id of the build configuration(s) for which you want to add a reference to the entitlements file. To do this there's a section called XCConfigurationList (look for /* Begin XCConfigurationList section */). There look for the target id we got in 1., Then find the configuration id(s) for the configurations you need.
5. Look for build configuration id in XCBuildConfiguration section
Go to /* Begin XCBuildConfiguration section */ and look for id(s) found in 5., then add path to entitlements to it's buildSettings. E.g:
E.g. you have sth like
EB9F6CF33A861055BB38355B /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B82B36921BDRI3622B0EC99 /* Pods-mytargetname.debug.xcconfig */;
buildSettings = {
/* build settings... */
CODE_SIGN_ENTITLEMENTS = mytargetname/myentitlements.entitlements; /* <-- add this */
};
name = Debug;
};
Note that XCode should "know" the entitlements file (just like the rest of your project files).

Swift Framework does not include symbols from extensions to generic structs

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

Groovy:Apparent variable xyz was found in a static scope but doesn't refer to a local variable, static field or class. Possible causes:

I am getting this "error" in an Spring Tool Suite for all of my domain classes. Its not really an error, because it compiles fine. But it's masking real compile errors, how can I get rid of it ? They are in the same package, so I dont need the import, if I add the import it says it can't find the class...
So the following code produce a red x
Groovy:Apparent variable 'ExampleB' was found in a static scope but doesn't refer to a local variable, static
field or class. Possible causes:
package domain.com.so;
class ExampleA {
static belongsTo = [exampleB: ExampleB]
static constraints = {
}
}
And this code produces a simpilar error:
Groovy:Apparent variable 'ExampleA' was found in a static scope but doesn't refer to a local variable, static field
or class. Possible causes:
package domain.com.so;
class ExampleB {
static hasMany = [exampleAs: ExampleA]
static constraints = {
}
}
Normally when STS complains something like this I run
grails clean
and
grails compile --refresh-dependencies
This happens quite often when I'm making changes to static members and the dynamic reloading is enabled while running the application. I find I have to delete and re-add the project to the workspace. It's almost like the metadata for the project gets in a "stuck" state of when the error occurred during run-time.

Resources