Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
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
0
100
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
Tweet
Share
More Decks by horimislime
See All by horimislime
PagerDuty を軸にした On-Call 構築と運用課題の解決 / PagerDuty Japan Community Meetup 4
horimislime
1
350
スタートアップの急成長に寄り添うOn-Call体制構築とその変遷
horimislime
3
2k
サポート効率を上げるためのロギング環境構築
horimislime
7
3.9k
migrating-from-promise-to-reactive
horimislime
0
400
社内Swiftもくもく会成果発表
horimislime
0
140
Swift Optional Extension Tips
horimislime
1
1.7k
ios-internationalization
horimislime
2
9k
UI testing in XCode7
horimislime
3
840
UIテストをカジュアルに自動化 / UI Automation using Remote
horimislime
2
2.4k
Other Decks in Programming
See All in Programming
開発に寄りそう自動テストの実現
goyoki
2
1.4k
tsgolintはいかにしてtypescript-goの非公開APIを呼び出しているのか
syumai
7
2.3k
Cell-Based Architecture
larchanjo
0
140
gunshi
kazupon
1
110
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
360
これならできる!個人開発のすゝめ
tinykitten
PRO
0
130
新卒エンジニアのプルリクエスト with AI駆動
fukunaga2025
0
230
エディターってAIで操作できるんだぜ
kis9a
0
750
Context is King? 〜Verifiability時代とコンテキスト設計 / Beyond "Context is King"
rkaga
10
1.4k
AIの誤りが許されない業務システムにおいて“信頼されるAI” を目指す / building-trusted-ai-systems
yuya4
6
3.9k
Tinkerbellから学ぶ、Podで DHCPをリッスンする手法
tomokon
0
140
GoLab2025 Recap
kuro_kurorrr
0
780
Featured
See All Featured
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Applied NLP in the Age of Generative AI
inesmontani
PRO
3
2k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
The untapped power of vector embeddings
frankvandijk
1
1.5k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
0
88
WCS-LA-2024
lcolladotor
0
390
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.2k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.6k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.8k
Navigating Weather and Climate Data
rabernat
0
50
Highjacked: Video Game Concept Design
rkendrick25
PRO
0
250
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ΞϓϦͱɺͦͷ։ൃελΠϧͷհ • ϥΠϒϥϦ࠾༻ͷҙͱɺগͳ͍ґଘͷϝϦοτ • ·ͩ·ͩ՝ࢁੵΈɺϥΠϒϥϦۙେվम!? ʘ"ຊ࣭"ʹϑΥʔΧε͍ͨ͠ΤϯδχΞɺͥͻฐࣾʹʂʗ