Share
Embed
iOS Development in 2016/2017: upcoming trends review from Andrian Budantsov
Thanks to Apple's efforts and contributions of open source society, iOS is one of the fastest growing mobile platforms nowadays. In talk “iOS development in 2016/2017: upcoming trends review” key trends of iOS development and usage of new technologies will be reviewed.
uaMobiTech
July 28, 2016
Transcript
85 slides
-
• makes iOS apps since the launch of the App
Store; Mac apps since 2015 • 6 large code bases • RD2, ScannerPro, RDPDFKit, SmartMail, Calendars, PrinterPro • ~ million lines of Obj-C code • ~ 50K lines of Swift code -
• Faster & More • With New Tech • Cross-Platform
(Faster & More) • Secure -
• Faster & More Better • With New Tech •
Cross-Platform (Faster & More) • Secure -
Obj-C • Static Analysis • Nullability annotations • Generics •
Build & Analyze • fbinfer -
Obj-C: fbinfer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(observer:) name:UIApplicationDidBecomeActiveNotification object:nil]; NSNumber *n
= [self randomBinaryNumber]; if (n) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "uamobi.tech", kCFStringEncodingASCII); FILE *f = fopen(“~/Desktop/Hello.txt“, "w"); if (!f) { str = (CFStringRef)f; // have no idea } const char *strPtr = CFStringGetCStringPtr(str, kCFStringEncodingASCII); fwrite(str, 1, strlen(strPtr), f); CFRelease(str); } -
Starting analysis (Infer version v0.8.1) FFF.............. Analyzed 3 files Found
4 issues StaticAnalyzerCheck/ViewController.m:15: warning: REGISTERED_OBSERVER_BEING_DEALLOCATED Object self is registered in a notification center but not being removed before deallocation at line 15, column 1. Consider removing the object from the notification center before its deallocation. 13. @end 14. 15. > @implementation ViewController 16. 17. - (NSString * _Nonnull)string { 18. StaticAnalyzerCheck/ViewController.m:34: error: BAD_POINTER_COMPARISON Implicitly checking whether NSNumber pointer n is nil at line 34, column 9. Did you mean to compare against the unboxed value instead? Please either explicitly compare n to nil or use one of the NSNumber accessors before the comparison. 32. 33. FILE *f = NULL; 34. > if (n) { 35. 36. CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "uamobi.tech", kCFStringEncodingASCII); StaticAnalyzerCheck/ViewController.m:45: error: RESOURCE_LEAK resource acquired to f by call to fopen() at line 39, column 13 is not released after line 45, column 9 43. 44. const char *strPtr = CFStringGetCStringPtr(str, kCFStringEncodingASCII); 45. > fwrite(str, 1, strlen(strPtr), f); 46. CFRelease(str); 47. } StaticAnalyzerCheck/ViewController.m:46: error: NULL_DEREFERENCE pointer str last assigned on line 41 could be null and is dereferenced at line 46, column 9 44. const char *strPtr = CFStringGetCStringPtr(str, kCFStringEncodingASCII); 45. fwrite(str, 1, strlen(strPtr), f); 46. > CFRelease(str); 47. } Summary of the reports REGISTERED_OBSERVER_BEING_DEALLOCATED: 1 NULL_DEREFERENCE: 1 BAD_POINTER_COMPARISON: 1 RESOURCE_LEAK: 1 -
Obj-C __auto_type randomDouble = drand48(); __auto_type string = [NSString stringWithFormat:@"%@",
[NSDate date]]; __auto_type rect = self.view.bounds; double randomDouble = drand48(); NSString *string = [NSString stringWithFormat:@"%@", [NSDate date]]; CGRect rect = self.view.bounds; -
Obj-C static UIColor *_carDefaultColor = nil; @interface Car : NSObject
@property (class) UIColor *defaultColor; @end @implementation Car + (UIColor *)defaultColor { return _carDefaultColor; } + (void)setDefaultColor:(UIColor *)defaultColor { _carDefaultColor = defaultColor; } @end - class property - is not synthesized -
Swift is more Swifter • Swifter API Guidelines • Swifter
C functions • Swift Package Manager -
Swifter API Guidelies let blue = UIColor.blueColor() let min =
numbers.minElement() attributedString.appendAttributedString(anotherString) names.insertObject("test", atIndex: 0) UIDevice.currentDevice() let blue = UIColor.blue() let min = numbers.min() attributedString.append(anotherString) names.insert("test", at: 0) UIDevice.current() -
Swifter C Functions let queue = dispatch_queue_create(“tech.uamobi.gcdexample”, nil) dispatch_async(queue) {
print("Hello World") } let queue = DispatchQueue(label: "tech.uamobi.gcdexample") queue.async { print("Hello World") } -
Swift Package Manager import PackageDescription let package = Package( name:
"DeckOfPlayingCards", targets: [], dependencies: [ .Package(url: "https://github.com/apple/example-package-fisheryates.git", majorVersion: 1), .Package(url: "https://github.com/apple/example-package-playingcard.git", majorVersion: 1), ] ) Started by Homebrew author; a bit like NPM -
Realm 1.0 • Easy • Multi-threading • Fast • Encrypted
• Open Source (Almost) -
Realm 1.0 class Dog: Object { dynamic var name =
"" dynamic var age = 0 } class Person: Object { dynamic var name = "" let dogs = List<Dog>() } -
// Create a standalone object let mydog = Dog() //
Set & read properties mydog.name = "Rex" mydog.age = 9 print("Name of dog: \(mydog.name)") // Realms are used to group data together let realm = try! Realm() // Create realm pointing to default file // Save your object realm.beginWrite() realm.add(mydog) try! realm.commitWrite() -
// Query let results = realm.objects(Dog.self). filter(NSPredicate(format:"name contains 'x'")) //
Queries are chainable! let results2 = results.filter("age > 8") print("Number of dogs: \(results.count)") print("Dogs older than eight: \(results2.count)") -
LayoutKit let image = SizeLayout<UIImageView>(width: 50, height: 50, config: {
imageView in imageView.image = UIImage(named: "earth.jpg") }) let label = LabelLayout(text: "Hello World!", alignment: .center) let stack = StackLayout( axis: .horizontal, spacing: 4, sublayouts: [image, label]) let insets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 8) let helloWorld = InsetLayout(insets: insets, layout: stack) helloWorld.arrangement().makeViews(inView: rootView) -
/// A simple hello world view that uses Auto Layout.
public class HelloWorldAutoLayout: UIView { private lazy var imageView: UIImageView = { let imageView = UIImageView() imageView.translatesAutoresizingMaskIntoConstraints = false imageView.setContentHuggingPriority(UILayoutPriorityRequired, forAxis: .Vertical) imageView.setContentHuggingPriority(UILayoutPriorityRequired, forAxis: .Horizontal) imageView.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical) imageView.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Horizontal) imageView.image = UIImage(named: "earth.png") return imageView }() private lazy var label: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Hello World!" return label }() public override init(frame: CGRect) { super.init(frame: frame) addSubview(imageView) addSubview(label) let views = ["imageView": imageView, "label": label] var constraints = [NSLayoutConstraint]() constraints.appendContentsOf(NSLayoutConstraint.constraintsWithVisualFormat("V:|-4-[imageView(==50)]-4-|", options: [], metrics: nil, views: views)) constraints.appendContentsOf(NSLayoutConstraint.constraintsWithVisualFormat(“H:|-4-[imageView(==50)]-4-[label]-8-|", options: [], metrics: nil, views: views)) constraints.append(NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1, constant: 0)) NSLayoutConstraint.activateConstraints(constraints) } } -
Architecture Classics • Dependency Injection • Flow Controllers • MVC
Improvements Always in trend -
Architecture : DI let viewModel = GithubSignupViewModel( input: ( username:
username, password: password, repeatedPassword: repeatedPassword, loginTaps: signupOutlet ), dependency: ( API: GitHubAPI.sharedAPI, validationService: GitHubValidationService.sharedValidationService, wireframe: DefaultWireframe.sharedInstance ) ) ⚠ DI is meaningless without protocols -
Flow Controllers • VC with delegates or blocks • Dependency
Injection of View Controllers -
Improving MVC • POO: Protocols and Extensions • Separation
of Concerns ( Services & Formatters ) • More layers: MVVM • VIPER -
class Store { var counterValue = 0 init() { dispatcher.addObserver(forName:
actionNotification, using: { let action = $0.object as! Action switch(action) { case is ActionIncrease: self.counterValue = self.counterValue + 1 default: self.counterValue = self.counterValue - 1 } self.notifyStoreChange(); }); } -
class Store { // (continued) // incoming actions func dispatch(anAction
: AnyObject) { dispatcher.post(name: actionNotification, object: anAction); } // stores func notifyStoreChange() { dispatcher.post(name: storeNotification, object: self) } func subscribe(_ subscriber: AnyObject, selector: Selector) { dispatcher.addObserver(subscriber, selector: selector, name: storeNotification, object: nil); } -
class ViewController: UIViewController { // ... override func viewDidLoad() {
super.viewDidLoad() self.createUI() store.subscribe(self, selector: #selector(updateNumber)) } func updateNumber(n: Notification) { let num = (n.object as! Store).counterValue valueLabel?.text = "\(num)" } func plusPressed() { store.dispatch(anAction: ActionIncrease()) } func minusPressed() { store.dispatch(anAction: ActionDecrease()) } -
class Store { var state = State(); init() { dispatcher.addObserver(forName:
actionNotification, using: { let action = $0.object as! Action self.state = reduce(state: self.state, action: action) self.notifyStoreChange(); }); } -
func reduce(state: State, action: Action) -> State { var newState
= state; switch(action) { case is ActionIncrease: newState.counterValue = state.counterValue + 1 default: newState.counterValue = state.counterValue - 1 } return newState; } -
New Tech: iOS 10 Call Directory, Intents, Messages, Notification Content,
Notification Service, Stickers, CallKit -
Voice [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) { if (status == SFSpeechRecognizerAuthorizationStatusAuthorized){ SFSpeechRecognizer
*recognizer = [[SFSpeechRecognizer alloc] initWithLocale:[NSLocale currentLocale]]; speechRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init]; speechRequest.taskHint = SFSpeechRecognitionTaskHintSearch; [recognizer recognitionTaskWithRequest:speechRequest delegate:self]; dispatch_async(dispatch_get_main_queue(), ^{ [self startCapture]; }); } }]; -
TrueTone UIWhitePointAdaptivityStyle (9.3 & higher) UIWhitePointAdaptivityStyleStandard Default value. Standard white-point
adaptivity. UIWhitePointAdaptivityStyleReading For reading-focused apps (stronger adaptivity than provided with the UIWhitePointAdaptivityStyleStandard option) UIWhitePointAdaptivityStylePhoto For photography-focused apps (weaker adaptivity than provided with the UIWhitePointAdaptivityStyleStandard option) UIWhitePointAdaptivityStyleVideo For video-focused apps (weaker adaptivity than provided with the UIWhitePointAdaptivityStyleStandard option) UIWhitePointAdaptivityStyleGame For games (weaker adaptivity than provided with the UIWhitePointAdaptivityStyleStandard option) -
More Stuff • SiriKit • Wide Color • App Search
• Core Data / Core Image • SceneKit / SpriteKit • Basic Neural Network Subroutines -
Cross Platform • C++ is the default • Trending: •
Go • Rust • JVM -
Go • Compiled, static, structural • Used mostly for backend
development in practice • Basic Apps (OpenGL) and bind (library integration) -
Go package hello import "fmt" func Greetings(name string) string {
return fmt.Sprintf("Hello, %s!", name) } -
Rust • Compiled, static, object-oriented • Mozilla Servo & System
Programming • can build static libs for arm • Bridge using C API • May work for Models -
Rust ———————— src/lib.rs #[no_mangle] pub extern fn rust_hello_world() -> i32
{ println!("Hello, I'm in Rust code! I'm about to return 10."); 10 } ———————— hello.h #pragma once #include <stdint.h> int32_t rust_hello_world(void); Source: https://www.bignerdranch.com/blog/building-an-ios-app-in-rust-part-1/ -
React • JavaScript (ES 6) • JSX: built-in XML-like extensions
• Manages View Hierarchies • Updates efficiently, using cache (VDOM) -
import React, { Component } from 'react'; import { ActivityIndicator,
AppRegistry, StyleSheet, Text, View } from 'react-native'; class MyComponent extends Component { render() { return ( <View> <ActivityIndicator animating={true} style={styles.actStyle} /> <Text style={styles.instructions}> Hello uamobi.tech </Text> </View> ) } } class ReactNativeAwesomeProject extends Component { render() { return ( <View style={styles.container}> <MyComponent /> </View> ); } } -
React Native Native Components @implementation ActivityIndicator RCT_EXPORT_MODULE() - (UIView *)view
{ return [UIActivityIndicatorView new]; } RCT_EXPORT_VIEW_PROPERTY(hidesWhenStopped, BOOL) RCT_CUSTOM_VIEW_PROPERTY(animating, BOOL, UIActivityIndicatorView) { BOOL animating = json ? [RCTConvert BOOL:json] : [defaultView isAnimating]; if (animating != [view isAnimating]) { if (animating) { [view startAnimating]; } else { [view stopAnimating]; } } } @end -
Security • TLS • Keychain • NSFileProtection • Think what
you do with user data -
Signal Protocol • By Open Whisper Systems • Adopted by:
Google Allo, WhatsApp, Facebook Messenger • Open Source, including iOS app -
Overview • fbinfer, new in Obj-C, new in Swift •
SQLite, Realm, LayoutKit • Architecture: General Notes, Flux/Redux • iOS 10 • Cross-Platform: Go / Rust / Other / React Native • Security: TLS, Signal -
Links • General Reliability • Top 5 Most Frequent Crashes
on iOS • WWDC • Swift API Guidelines • Thread Sanitizer and Static Analysis • Static Analysis • fbinfer -
Links • Libs • LayoutKit (auto-layout alternative) • Realm (Core-Data
alternative) • Architecture: • iOS Application Architecture talk by Krzysztof Zabłocki • iOS Architecture Patterns by Bohdan Orlov • Redux (for JS frontend) • ReSwift (Unidirectional Data Flow in Swift - Inspired by Redux) -
Links • What’s New in iOS 10 • Cross-Platform •
Go Mobile • Building iOS app in Rust • JVM: Parpar VM and BugVM • React Native -
Links • Security • OWASP iOS Developer Cheat Sheet •
Signal iOS app sources