How do you create public extensions, in a shared framework, for XCTest? - swift

For example, I never use the description of XCTestCase.expectation, so I'd like to use a function to provide a default for it, and make it clear via naming that I'm initializing the expectation, as you can't really use an initializer for XCTestExpectation. But if the extension is not in a test target, then it can't compile:
Cannot load underlying module for 'XCTest'
import XCTest
public extension XCTestCase {
func makeExpectation() -> XCTestExpectation {
return expectation(withDescription: "")
}
}

I've created an xcworkspace here (https://github.com/dtweston/TestFrameworkSample) that demonstrates a solution to your issue. There are two projects in this workspace:
SampleApp project with an iOS app target and a unit test target.
SharedTestFramework project that imports XCTest and declares the single extension you put in your question.
The SampleAppTests target links to the SharedTestFramework to be able to use the extension it defines. The single test file imports the SharedTestFramework.
With those steps, I also encounter the Cannot load underlying module for 'XCTest' when building the SharedTestFramework.
The fix for that is to update the Framework Search Paths to include "$(PLATFORM_DIR)/Developer/Library/Frameworks". Now the SharedTestFramework compiles correctly, and as you can see in the workspace I uploaded, the SampleAppTests target is able to use it successfully.
Old and busted answer
Are you building a separate framework that is designed to be imported into test targets? If that's the case then I think you just need to reference XCTest.framework from this custom framework you're building.
On the other hand, if you're trying to add this extension to a framework that is used by your app target, that seems like a bad idea, because it would mean linking XCTest.framework to the binary that goes to the store and runs on people's devices.
I'm not sure if that's possible. I'm more confident that it's not a scenario Apple expects or supports.

Related

Add Swift Package to a custom framework

Pretty new to creating frameworks with SPM dependencies. So I made a new framework project, added some of my classes/files as well as a SPM dependency (CocoaLumberjack logger). Framework compiles fine.
When I look for my framework product that I'm planning on embedding into some other project I see that it is in my Products folder. Alongside with it I see CocoaLumberjack module. Inside of my framework there is not much beside the exec file.
When I try to embed my framework into some other projects. Nothing compiles because it says that CocoaLumberjack module is missing.
Does anyone know how to fix this? Am I missing an important step or soemthing?
Well, there are numerous isses you could have faced during importing framework itself. It also depends if you use framework as binary or source code. I assume you were using source code approach as you are the creator of framework. You can however check all approaches here: in this SO question . Lets look at all the steps you need to implement in order to successfully use framework with SPM dependencies in your swift project.
create SPM properly and also link all additional SPM dependencies tutorial here. Make sure all your classes, structs etc. and their coresponding initializer has correct access level property. If you plan to use them outside of the package, use public initializers..
2)Once you created you SPM package, link it to framework. For the sake of this answer I created testFramework and linked one of my custom SPM package called VodApiPackage . This package also contains dependency to another BaseTvApiServicePackage.
I also added TestPrinter file containing simple function for creating error declared in my SPM package. This function servers only for checking that everything is working properly and will be user later. It is also declared public.
import Foundation
import VodApiPackage
public struct TestPrinter {
public init () {}
public func makeTest() {
let x = VodApiError.customErr(msg: "testMsg")
print(x.localizedDescription)
}
}
Open your project and make link to framework, you can also check this nice tutorial. The most important step from tutorial is step 5 and 6. Where you drag .xcproj into your project and link libraries and framework
make sure your library and SPM dependencies are correctly linked in your project. Check sample project below.
Build and test using your framework and its packages:
import UIKit
import testFramework
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
testmodel()
TestPrinter().makeTest()
}
}

How does importing work in swift?

If i have a function, struct, enum or class in another file/nested folder
how do i import it to use in another file?
I'm a javascript dev and i've decided to pick up swift, so i'm still trying to wrap my head around this.
NOTE: I'm not building ios/macOS apps. I'm trying to build linux cli software.
If this is the same target - you don't import anything, you should be able to use it without problems.
If you have separate targets (e.g. a Framework for a library) you should do import ModuleName to get access to the code.
By default things are internal which means you can access them in the whole module. You can't if they're private or fileprivate
write "var main = Main()" any where you like to use it, then use main.getArgs
// by writing that you define instance of the struct

How to make a Swift framework submodule really private?

I've found another question which brings more details regarding the problem and possible solutions. It seems like there is a known bug which is a subject for future improvements.
Objective C classes within an iOS Swift-based dynamic framework
I'm developing a framework in Swift and I'm using some Objective-C code inside the framework. So far my module map looks like this:
framework module MyModule {
umbrella header "MyModule-umbrella.h"
export *
explicit module Private {
header "MyTools.h"
}
}
My concern is that all the APIs from MyTools.h are visible from outside the framework: for example, if you install the framework using Cocoapods, then you import MyModule into your application (not MyModule.Private), you are able to access MyTools.h which is not desirable and redundant. Is there any way to make MyTools invisible from outside the framework?
PS. I use Cocoapods to distribute the framework, here is my podspec (the most significant part):
s.module_map = 'Pod/MyModule.modulemap'
s.frameworks = 'CoreData', 'CoreTelephony', 'SystemConfiguration'
s.resources = 'Pod/Classes/MessageStorage/*.xcdatamodeld'
s.public_header_files = 'Pod/Classes/**/*.h'
s.private_header_files = 'Pod/Classes/MyTools/**/*.h'
s.source_files = 'Pod/Classes/**/*.{h,m,swift}'
PSS. My umbrella header does not import MyTools.h
PSSS. Just tried to exclude the header from the main module:
framework module MyModule {
umbrella header "MyModule-umbrella.h"
export *
exclude header "MyTools.h"
explicit module Private {
header "MyTools.h"
}
}
No luck.
I found another question which brings more details regarding the problem and possible solutions (which don't work though). It seems like there is a known bug which is a subject for future improvements.
Objective C classes within an iOS Swift-based dynamic framework
I had exactly the same problems recently. The quick answer is you can't :) Even if you declare "private" modulemap, it can be always imported by your framework users. Please note, that usually, it is not a concern, especially with open source. You just say "this is an internal module, don't use it".
But (there is always but) - you can have behavior, that effectively works the same - allows you to use your Objective-C classes withing same framework target, without making them public. It works in closed source setup, I'm not 100% sure how would it behave with pods.
The case a bit too complex to paste everything here. I'm adding a link to my article about the topic, maybe it will help you. But speaking honestly - it might be a bit of overhead in your setup.
Creating Swift framework with private Objective-C members. The Good, the Bad, and the Ugly
Github example project

CocoaLumberjack Swift framework

I'm currently writing some swift libraries to be included in an App that uses CocoaLumberjack to log.
So initially I've added CocoaLumberjack as a dependency to all of them and it works quite well.
Then I've seen this ticket where they say, that you should not add it as a dependency, but use if it is there.
Despite that I've already seen some projects on GitHub where they do exactly that in Objective-C, I haven't seen it yet in Swift.
Can somebody point me to a sample project or help me to find the right direction to take
THX
Your should add CocoaLumberjack/Swift as a dependency if your library uses it as a logger.
But your library code should not add any loggers (DDTTYLogger, DDFileLogger, etc.) to avoid log duplication.
Adding loggers should be done in final application that uses your library.
For library itself it could be test bundle with tests:
class YourKitTests: XCTestCase {
override func setUp() {
super.setUp()
DDLog.add(DDTTYLogger.sharedInstance(), with: .verbose)
}
}

Including a static library inside a dynamic framework in iOS

I needed to create a framework (which requires a static library) for a project I'm working on. I used this tutorial to create the framework, then copied the static library into the project and it worked.
But, when I dragged the framework to an iOS project, it shows a ton of errors.
`Undefined symbols for architecture i386:"_OBJC_CLASS_$_SomeClassFromTheStaticLibrary",referenced from:_OBJC_CLASS_$_AnotherClass in MyFramework`
What I think is happening is that the iOS project wants to recompile the framework and it cannot, because it can't locate the static library. All errors disappear if I add the static library to the iOS project. This is what I want to avoid.
Basically I want to have the iOS project -> Framework -> Library instead of having the library in both the project and the framework.
I have tried adding the static library as a resource in the framework, but it didn't work.
I doubt this is possible. When you think about what is happening you will see the problem.
The framework is compiled and the static library is processed so that things like extra symbols are stripped out
The app is now compiled and linked against the framework which may or may not have had the symbols that the app is requiring
I did get this to work if ONLY the framework was using the static library (logical) but I can't find a way to share the code across the framework & the app.
If a symbol is hidden (either via Symbols Hidden by Default/GCC_SYMBOLS_PRIVATE_EXTERN being set to YES or __attribute__ ((visibility ("hidden"))) being applied to certain symbols), then that symbol will be available when statically linking the library, but not when dynamically linking the framework.
Ensure that the static library's symbols are not hidden, and you should be able to access them from your app.
I have followed this link to create custom framework. I have static library inside my framework and it works fine with that.
I have copied his steps in my blog for my understanding along with a script to make it universal.