iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. -- New Features in Xcode 6
- CAN mix-and-match Objective-C code
- CAN import all Objective-C system frameworks (e.g., UIKit)
- CAN import C libraries defined as modules (e.g., Dispatch)
- CAN'T use other libraries directly (e.g., CommonCrypto)
See: Swift and Objective-C in the Same Project
Xcode does not support building static libraries that include Swift code. -- Xcode 6.0 Beta 5 release notes
Add a Swift file to a static library and build:
error: /Applications/Xcode.app/Contents/Developer/Toolchains/
XcodeDefault.xctoolchain/usr/bin/libtool:
unknown option character `X' in: -Xlinker
Follow: http://stackoverflow.com/q/24020986/143378
Drag the framework project into your app project.
- Select your project and then your app target
- Open the Build Phases panel
- Expand Target Dependencies
- Click + and select the framework
From Build Phases:
- Click on the + button at the top left of the panel and select New Copy Files Phase
- Set Destination to Frameworks
- Click + and select the framework
import MyFramework
[I]f you are writing a single-target app, you may not need to specify explicit access control levels at all. -- The Swift Programming Language
- Public: accessible everywhere
- Internal (default): accessible within any source file from the defining module, but not outside of that module
- Private: accessible only within its own defining source file
In Objective-C land:
- Public: declared in public headers
- Internal: declared in project headers
- Private: declared in implementation files
* (without enforcing access control)
No entity can be defined in terms of another entity that has a lower (more restrictive) access level
private struct PrivateThing {}
struct InternalThing {}
public struct PublicThing {
let t1 : PrivateThing
public let t2 : InternalThing
func doSomething(t : PrivateThing) {}
public func doSomethingElse(t : InternalThing) {}
}
Consider a framework with a public function that fetches a string asynchronously.
The function returns a Fetch object that can have a callback for success.
let fetch = fetchSomething().onSuccess { println($0) }
Let's define the access levels of the Fetch class.
class Fetch {
var onSuccess : ((String) -> ())?
var state : FetchState = FetchState.Pending
func onSuccess(onSuccess : ((String) -> ())) -> Self {
self.onSuccess = onSuccess
switch self.state {
case FetchState.Success(let value):
onSuccess(value)
default: break
}
return self
}
func succeed(value : String) {
self.state = FetchState.Success(value)
self.onSuccess?(value)
}
}
public class Fetch {
private var onSuccess : ((String) -> ())?
private var state : FetchState = FetchState.Pending
public func onSuccess(onSuccess : ((String) -> ())) -> Self {
self.onSuccess = onSuccess
switch self.state {
case FetchState.Success(let value):
onSuccess(value)
default: break
}
return self
}
func succeed(value : String) {
self.state = FetchState.Success(value)
self.onSuccess?(value)
}
}
Namespacing is implicit in swift, all classes (etc) are implicitly scoped by the module (Xcode target) they are in. no class prefixes needed -- Chris Lattner
The framework name is the namespace.
When importing a framework types can be accessed implicitly or explicitly.
Consider:
A framework FrameworkA that defines a public type Thing.
An app MyApp that imports FrameworkA.
import FrameworkA
var a : FrameworkA.Thing?
var t : Thing?
The two variables have the same type.
import FrameworkA
public class Thing { }
var a : FrameworkA.Thing?
var t : Thing?
var t2 : MyApp.Thing?
What is the type of t?
What if MyApp also imports a FrameworkB that also defines a public type Thing?
import FrameworkA
import FrameworkB
var a : Thing?
We get a nice error.
'Thing' is ambiguous for type lookup in this context
let t1 : Thing? = nil
^~~~~
FrameworkA.Thing:1:7: note: found this candidate
class Thing
^
FrameworkB.Thing:1:7: note: found this candidate
class Thing
^
Solution: use fully qualified names.
import FrameworkA
import FrameworkB
public class Thing { }
var a : FrameworkA.Thing?
var t : Thing?
var b : FrameworkB.Thing?
t is of type MyApp.Thing.
What if FrameworkB also defines a public type named...
FrameworkA
?
import FrameworkA
import FrameworkB
var a : FrameworkA.Thing?
var b : FrameworkB.Thing?
We get a misleading error: 'Thing' is not a member type of 'FrameworkA'
Workaround: define a typealias somewhere else in MyApp.
import FrameworkA
typealias ThingA = Thing
Then:
import FrameworkA
import FrameworkB
var a : ThingA?
var b : FrameworkB.Thing?
Limit the scope* of types whenever possible.
Even more so for public types.
Don't define a type named like the framework (Haneke oops!).
public static let ErrorDomain = "io.haneke"
public struct HanekeGlobals {
public static let ErrorDomain = "io.haneke"
}
let s = HanekeGlobals.ErrorDomain
public enum ScaleMode {
case Fill
case AspectFit
case AspectFill
}
public struct ImageResizer {
public var scaleMode: ScaleMode
}
public struct ImageResizer {
public enum ScaleMode {
case Fill
case AspectFit
case AspectFill
}
public let scaleMode: ScaleMode
}
Generic types can't define static types. This fails to compile:
public class NetworkFetcher<T> {
public enum ErrorCode : Int {
case InvalidData = -400
case MissingData = -401
case InvalidStatusCode = -402
}
}
Workaround: abuse the globals struct from before.
extend HanekeGlobals {
public struct NetworkFetcher {
public enum ErrorCode : Int {
case InvalidData = -400
case MissingData = -401
case InvalidStatusCode = -402
}
}
}
public class NetworkFetcher<T> {}
Extensions of Objective-C types (NSObject subclasses) are categories and as such need prefixes.
extension UIImage {
public func hnk_hasAlpha() -> Bool { ... }
}
See: http://stackoverflow.com/q/25567743/143378
Easy. Simply import the library in the bridging header:
#import <CommonCrypto/CommonCryptor.h>
More info: Swift and Objective-C in the Same Project
import works with modules. So let's define a module for the C library.
Using CommonCrypto as an example...
- Create a CommonCrypto directory inside the framework directory
- Within, create a module.map file
module CommonCrypto [system] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/
iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/us
r/include/CommonCrypto/CommonCrypto.h"
link "CommonCrypto"
export *
}
- Go to the framework Build Settings
- Find Swift Compiler - Search Paths
- Add the CommonCrypto directory to Import Paths
import CommonCrypto
func MD5(aString : String) -> String {
if let data = aString.dataUsingEncoding(NSUTF8StringEncoding) {
let result = NSMutableData(length: Int(CC_MD5_DIGEST_LENGTH))
let resultBytes = UnsafeMutablePointer<CUnsignedChar>(result.mutableBytes)
CC_MD5(data.bytes, CC_LONG(data.length), resultBytes)
let e = UnsafeBufferPointer<CUnsignedChar>(start: resultBytes, length: result.length)
let MD5 = NSMutableString()
for c in e { MD5.appendFormat("%02x", c) }
return MD5
}
return ""
}
Projects that use the framework must repeat step 2.
The given module map is not platform independent. Is it possible to make the header path relative to the current platform?
Follow: http://stackoverflow.com/q/25248598/143378
Objective-C frameworks can use C libraries without any issues. So let's create one that wraps the C library.
Using CommonCrypto as an example...
- Add a new target to the project.
- Select Cocoa Touch Framework.
- Name it MyFrameworkBridge and select Objective-C as a language.
- Add a public wrapper class or category.
- Import and use the library in the implementation file.
- Import the wrapper header (e.g., NSString+CommonCrypto.h) in the framework header (MyFrameworkBridge.h).
#import "NSString+CommonCrypto.h"
#import <CommonCrypto/CommonCrypto.h>
@implementation NSString(Haneke)
- (NSString*)hnk_MD5String {
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(data.bytes, (CC_LONG)data.length, result);
NSMutableString *MD5String = [NSMutableString
stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[MD5String appendFormat:@"%02x",result[i]];
}
return MD5String;
}
Importing the C library in a public header of the bridging framework will cause a compile error:
include of non-modular header inside framework module 'MyFrameworkBridge'
import MyFrameworkBridge
var s : NSString
...
let MD5 = s.hnk_MD5String
Projects that use the framework must also include the bridging framework.
The wrapper methods of the bridging framework will be visible to projects who use the original framework.
#BTW
If you need to use simple cryptography such as MD5 in a framework, check out CryptoSwift.
## "A limitation of the access control system is that unit tests cannot interact with the classes and methods in an application unless they are marked public." [Xcode 6.0 beta 4 release notes](http://ksm.github.io/SwiftInFlux/docs/beta4.pdf)
We're aware that our access control design isn't great for unit testing (and this was in the release notes), we're evaluating the situation to see what we can do. -- Chris Lattner
Internals are not public API but they're "public" API within the scope of the framework. Do you always work alone?
Helper functions/extensions/types will most likely internal and are great candidates for unit testing.
extension CGSize {
func hnk_aspectFillSize(size: CGSize) -> CGSize { ... }
}
- Add all the framework files to the test target.
- Remove the framework from Target Dependencies and Link Binary With Libraries build phases of the test target (to prevent naming collisions).
You can now test public and internal elements.