building a testable mixed-codebase ios framework · pdf filefrom localytics to google...
TRANSCRIPT
![Page 1: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/1.jpg)
§
Building a testable mixed-codebase iOS Framework
Nikos MaounisiOS Developer @Psy2k
Christos KaraiskosiOS Developer @karaiskc
![Page 2: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/2.jpg)
Outline
• Introduction and Motivation
• Distributing Libraries and Resources
• Design Considerations
• Testability
• Swift and ObjC Coexistence
![Page 3: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/3.jpg)
Introduction
The App Store evolution
![Page 4: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/4.jpg)
Introduction
The App Store evolution
![Page 5: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/5.jpg)
Introduction
New Horizons
• Extend beyond the traditional app experience
• Quick interactions with your app throughout the OS and throughout different devices
![Page 6: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/6.jpg)
IntroductionShared Code
LocationLogging
Networking
ReachabilityAnalytics Resources
LocationLogging
Networking
ReachabilityAnalytics Resources
![Page 7: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/7.jpg)
Motivation
• Be at the edge of innovation, follow changes in the app and device landscape (widget, watch, notifications, Siri, Maps)
• Lay the foundations for the future, with focus on code reuse, testability and self-documentation for new team members
• Prepare for complete and safe transition to Swift, with unit-test assurance
![Page 8: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/8.jpg)
Distributing Libraries and Resources
![Page 9: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/9.jpg)
Static Libraries
• Names of the form libname.a • Archives of object files • Statically linked objects become part of executable: Increase in size • Separate distribution of headers and resources • Changes require re-linking • Swift does not support them
![Page 10: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/10.jpg)
Dynamic Libraries
• Names of the form dynamicLibrary.dylib • Single copy of objects is shared among different applications • Symbol binding performed at runtime. Not being able to find and load a required dylib
will crash your app. • Not part of main executable: Static linker only makes note of the install names of
dylibs that are referenced by symbols in your app • Versioned: dylibs may be updated independently of their consumers (i.e., minor
version updates)
![Page 11: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/11.jpg)
Frameworks
• Names of the form frameworkName.framework • Act as dylib wrappers, inherit all their characteristics • Bundle format, relocatable • Include dynamic library, headers, resources, versioning info • Resources include: NIB files, images, localised strings, etc.
![Page 12: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/12.jpg)
User Frameworks in iOS
• Introduced in iOS 8, support Swift • The official way to package reusable code with resources, without requiring hacks (e.g. static frameworks)
• Due to sandbox restrictions, each application must embed its own copy (along with any further dylib/framework dependencies)
• Frameworks cannot be updated alone, even bumping the minor version requires resubmitting the whole application
• Conversely: They are easier to handle, with the guarantee that the correct version of the framework will run, new versions won’t break anything in previous versions
![Page 13: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/13.jpg)
Design Considerations
![Page 14: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/14.jpg)
Swift vs ObjC in Taxibeat
23.5% 76.5%
26.2% 73.8%
Github Linguist
Passenger
Driver
![Page 15: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/15.jpg)
Our Approach
• Use both ObjC and Swift. Migrate to pure Swift when framework is stable and tested. New classes should be written in Swift.
• Focus on model part of MVC. Determine parts heavily shared among apps and extensions (e.g. location management, networking, logging)
• Treat Framework as a clean room: warnings as errors, unit tests, code reviews, design patterns, double-think before adding functionality
• Code organization. Different project, different repo, same workspace
• Don’t risk client stability. If migration of a class from client to framework is not straightforward, keep both so that nothing breaks and replace only when tests pass.
![Page 16: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/16.jpg)
Project Organization
![Page 17: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/17.jpg)
Extensions
• Check “Allow app extension API only”
• Compiler-enforced restriction on what you can include in framework
• Ensures that what you build can be used in extensions.
ld: warning: linking against a dylib which is not safe for use in application extensions
![Page 18: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/18.jpg)
Architectural Differences
• A framework target can only be built for a single platform, and the respective supported architectures
Solution for deploying to watchOS or tvOS: • Separate framework target for each platform • Use Swift directives (e.g., #if os(watchOS)), different
platforms support different system API subsets
![Page 19: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/19.jpg)
Testability in Mind
![Page 20: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/20.jpg)
The Importance of Tests
• Confidence to refactor
• Test assisted design promotes good API design. Find flaws early and iterate.
• Modularity and loose coupling by definition
• Simulate edge scenarios using fake objects
• Dynamic documentation
![Page 21: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/21.jpg)
Xcode Server Coverage Stats
We created an Xcode Server to “force” ourselves to write tests
![Page 22: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/22.jpg)
Example 1: Unit Testing the Location Manager
![Page 23: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/23.jpg)
Example 1: Unit Testing the Location Manager
• Need to simulate location and authorization scenarios • Override the need for depending on actual hardware
measurements (GPS, A/GPS) • Override the need for user input (Permissions etc)
![Page 24: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/24.jpg)
Example 1: Unit Testing the Location Manager
• Continuous user tracking not always required throughout app lifecycle, battery drain
• iOS >= 9 provides requestLocation() using delegation
• Closure/block-based approach: ease of use, compact, code not scattered around
• Execute callback when sufficiently accurate location is acquired
@objc(TXBLocationManager) public class LocationManager: NSObject {
public func requestLocationOnce(completion: @escaping ((CLLocation) -> Void)) { // LocationManager saves closure, internally handles inaccurate locations, lack of authorization etc.
// when sufficiently accurate location is retrieved, closure is called } }
![Page 25: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/25.jpg)
Example 1: Unit Testing the Location Manager
extension LocationManagerTests { public class MockRequestOnceSuccessLocationManager: CLLocationManager { override class func locationServicesEnabled() -> Bool { return true } override class func authorizationStatus() -> CLAuthorizationStatus { return .authorizedWhenInUse } override public func startUpdatingLocation() { delegate?.locationManager?(self, didUpdateLocations: [CLLocation(latitude: 10.0, longitude: 10.0)]) } } }
In order to unit test our LocationManager we need to “control” the CLLocationManager which is used internally
Step 1: Subclass CLLocationManager (or define a protocol with common functions)
![Page 26: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/26.jpg)
Example 1: Unit Testing the Location Manager
locMgr = LocationManager(locationManagerType: MockRequestOnceSuccessLocationManager.self) // TEST
Step 2: Inject dependency to our LocationManager
init(locationManagerType:CLLocationManager.Type = CLLocationManager.self) { … }
Init for LocationManager
self.locationManagerType.authorizationStatus()
Step 3: Remove hardcoded references of CLLocationManager within LocationManager
CLLocationManager.authorizationStatus()
instead of
Create new instance:
![Page 27: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/27.jpg)
Example 1: Unit Testing the Location Manager
Step 4: Write an asynchronous test
class LocationManagerTests: XCTestCase { var locMgr: LocationManager! var exp: XCTestExpectation!
func testRequestOnceAllowedWhenInUse() {
exp = self.expectation(description: "Request once callback") locMgr = LocationManager(locationManagerType: MockRequestOnceSuccessLocationManager.self) locMgr.requestLocationOnce(completion: { (location) in XCTAssertEqual(location.coordinate.latitude, 10.0) XCTAssertEqual(location.coordinate.longitude, 10.0) self.exp.fulfill() }) self.waitForExpectations(timeout: 2.0, handler: nil)
} }
Test case for successful scenario
![Page 28: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/28.jpg)
Example 2: From Localytics to Google Analytics
• Be careful not to become too coupled with 3rd-party applications
• Unit testing revealed that as consumers we just want to log events
• Consumers don’t care if it’s Google Analytics or Localytics under the hood
- (IBAction)favoriteButtonPressed:(id)sender { [Localytics tagEvent:@"favoriteButtonPressed" attributes:@{……}]; // handle action }
- (IBAction)favoriteButtonPressed:(id)sender { id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"someCategory" action:@"favoriteButtonPressed" label:@"someLabel" value:nil] build]]; }
Tight coupling with
3rd party libraries
![Page 29: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/29.jpg)
Example 2: From Localytics to Google Analytics
• Create an abstraction layer, hide actual implementation details
• Migration transparent to consumer (besides the API key initializer)
• Changes required within single class, not scattered throughout project
+ (void)sendHitToProviderWithName:(NSString *)eventName andParameters:(NSDictionary *)params { [Localytics tagEvent:eventName attributes:params]; }
+ (void)sendHitToProviderWithName:(NSString *)eventName andParameters:(NSDictionary *)params { id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"ui_action" action:eventName label:@"app action" value:nil] build]]; }
- (IBAction)favoriteButtonPressed:(id)sender { [BKAnalyticsHelper sendHitToProviderWithName:@"favoriteButtonPressed" andParameters:@{}]; // handle action }
![Page 30: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/30.jpg)
Swift and ObjC Coexistence
![Page 31: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/31.jpg)
Exposing ObjC Symbols
• Umbrella header, named MyFramework.h • Exposes Objective-C/C classes as public • Similar to Bridging header of mixed-codebase app target
• Should include all the ObjC headers you want disclosed in your public API
• Be sure to mark the header file as Public in the inspector
![Page 32: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/32.jpg)
Exposing Swift Symbols
• Use access control to restrict visibility to parts of your code from code in other source files and modules
• Explicitly mark classes and methods as public if you want them exposed (default is internal)
![Page 33: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/33.jpg)
Namespaces
• Swift supports namespaces • ObjC does not. Convention is 3-character prefixing Solution for exposing Swift symbols:
Seen in ObjC as:
@objc(TXBLogger) public class Logger: NSObject {
public func logDebug(_ params:Any...) { // .... } }
SWIFT_CLASS_NAMED("Logger") @interface TXBLogger : NSObject + (void)logDebug; - (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; @end
![Page 34: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/34.jpg)
Pure Swift Features
• As long as there is ObjC we cannot fully take advantage of Swift’s powerful features
• We could not use optional primitives, associated value enums, structs, tuples, nested classes, default function params etc.
Solution: • Use pure Swift features only for internal Swift classes that do not
interact with ObjC. • If at some point they need to, write wrapper methods (e.g. to expose
a String enum to ObjC)
![Page 35: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/35.jpg)
Delegation in Swift (1/2)
• Handling weak references in delegates
Solution(??): declare delegate var weak, as in ObjC
strong reference to A
![Page 36: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/36.jpg)
Delegation in Swift (2/2)
Solution: Declare the protocol to inherit from class and property weak
delegate released when it goes out of method scope
![Page 37: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/37.jpg)
Being Swifty
• Use guard to exit functions early • Use type inference • Use the trailing closure syntax • Follow case conventions. Names of types and protocols are
UpperCamelCase. Everything else is lowerCamelCase. • Default to structs unless you really need a class • Favor immutable variables
https://swift.org/documentation/api-design-guidelines/
![Page 38: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/38.jpg)
Being Swifty Example 1
•Map reduce filter vs C-style loop NSArray<NSNumber *> *sampleArray = @[@1, @2, @3, @4, @5]; NSMutableArray *finalArray = [[NSMutableArray alloc] init]; for (NSInteger i = 0; i <= sampleArray.count; i++) { NSNumber *iDoubled = [NSNumber numberWithInteger: sampleArray[i].integerValue * 2]; [finalArray addObject:iDoubled]; }
let sampleArray = [1, 2, 3, 4, 5]
let finalArray = sampleArray.map { return $0*2 }
![Page 39: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/39.jpg)
Being Swifty Example 2
Naming conventions
typedef NS_ENUM(NSInteger,TXBStatusViewState) { kStatusViewStateNone, kStatusViewStateWaiting, kStatusViewStateNoAvailableDrivers, kStatusViewStateUnsupportedArea, kStatusViewStateMain, kStatusViewStateAddress, kStatusViewStateBlocked, kStatusViewStateNoInternet };
let statusViewMode: TXBStatusViewState = .statusViewStateBlocked //NOT SWIFTY
typedef NS_ENUM(NSInteger,TXBStatusViewState) { TXBStatusViewStateNone, TXBStatusViewStateWaiting, TXBStatusViewStateNoAvailableDrivers, TXBStatusViewStateUnsupportedArea, TXBStatusViewStateMain, TXBStatusViewStateAddress, TXBStatusViewStateBlocked, TXBStatusViewStateNoInternet };
let statusViewMode: TXBStatusViewState = .blocked //SWIFTY
😭
😍
![Page 40: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/40.jpg)
Being Swifty Example 3
Type Safety[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_badRequestNotification:) name:@"kRequestBadNotification" object:nil];
NotificationCenter.default.addObserver(self, selector: #selector(self.badRequestNotification), name: Notification.Name.requestBad, object: nil)
![Page 41: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/41.jpg)
Coming Soon…
Taxibeat Widget
![Page 42: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/42.jpg)
iOS.Conf app
The .Conf app is built using embedded frameworks for the Watch App
and…
we made it open source on Github https://github.com/taxibeat/ios-conference
![Page 43: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/43.jpg)
iOS.Conf app
we made it open source on Github https://github.com/taxibeat/ios-conference
Questions? enum SupportedLanguages { case # case $ }
![Page 44: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/44.jpg)
Questions
![Page 45: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/45.jpg)
CocoaPods
•Single Podfile for both projects, same workspace
•pod update acts on both projects
source 'https://github.com/CocoaPods/Specs.git' workspace 'MyWorkspace' platform :ios, '9.0' use_frameworks!
target 'MyProject' do project 'MyProject.xcodeproj' pod 'FBSDKCoreKit' pod 'FBSDKLoginKit' pod 'FBSDKShareKit' pod 'FBSDKMessengerShareKit' pod 'Fabric' pod 'Crashlytics' end
target 'MyFramework' do project ‘../iOS-Framework/MyFramework/MyFramework.xcodeproj' pod 'CocoaLumberjack' end
![Page 46: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/46.jpg)
Warnings as errors
•Static Analyzer (ObjC): Localized Strings missing comments
•Treat warning as errors
![Page 47: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/47.jpg)
API Design Example 1: Request Location Once
[[TXBLocationManager sharedInstance] requestLocationOnceWithCompletion:^(CLLocation * _Nonnull location) {
// center map to user location
}];
![Page 48: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/48.jpg)
Mock URLSession
class MockSession: URLSession { var completionHandler:((Data?, URLResponse?, Error?) -> Void)? static var mockResponse: (data: Data?, urlResponse: URLResponse?, error: NSError?) override class var shared: URLSession { return MockSession() } override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { self.completionHandler = completionHandler return MockTask(response: MockSession.mockResponse, completionHandler: completionHandler) } class MockTask: URLSessionDataTask { typealias Response = (data: Data?, urlResponse: URLResponse?, error: NSError?) var mockResponse: Response let completionHandler: ((Data?, URLResponse?, Error?) -> Void)? init(response: Response, completionHandler:((Data?, URLResponse?, Error?) -> Void)?) { self.mockResponse = response self.completionHandler = completionHandler } override func resume() { completionHandler!(mockResponse.data, mockResponse.urlResponse, mockResponse.error) } } }
![Page 49: Building a testable mixed-codebase iOS Framework · PDF fileFrom Localytics to Google Analytics • Be careful not to become too coupled with 3rd-party applications](https://reader033.vdocuments.site/reader033/viewer/2022051721/5a860f267f8b9aa5408cf51d/html5/thumbnails/49.jpg)
Organization
•Separate project and git repo
•Same workspace (already setup due to CocoaPods)
•Alternatives: Subproject, different target [1]
[1] https://developer.apple.com/library/content/technotes/tn2435