Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
How we build our app with minimum 3rd party dep...
Search
horimislime
October 04, 2018
Programming
120
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
How we build our app with minimum 3rd party dependencies
bitFlyer Drink Meetup for iOSエンジニア 登壇資料
https://bitflyer.connpass.com/event/100661
horimislime
October 04, 2018
More Decks by horimislime
See All by horimislime
PagerDuty を軸にした On-Call 構築と運用課題の解決 / PagerDuty Japan Community Meetup 4
horimislime
1
440
スタートアップの急成長に寄り添うOn-Call体制構築とその変遷
horimislime
3
2.2k
サポート効率を上げるためのロギング環境構築
horimislime
7
4k
migrating-from-promise-to-reactive
horimislime
0
430
社内Swiftもくもく会成果発表
horimislime
0
170
Swift Optional Extension Tips
horimislime
1
1.8k
ios-internationalization
horimislime
2
9.1k
UI testing in XCode7
horimislime
3
870
UIテストをカジュアルに自動化 / UI Automation using Remote
horimislime
2
2.5k
Other Decks in Programming
See All in Programming
Cloudflare is Agents
chimame
0
180
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
660
Go 1.27からのGODEBUG / Go 1.27 リリースパーティ #go127party
mazrean
0
250
Loosening the Reins: Go Generics Get More Flexible
kuro_kurorrr
0
350
属人化した知識を、 AIが辿れる地図にする
pkshadeck
PRO
1
220
リアルな遅延を測る仕様
kota_yata
1
130
「つくるAI」だけではバグは見つからない ~テストに必要な「見つけるAI」を分離させる戦略~
mfunaki
0
200
AIの中の人になってみる
htkym
0
140
Dockerfile CMD for Node.js
grazie1999
0
140
KotlinConf Extended South Korea 2026 Keynote
l2hyunwoo
0
120
承認済みなのに差戻しできてしまうバグ、型で潰せます
shinchit
0
120
仕様駆動開発へのトライを機に チームに適合する手法を模索し続けている話
freee
PRO
0
650
Featured
See All Featured
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
500
The Mindset for Success: Future Career Progression
greggifford
PRO
0
480
Accessibility Awareness
sabderemane
1
180
Producing Creativity
orderedlist
PRO
348
40k
We Are The Robots
honzajavorek
0
310
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.5k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Transcript
bitFlyerΞϓϦ ͍͔ʹͯ͠ϥΠϒϥϦґଘΛ࠷খݶʹ ։ൃΛߦ͍ͬͯΔͷ͔ גࣜձࣾCJU'MZFS ງݟफҰ
ΞδΣϯμ • bitFlyer ͷΞϓϦͱ։ൃελΠϧͷհ • ϥΠϒϥϦ࠾༻ͰؕΓ͍͢᠘ͱɺ bitFlyerͰͷΞϓϩʔν • ΞϓϩʔνΛৼΓฦͬͯ
bitFlyer ͷΞϓϦͱ։ൃελΠϧͷհ
bitFlyer Wallet • ຊ࠷େͷԾ௨՟औҾॴ CJU'MZFSͷJ04͚ΞϓϦ • ͔Β։ൃ։࢝ • ίʔυϕʔεສߦ
*1 ௐࠪҕୗઌϚΫϩϛϧʢ2018 2 ݄ɺΠϯλʔωοτௐࠪʮԾ௨՟ɾ҉߸௨՟औҾ αʔϏεʹؔ͢ΔΞϯέʔτʯʣɺBitcoin ຊޠใαΠτௐɻ 2016 4݄-2018 4 ݄ɺࠃऔҾॴͷ૯݄ؒग़དྷߴʢݱ/ܾࠩۚࡁ/ઌऔҾΛؚΉʣ
ͲͷΑ͏ʹ࡞͍ͬͯΔ͔ • ݱࡏ4ਓͰ։ൃத • ΞʔΩςΫνϟجຊతʹMVC • 3rd partyϥΠϒϥϦʹཔΓ"͗ͣ͢"։ൃ͍ͯ͠Δ
bitFlyerͰ͍ͬͯΔϥΠϒϥϦ
bitFlyerͰ͍ͬͯΔϥΠϒϥϦ
bitFlyerͰ͍ͬͯΔϥΠϒϥϦ
bitFlyerͰ͍ͬͯΔϥΠϒϥϦ ͘͝Ұ෦Ͱ༻ ࠷ۙ$PEBCMFʹ
ຊ͞ͳ͍͜ͱ • ඪ४SDKݪཧओٛతͳɾOSSͷDisΓ • ϥΠϒϥϦʹͲΕ͘Β͍པΔ͖͔ঢ়گʹΑΔ • ͷݟࠐΊΔαʔϏεɾҰఆͷ։ൃྗ͕͋ΔνʔϜ Ͱࣗલ࣮ͷํ͕ϝϦοτ͕ߴ͍
ຊ͢͜ͱ • ϥΠϒϥϦศར͕ͩɺΉΈʹ͏ͱ͔ͤʹ • Ͳ͜Ͱ᠘ʹؕΓ͍͢ͷ͔ʁ • ͦ͜ͰͲ͏࣮͍ͯ͠Δͷ͔ʁʹ͍ͭͯհ͠·͢
ϥΠϒϥϦ࠾༻ͰؕΓ͍͢᠘ͱ bitFlyerͰͷΞϓϩʔν
ϥΠϒϥϦ࠾༻ͰؕΓ͍͢᠘ͱ bitFlyerͰͷΞϓϩʔν • UIKitͰϥΠϒϥϦΛ͍ͨ͘ͳΔϙΠϯτ 1. UITableViewͷѻ͍ʹ͘͞ 2. ํόΠϯσΟϯά 3. StyleComponentͷऔΓѻ͍
(Font, Color, IB) • Өڹൣғ͕ۃେԽ • ֶशίετɾϩοΫΠϯΛͲ͏ղܾ͢Δ͔ʁ
1. UITableViewͷѻ͍ʹ͘͞
1. UITableViewͷѻ͍ʹ͘͞ • ࣌એݴతUITableView / CollectionViewઓࠃ࣌ʁ • RxDataSources Λ࢝ΊɺiOSDCͰʹ •
ܾఆతͳఆ൪·ͩͳ͘ɺͦΕͧΕҰҰΞϦ • ѻ͍͢͞ͱҾ͖͑ʹύϥμΠϜ่յ͕ى͜Δ
ബ͍ϑϨʔϜϫʔΫͷඋ • ͱ͍͑ૉͷUIKitݫ͍͠ • ࣗલͰܰྔϑϨʔϜϫʔΫΛ࣮͍ͯ͠Δ • ࣮ίετ͔͔Δ͕ɺऔΓճ͘͢͠ ඞཁʹԠ֦ͯ͡ு͍͢͠
ബ͍ϑϨʔϜϫʔΫͷ࣮ final class HistoryViewController: UIViewController { @IBOutlet private weak var
tableView: UITableView! private var entities = [History]() private let dataSource = TableDataSource() private let entitiesProvider = EntitiesProvider<History>() override func viewDidLoad() { super.viewDidLoad() dataSource.mapper.register("HistoryCell") { (cell: HistoryCell, entity: History) in cell.update(with: entity) } dataSource.provider = entitiesProvider tableView.dataSource = dataSource tableView.delegate = self fetchData() }
ബ͍ϑϨʔϜϫʔΫͷ࣮ final class HistoryViewController: UIViewController { @IBOutlet private weak var
tableView: UITableView! private var entities = [History]() private let dataSource = TableDataSource() private let entitiesProvider = EntitiesProvider<History>() override func viewDidLoad() { super.viewDidLoad() dataSource.mapper.register("HistoryCell") { (cell: HistoryCell, entity: History) in cell.update(with: entity) } dataSource.provider = entitiesProvider tableView.dataSource = dataSource tableView.delegate = self fetchData() } EntitiesProvider: ίϯςϯπҰཡͷϚωʔδ TableDataSource: UITableViewDataSourceʹ४ڌ Provider -> TableViewͷڮ͠
ബ͍ϑϨʔϜϫʔΫͷ࣮ final class HistoryViewController: UIViewController { @IBOutlet private weak var
tableView: UITableView! private var entities = [History]() private let dataSource = TableDataSource() private let entitiesProvider = EntitiesProvider<History>() override func viewDidLoad() { super.viewDidLoad() dataSource.mapper.register("HistoryCell") { (cell: HistoryCell, entity: History) in cell.update(with: entity) } dataSource.provider = entitiesProvider tableView.dataSource = dataSource tableView.delegate = self fetchData() } EntitiesProvider: ίϯςϯπҰཡͷϚωʔδ TableDataSource: UITableViewDataSourceʹ४ڌ Provider -> TableViewͷڮ͠
ബ͍ϑϨʔϜϫʔΫͷ࣮ final class HistoryViewController: UIViewController { @IBOutlet private weak var
tableView: UITableView! private var entities = [History]() private let dataSource = TableDataSource() private let entitiesProvider = EntitiesProvider<History>() override func viewDidLoad() { super.viewDidLoad() dataSource.mapper.register("HistoryCell") { (cell: HistoryCell, entity: History) in cell.update(with: entity) } dataSource.provider = entitiesProvider tableView.dataSource = dataSource tableView.delegate = self fetchData() } reloadData()ͨ͠Βclosure͕ݺΕΔ (cellForRowAt૬)
ബ͍ϑϨʔϜϫʔΫͷྑ͞ • ࠷খݶͷ࣮ • ෆඞཁͳ֓೦͕ಋೖ͞Εͳ͍ • ཁ݅ɾҙࣝυϦϒϯͰਐԽͰ͖Δ
2. ํόΠϯσΟϯά
2. ํόΠϯσΟϯά • ೖྗΛ͏6*5BCMF7JFX • 5FYUϑΥʔϜɺબࢶεΠον • #JOEJOHΧοίΑ͘ॻ͚Δ͕ Λ͘͜͢͠Δ •
ෳࡶͳؔΛ͍࢝ΊΔͱ σόοάࠔʹ
TableViewCellͷ࠶ར༻ΛΊͯγϯϓϧʹ • Γ͍ͨͷೖྗʹԠͨ͡Πϕϯτॲཧɾೖྗࢀর • Cellͷ࠶ར༻ΛΊͯ͠·͑ྑ͍ • ೖྗϑΥʔϜͱ༗ݶͳCellͰߏ͞Ε͍ͯΔͣ
CellΛ࠶ར༻͠ͳ͍γϯϓϧͳTableView࣮ final class InputFormViewController: UIViewController { private lazy var userNameCell:
TextFieldCell = { ... }() private lazy var preferenceCell: SwitchCell = { ... }() private lazy var rows: [UITableViewCell] = { [userNameCell, preferenceCell] }() @IBAction private func doneTapped() { API.send( name: userNameCell.textField.text, preference: preferenceCell.toggle.isOn ) } } extension InputFormViewController: UITableViewDataSource { func tableView(…, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return rows[indexPath.row] } }
CellΛ࠶ར༻͠ͳ͍γϯϓϧͳTableView࣮ final class InputFormViewController: UIViewController { private lazy var userNameCell:
TextFieldCell = { ... }() private lazy var preferenceCell: SwitchCell = { ... }() private lazy var rows: [UITableViewCell] = { [userNameCell, preferenceCell] }() @IBAction private func doneTapped() { API.send( name: userNameCell.textField.text, preference: preferenceCell.toggle.isOn ) } } extension InputFormViewController: UITableViewDataSource { func tableView(…, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return rows[indexPath.row] } }
CellΛ࠶ར༻͠ͳ͍γϯϓϧͳTableView࣮ final class InputFormViewController: UIViewController { private lazy var userNameCell:
TextFieldCell = { ... }() private lazy var preferenceCell: SwitchCell = { ... }() private lazy var rows: [UITableViewCell] = { [userNameCell, preferenceCell] }() @IBAction private func doneTapped() { API.send( name: userNameCell.textField.text, preference: preferenceCell.toggle.isOn ) } } extension InputFormViewController: UITableViewDataSource { func tableView(…, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return rows[indexPath.row] } }
CellΛ࠶ར༻͠ͳ͍γϯϓϧͳTableView࣮ final class InputFormViewController: UIViewController { private lazy var userNameCell:
TextFieldCell = { ... }() private lazy var preferenceCell: SwitchCell = { ... }() private lazy var rows: [UITableViewCell] = { [userNameCell, preferenceCell] }() @IBAction private func doneTapped() { API.send( name: userNameCell.textField.text, preference: preferenceCell.toggle.isOn ) } } extension InputFormViewController: UITableViewDataSource { func tableView(…, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return rows[indexPath.row] } } ֤Form Cell໊લͷࢀরΛ͍࣋ͬͯΔ UIύʔπ͕͍࣋ͬͯΔσʔλΛݟʹ͍͘
࠶ར༻ΛΊΔ͜ͱͷྑ͞ • γϯϓϧ • ૉͳ࣮ͳͷͰ୭͕ಡΜͰ͔Γ͍͢ • ొਓΛݶఆ͢ΔͱϝϦοτ͕େ͖͍
3. StyleComponentͷऔΓѻ͍
3. Style ComponentͷऔΓѻ͍ • 5FYU $PMPSͳͲൣғʹӨڹ • /4"UUSJCVUFE4USJOH 6*4UPSZCPBSE͕ΠϚΠν •
ඪ४4%,͕ؤுͬͯ΄͍͠ • 044େͳ ग़యWTPV[BBXFTPNFJPTUFYU
Text, Color Styles • ΞϓϦશମʹίϯϙʔωϯτɾελΠϧΨΠυඋࡁ • NSAttributedStringͷextension initializerͰશςΩετʹ ৭ϑΥϯτͷελΠϧΛద༻
Color Paletteͷ࣮ extension UIColor { private static func bf_blueColor() ->
UIColor { return UIColor(red: 0.26, green: 0.52, blue: 0.75, alpha: 1.0) } private static func bf_lightBlueColor() -> UIColor { return UIColor(red: 0.16, green: 0.64, blue: 0.89, alpha: 1.0) } … static func primaryColor() -> UIColor { return bf_blueColor() } static func secondaryTextColor() -> UIColor { return bf_grayColor() } … }
Color Paletteͷ࣮ extension UIColor { private static func bf_blueColor() ->
UIColor { return UIColor(red: 0.26, green: 0.52, blue: 0.75, alpha: 1.0) } private static func bf_lightBlueColor() -> UIColor { return UIColor(red: 0.16, green: 0.64, blue: 0.89, alpha: 1.0) } … static func primaryColor() -> UIColor { return bf_blueColor() } static func secondaryTextColor() -> UIColor { return bf_grayColor() } … }
Color Paletteͷ࣮ extension UIColor { private static func bf_blueColor() ->
UIColor { return UIColor(red: 0.26, green: 0.52, blue: 0.75, alpha: 1.0) } private static func bf_lightBlueColor() -> UIColor { return UIColor(red: 0.16, green: 0.64, blue: 0.89, alpha: 1.0) } … static func primaryColor() -> UIColor { return bf_blueColor() } static func secondaryTextColor() -> UIColor { return bf_grayColor() } … } ΞϓϦͰ͏৭
Color Paletteͷ࣮ extension UIColor { private static func bf_blueColor() ->
UIColor { return UIColor(red: 0.26, green: 0.52, blue: 0.75, alpha: 1.0) } private static func bf_lightBlueColor() -> UIColor { return UIColor(red: 0.16, green: 0.64, blue: 0.89, alpha: 1.0) } … static func primaryColor() -> UIColor { return bf_blueColor() } static func secondaryTextColor() -> UIColor { return bf_grayColor() } … }
Color Paletteͷ࣮ extension UIColor { private static func bf_blueColor() ->
UIColor { return UIColor(red: 0.26, green: 0.52, blue: 0.75, alpha: 1.0) } private static func bf_lightBlueColor() -> UIColor { return UIColor(red: 0.16, green: 0.64, blue: 0.89, alpha: 1.0) } … static func primaryColor() -> UIColor { return bf_blueColor() } static func secondaryTextColor() -> UIColor { return bf_grayColor() } … } ίϯςΩετͷఆٛ
Text Stylesͷ࣮ class TextStyle { let styleName: String // ελΠϧ໊
var size: CGFloat // ϑΥϯταΠζ var color: UIColor // จࣈͷ৭ enum Weight { case thin, normal, bold } var weight: Weight // จࣈͷଠ͞ var textAlignment: NSTextAlignment // จࣈἧ͑ var kern: CGFloat // จࣈؒͷڑ enum FontType { case proportional, monospaced, compact, monospacedCompact } var fontType: FontType // ϑΥϯτͷछྨ ...
Text Stylesͷ࣮ extension TextStyle { static var body: TextStyle {
return TextStyle("body", size: 17, color: UIColor.primaryTextColor(), weight: .normal, textAlignment: .left, kern: 0.2) } ... }
Text Stylesͷ࣮ extension TextStyle { static var body: TextStyle {
return TextStyle("body", size: 17, color: UIColor.primaryTextColor(), weight: .normal, textAlignment: .left, kern: 0.2) } ... }
Text Stylesͷ࣮ extension TextStyle { static var body: TextStyle {
return TextStyle("body", size: 17, color: UIColor.primaryTextColor(), weight: .normal, textAlignment: .left, kern: 0.2) } ... }
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() }
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() }
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() }
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() } Attributes DictionaryΛੜ
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() }
Text Styleͷద༻ extension NSAttributedString { convenience init(string str: String, style:
TextStyle, tweak: (_ builder: TextStyle) -> Void) { tweak(style) self.init(string: str, attributes: style.build()) } } message.text = NSAttributedString(string: "Hello!", style: .body) name.text = NSAttributedString(string: "Taro", style: .body) { tweak in tweak.color = UIColor.secondaryTextColor() } .bodyʹclosureͷΧελϚΠζΛద༻
Storyboard, Xibͷѻ͍ • SwiftGenͳͲΘͣɺఆ൪ͷprotocol४ڌͷΈ protocol StoryboardInitializable: class { static var
storyboardName: String { get } static func instantiateStoryboard(storyboardName: String?) -> Self } extension StoryboardInitializable where Self: UIViewController { static var storyboardName: String { return String(describing: self) } static func instantiateStoryboard() -> Self { ... } }
Localizable, Assets • LocalizedStringී௨ʹจࣈྻࢦఆ • ิ͕ޮ͔ͳ͍ͷඍົ • վमը໘Ҏ֎ͰࣄނΔϦεΫগͳ͘ࠔΒͳ͍ • Α͘ࠔΔͱͨ͠Β։ൃϑϩʔࣗମʹ͕͋Δ͍ٙ
ΞϓϩʔνΛৼΓฦͬͯ
ΞϓϩʔνΛৼΓฦͬͯ • ଟ͘ͷϥΠϒϥϦ՝ʹରͯ͠ΦʔόʔΩϧͩͬͨ • ࣮ࣗલͰ࣮ͯ͠େม͡Όͳ͔ͬͨέʔεଟ͍ • ߟ͑ൈ͚γϯϓϧɾϕετͳղ๏͕ݟ͔ͭΔ
XcodeΞοϓσʔτָ͕ʹ • ϥΠϒϥϦͷϝϯςঢ়گʹ։ൃ͕ࠨӈ͞Εʹ͘͘ͳͬͨ • Xcode10ରԠεϜʔζ
ඪ४SDKຊ࣭తͳઃܭʹٞΛूதͰ͖Δ
ϥΠϒϥϦ vs ಠ࣮ࣗ νʔϜ։ൃΛݟ͖͔͚ͬ͢ʹͳΔ • ϥΠϒϥϦΛ࠾༻ͯ͠ɺԿΛࢦ͔ͨͬͨ͠ͷ͔ • ෳࡶͳϥΠϒϥϦ͕ඞཁͳͷ༷ʹ͕ͳ͍͔ʁ • σάϨͷසൃศརϥΠϒϥϦͰղܾͰ͖Δͷ͔ʁ
ϥΠϒϥϦ࠾༻ͷצॴ • ͯࣗ͢લ࣮͢Δͷݱ࣮తͰͳ͍ • ͋ͱͰҾ͖͕͍͢͠Ϟϊඅ༻ରޮՌ͕ߴ͍ • ͕໌֬ͳAPIKitɾNukeɻRx͍ํ࣍ୈ • ৫ΤίγεςϜͷਐԽʹద༻͍ͨ͘͢͠͠
·ͱΊ • bitFlyerΞϓϦͱɺͦͷ։ൃελΠϧͷհ • ϥΠϒϥϦ࠾༻ͷҙͱɺগͳ͍ґଘͷϝϦοτ • ·ͩ·ͩ՝ࢁੵΈɺϥΠϒϥϦۙେվम!? ʘ"ຊ࣭"ʹϑΥʔΧε͍ͨ͠ΤϯδχΞɺͥͻฐࣾʹʂʗ