LIpsとCartunesのCEOに聞くコミュニティ形成の方法
どこに、流動性があるのかを見つける->Lipsでは、SNSとして使われているのではなくECサイトとして作られている
サービス開始時 一気にユーザーを増やすと、ノイズになって検証がしにくい 熱狂的なファンがとごに刺さってるかを見つける 有名人がいると、投稿がしにくい→一気にスケールすることを目指すんじゃなくて5~7%の成長率になることを目指す。 そのためにも、コアなユーザーと向き合っていく必要がある。
スケールする方法 投稿者かつ閲覧者をつくる →投稿する動向付け カーチューンなら、愛車を選ぶで、そのまま投稿にまで持って行ってる 1発目の投稿とコア層の投稿を同じレベルにする(コアな人投稿していて、投稿しにくい環境ではなく誰でもできるアットフォームな環境作り 短期で熱くなる人は、ターゲットに入れない→冷めやすい
成長率をおう、オーガニックで伸びるようにする。 ひとりあたりの配信量、配信時間をいかに伸ばすかも課題となってくる `Yconbinator`メディア→スケールしないことを続ける 登録がいらないところは登録させてない→最初は、その壁をも乗り越えるファンだけ囲うためにもするのはいるかも! コミュニティは、みんなで作っている感を出すためにもチャットから生まれたものから派生する方がいい。 企業側からやるんじゃなくて、ユーザー側から選択的に出来たらいい!そこに支援する感じ
アーカイブ時に起こったエラー ITMS-90037 The Info.plist file is missing or could not be parsed while app store submittion
今回、Appの登録のためにアーカイブを行い、Appstoreに登録を行おうと思いました。 しかし、ITMS-90037 The Info.plist file is missing or could not be parsed while app store submittionというエラーがおこりました。今回は、この解決策を書いていきたいと思います。 調べて見ると、以下の解決法が挙げられました。
- Expand Build settings は YES になってるか
- output encoding は binary になっているか
- 別のディレクトリに Info.plist ファイルが複数あるとダメ この3つは、問題がありませんでした。
そこで、vimでInfo.plistを開きました。fileの開きかたは、vim info.plist
で開くことが可能です。
vimで、plistを開いて見ると^R
という文字が入っていました。それを消去することでエラーが治りました。
Swifにおける依存性の注入(DI)について Swinjectを使って紐解く
この記事では、Swiftにおける依存性の注入についてをまとめていきます。
Dependency Injection
Dependency Injectionを直訳されたのが依存性の注入
です。
正直、依存性の注入と言われてもさっぱり意味がわかりません。
そこで、調べて見ることにしました。
Dependencyは、依存性と訳されることが多いですが、Objectと考える方がわかりやすい
とあります。
確かに、objectを注入すると考えると分かりやすいかもしれません。
必要なobjectを毎回初期化して利用するのではなく、
1つの場所でObjectを初期化しておき必要な時はそこからObjectを受け取るというイメージです。
今回利用するフレームワークのSwinject以外にもsharedを使って行うことがあります。
DIを行うことで、テストが簡単に書くことが出来るなどのメリットがあります。
では、実際のコードを見ながらDIについて考えたいと思います。 ここでは、 DIを行うフレームワークのSwinjectを使ってみました。
//ViewControllerAssembly import UIKit import Swinject final class ViewControllerAssembly: Assembly { func assemble(container: Container) { //MARK: -ReaderViewController container.register(ReaderViewController.self) { resolver in let viewController = UIStoryboard.instantiateViewController(of: ReaderViewController.self) viewController.viewModel = resolver.resolve(ReaderViewModel.self)! return viewController } } }
containerは、複数個のオブジェクトを管理するものです。DIで実装していくと引数が多くなるため同じオブジェクトをまた生成するというコストを削減させることができる。
そのcontainerに、container.registerでViewControllerに登録して行きます。
instantiateViewController
はstoryboardからViewControllerを作成する方法です。Swinjectでは、Swinjectstoryboardというものもあるのでそちらを利用するのもおすすめです。
そして、下記のコードでViewModelをcontainerに登録し、それをresolver.resolve
で呼んでいます。
最後にreturnで値を返しています。
//ViewModelAssembly import Swinject final class ViewModelAssembly: Assembly { func assemble(container: Container) { //MARK: - ReaderViewModel container.register(ReaderViewModel.self) { (resolver) in return ReaderViewModel() } } }
//Container+Extension import Swinject extension Container { static let shared = assembler.resolver private static let assembler = Assembler([ ViewControllerAssembly(), ViewModelAssembly(), ]) }
Container+Extensionでは、 ViewControllerAssembly( )
,ViewModelAssembly( )
の二つをShredで呼べるようにしています。これを行うことで2つのassemblyをシングルトンとして利用することができます。
//UIViewController.Container+Extension import Swinject import UIKit extension ReaderViewController { static func make() -> ReaderViewController { return Container.shared.resolve(ReaderViewController.self)! } }
このmake( )
を使うことで、Swinjectを使ってオブジェクトを外部から注入することができます。
実際にAppDelegateでwindow?.rootViewController = ReaderViewController.make()
でクラスの初期化を行なっています。
Swift4 QRコードの読み取りと生成を行なう
今回、QRCoder
という名前でQRコードを読み取るかつ生成するという簡単なクラスを作ってみました。
主に、使ったLibraryはRxSwift
と SwinJect
です。
Protocolの作成
まずは、コードから
protocol QRCoderDelegate { func qrCoder(_ qrCoder: QRCoder, didDetectQRCode url: URL) }
まず、QRコードの読み取りが終わったことを伝えるdelegate処理を行います。このdelegate処理が伝えられる場所では、urlを受け取りことが可能です。
protocol QRCoderProtocol { func configure(on view: UIView) func startRunning() func stopRunning() func generate(from url: String) -> UIImage? func scanQR(from view: UIImage) -> String? }
上記のようにprotocolを生成するとこのprotocolに準拠するClass or Structは、このメソッドを書かなければエラーが起こります。
下のClass QRCoder
は、QRCoderProtocol に準拠しています。そのため、protocolで書かれたメソッドが全て呼ばれています。
final class QRCoder: NSObject, QRCoderProtocol { //カメラの入出力を行うクラス private let captureSession = AVCaptureSession() //カメラに映る映像を画面に写すクラス private var videoPreviewLayer: AVCaptureVideoPreviewLayer? = nil private var delegate: QRCoderDelegate? init(delegate: QRCoderDelegate?) { self.delegate = delegate } func configure(on view: UIView) { configureSession() addVideoPreviewLayer(on: view) } //このメソッドは、captureSessionでの入出力が行われていない場合に`caputureSession.startRunning`で入出力を開始させることができます。 func startRunning() { guard !captureSession.isRunning else { return } captureSession.startRunning() } //このメソッドは、captureSessionでの入出力が行われている場合に`caputureSession.stopRunning`で入出力を終了させることができます。 func stopRunning() { guard captureSession.isRunning else { return } captureSession.stopRunning() } //ここで、QRコードの生成を行なっています。URLを受けっとてそれをUIImageに変換させています。 func generate(from url: String) -> UIImage? { //string型をURLに直すことができるかをguardで判断している。 guard let _ = URL(string: url) else { return nil } //urlをdataに変え、 "inputCorrectionLevel"として英文字を加えます。 let parameters: [String: Any] = [ "inputMessage": url.data(using: .utf8)!, "inputCorrectionLevel": "H" ] //CIFilterは、主に画像の加工などを行う際に利用されます。引数のnameに行いたい処理を書くことでその加工を行うことができます。 //今回は、QRコードの生成ということで`CIQRCodeGenerator`と書きました。そして、Parameterも引数として渡します。 let filter = CIFilter(name: "CIQRCodeGenerator", withInputParameters: parameters) guard let outputImage = filter?.outputImage else { return nil } //次にCIImage->CgImageに変換させる let scaleImage = outputImage.transformed(by: CGAffineTransform(scaleX: 10, y: 10)) guard let cgImage = CIContext().createCGImage(scaleImage, from: scaleImage.extent) else { return nil } //最後に、cgImageをUIImageに変換させます。 return UIImage(cgImage: cgImage) } }
ところどころで、エラーの際はnilを返しています。ここで、nilを流すことでエラー時のハンドリング処理が行うことができます。 上記のコードを見て見ると、CIImageとCGImage、UIImageと3つのimageが出ています。 その違いについて簡単に紹介して行きたいと思います。 CIImage: CIImageは、主に画像のエフェクトをかけることができる。 Foodieなどのアプリは、Imageを受け取りCGImageに直しているのではないかと思います。
CGImage:画像の切り取りなどを行うことができます。 UIImage: UIImageでは加工などを行うことは出来ず主にViewとして使うことができる。
詳しくは、こちらのQiitam記事を参照ください。
https://qiita.com/jok/items/7c9e384abb29c161f56b
//次にimageから QRコードを読み取りstring型で返すという処理の実装です。 func scanQR(from view: UIImage) -> String? { guard let ciImage = CIImage(image: view) else { return nil } let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil) let features = detector?.features(in: ciImage) guard let castFeatures = features as? [CIQRCodeFeature] else { return nil } for feature in castFeatures { guard feature.messageString != nil else { return nil } return feature.messageString } return nil }
//以下で、QRの読み取りを行なっています。 private extension QRCoder { //携帯のカメラを使って処理を行うのかについて書いています。 //以下では、背面のbuiltInWideAngleCameraでVideoを撮るという処理を行なっています。QRコードはvideoで読み取っているみたいです。 func configureSession() { let deviceDiscoverySession = AVCaptureDevice .DiscoverySession( deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back ) //infp.plistでカメラ機能の利用を許可していなければここでerrorになる仕組みです。 guard let captureDevice = deviceDiscoverySession.devices.first else { fatalError("Failed to get the camera device") } //カメラの出力に対する処理を書いています。deviceで読み取ったものを` captureSession.addInput(input)`でinputしています。 do { let input = try AVCaptureDeviceInput(device: captureDevice) captureSession.addInput(input) } catch let error { fatalError(error.localizedDescription) } //出力に対する処理を書いています。 //AVCaptureMetadataOutputは、指定されたメタデータ検出しその処理のためにデリゲートに転送します。 let captureMetadataOutput = AVCaptureMetadataOutput() captureSession.addOutput(captureMetadataOutput) //ここで、データの受け渡しを行うdelegateを設定します。 captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) //今回行うはQRコードの読み取りなのでqrを指定します。 captureMetadataOutput.metadataObjectTypes = [.qr] //最後にカメラからの映像を`AVCaptureVideoPreviewLayer`でviewに写すことができます。 videoPreviewLayer = AVCaptureVideoPreviewLayer.init(session: captureSession) videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill } //上記の関数でもvideoPreviewLayerに対する処理を書きましたが、可読性をあげるため、引数のUIViewを扱う処理は別に書きます。 func addVideoPreviewLayer(on view: UIView) { videoPreviewLayer?.frame = view.layer.bounds //UIViewにaddsubViewするのではなく今回はLayerに.addSublayerというコードを書きます。 //Layerに処理を書くことで、 GPUを使った処理を可能とすることができるからです。 view.layer.addSublayer(videoPreviewLayer!) } }
//AVCaptureMetadataOutputから入ってきた情報に対する処理を書いて行きます。 extension QRCoder: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard metadataObjects.count != 0 else { return } //metadataObjectsがQRコードなのかを判断しています。 guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, metadataObject.type == AVMetadataObject.ObjectType.qr else { return } guard let url = URL(string: metadataObject.stringValue!) else { return } //最後にdelegateを使って処理した値をdelegateで返しています。 delegate?.qrCoder(self, didDetectQRCode: url) } }
参考に↓今回の記事の GitHubです。 github.com
KVOとGCD
KVO
今回は、 KVOについて興味を持ったので調べてみることにした。 KVOは、RxSwiftを有効活用できる処理の一つです。
KVOとは、Key-value-Observing
の略で、プロパティの値の変化を通知してくれる仕組みのこと。
Swiftで、KVOを行なった場合
private var observerContext = 0 override func viewDidLoad() { super.viewDidLoad() user.addObserver(self, forKeyPath: #keyPath(User.name), options: [.new, .initial], context: &observerContext) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if context == &observerContext { let newValue = change?[.newKey] as? String print("do something with newValue") } else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } } deinit { user.removeObserver(self, forKeyPath: #keyPath(User.name)) }
addObserver: notification queueのdispatch tableに、notification queueと queueに追加するブロック、オプションの通信名と送信者を含む登録を追加する。 ->キー値を監視して値の変更があった際に通知を受け取ることができる。 上記では、userの変更を監視している。
deinit:initが初期化処理を記述できるのに対して、クラスの後始末の処理を行うことができる。 上記では、removeObserverを行なっておりメモリリークを防いでいる。 Rxのdisposeに似ていることをしている。
この処理をRXswiftで行うと
override func viewDidLoad() { super.viewDidLoad() user.rx.observe(String.self, #keyPath(User.name)) .subscribe(onNext: { newValue in print("do something with newValue") }) .disposed(by: disposeBag) }
となる。みてわかる通り簡単にまとめることができます。 また、RxCocoaのBehaviorRelayを使うことで
let user = BehaviorRelay(value: “Ryou”) override func viewDidLoad() { super.viewDidLoad() user.accept(“Syou”) } user.subscribe(onNext: { newValue in print("do something with newValue") }) .disposed(by: disposeBag)
と書くことができる。 プロパティ内の値が変わった時に処理を行うため、値の監視を行うことをKVOという。そして、RxSwiftを使うとこのKVOがより簡単に行うことができることがわかった。
GCD
GCDは、queueという「処理の入れ物」に対して処理を登録することで適切なスレッド上で処理が行われる。その際に、同期か非同期どちらの処理で行うかを選ぶことができる。
- 同期:キューに登録したスレッドが登録した処理が完了するのを待つ。
メソッドは、sync
- 非同期:キューに処理を登録したスレッドが登録した処理が完了するのを待たない。
メソッドは、async
キューの種類
Dispatch Queue: 処理待ちタスクを追加するためのキュー。追加された順にタスクを処理側へ渡す役割を担う。タスクの処理は実行しない。 Dispatch Queueが処理側へタスクを引き渡す方式が2種類 - Serial:キュー内の処理が完了してから次の処理を実行させる。 - Concurrent:一度に複数の処理を実行することができる。 main queue:アプリの起動時にシステムによって自動的に作られるキューのことをいう。このキューは、メインスレッド上で順番に実行されるのでSerialキューとして考える。
基本的に、負荷の高い処理や終わるタイミングが未定の通信などの処理はメインスレッドでは行わない。 実際に、Dispatchを使った`同期``非同期`処理
func simpleQueues(){ let queue = DispatchQueue(label: "com.ayakosayama.simpleQueue") //syncを使っているので、同期処理を行なっている。 queue.sync { for i in 0..<10{ print("💜",i) } } for i in 100..<110{ print("💛",i) } } //下記のように表示される 💜 0 💜 1 💜 2 💜 3 💜 4 💜 5 💜 6 💜 7 💜 8 💜 9 💛 100 💛 101 💛 102 💛 103 💛 104 💛 105 💛 106 💛 107 💛 108 💛 109
上記の場合、queueが直列に処理される同期処理のため💜 が先に表示され、💛 が後に表示されている。
func asyncQueues(){ let queue = DispatchQueue(label: "com.ayakosayama.myqueue1") let queue2 = DispatchQueue(label: "com.ayakosayama.myqueue2") queue.async { for i in 0..<10{ print("🔵",i) } } queue2.async { for i in 100..<110{ print("🔴",i) } } for i in 1000..<1010{ print("⚫️",i) } } //下記のように表示される 🔵 0 🔴 100 ⚫️ 1000 🔵 1 ⚫️ 1001 🔴 101 ⚫️ 1002 🔵 2 ⚫️ 1003 🔵 3 ⚫️ 1004 🔵 4 ⚫️ 1005 🔵 5 ⚫️ 1006 🔵 6 ⚫️ 1007 🔵 7 ⚫️ 1008 🔴 102 ⚫️ 1009 🔵 8 🔴 103 🔵 9 🔴 104 🔴 105 🔴 106 🔴 107 🔴 108 🔴 109
この処理を行うと上記のようにそれぞれの処理を同時に行う。
このように、dispatchqueueで同期処理``非同期処理
を切り替えることができる。
DIspatchでは、今回行なった非同期処理以外にも処理速度の制限などを行うことができる。
heyの佐藤さんの話
多くの企業が広告に結構依存している
広告の歴史
知識がないと知識のある人が世界を作ってしまう。ー>一般市民にも知恵を与える フランス革命の時に、新聞に広告掲載されるようになった。 広告費は、ユーザーが支払うのではなく企業がお金を支払うようになる。 安価で、たくさんの人の知見を利用することができる。
面白いコンテンツ ー>より豊かなコンテンツを生み出す 面白いコンテンツには、多くのオーディエンスがいる 多くのオーディエンスがいるコンテンツには、多くの企業が広告を支払う
広告ビジネス
広告も需要と供給でできている 広告を出したい人と出す人でのバランス
マス広告時(昔)は、需要が多かった ー>supplyが少ない。テレビ、新聞などしか全国に発信できるものがなかった。 民放は、購読料がいらない
今、供給が無限に広がる ー>枠にお金を支払うのではなく、成果にお金を払う(クリック単価) ー>スマホの登場により多くの人に発信できる術ができた。 1.参入障壁が少ない 2.CGM オーディエンスは見るだけだったのに対して、オーディエンス側も作ることができるようになった。 ブログを作れば、そこに広告が入ってくる。 枠が増えて行くため、枠代がどんどん下がる
トラッキングがインターネットでは簡単にできる。(ログが残る) テレビ CMでは、どれくらい売り上げに繋がったのかがわかりずらい。 インターネットでの成果での支払いは、リスクが低い
Facebookとgoogleが、35%のユーザー滞在時間を占めている Mobileの広告でも、7割がFacebookとgoogle 2社がデジタル広告を寡占している。
ユーザーの時間をどのようにして取ることができるか
リビングの可処分時間(テレビ、新聞、ゲーム、携帯、家族の会話、スマートスピーカー) リビングは、寡占状態 新しい、可処分時間は結構ある。 最近では、外でのスマホ(エレベーター、信号待ちなど空き時間があればスマホをいじっている。) 1分の暇という可処分時間 ー> そこに時間にあったコンテンツが生まれてくる Twitterのタイムライン インスタのストーリー
パソコンは自分から情報を取得する モバイルは、コンテンツが受動的に入ってくる
新しいものを産まれると暇と考える。 VRとか? Mobility(ライドシェア、カーシェア、自動運転) 音の重要性が高まっている。ー>何かやりながら聞ける ARの視野を可処分所得を作り出せる。 ARで、バーチャル状のCMを作る。 Gatoradeは、Gatoradeを頭から被るARの動画を流して広告にしている。
消費の効率が上がったー>自分にピッタリ会うコンテンツを認知させることができた 消費することに飽きてくる つくる方に回る、作る人の身内感に回る 自分で、コンテンツを作る(カジュアルに何かを作ることが出来る) 例.Tik tok インスタのように鬼が散るやつができる カジュアルにできるものが少なくなる 作り手を応援するようになる showroomの投げ銭
そのお店の背景、働いている人に憧れがある。 スタバでコーヒーを飲む そこで、飲むことに価値がある。(no coffee)
カルチャーやそこで飲むことの価値を売る。ー>それをサポートする エモい消費が増えて行く
Observableとsubjectに関して
Rxswiftまとめ
Rxswiftには、swiftを勉強していてもなかなか出てこないメソッドが多くあります。 そこで、自分が読んだ記事を参考にまとめることにします。
Observable
ObserVableは、イベントの監視を可能とするメソッド。observableを表している。 これらで、onError,onCompletedに関しては呼ばれた時点で監視が終了する。 ・onNext ・・・ 通常のイベント通知(値を入れることができ、何回でも呼ばれる) ・onError ・・・ エラー通知(1回だけ、呼ばれた時点で終了) ・onCompleted ・・・ 完了(1回だけ、呼ばれた時点で終了)
onError、onCompletedが呼ばれた時点で、監視が終了したことを表す。
let contentOffset = tableView.rx.contentOffset .subscribe(onNext: { print("next") }, onError: { _ in print("error") }, onCompleted: { print("completed") })
contentOffsetは、scrollViewでよく用いられるメソッド。UIScrollViewのスクロール位置が初期状態(= contentOffset = {0, 0})の状態からx, y方向にどれだけスクロールしたかを表す。 今回は、scrollViewがスクロールされるたびにイベントが発生しその値を監視している。そして、UIKitをsubscribeする場合はとくにエラー処理や完了処理を行わなくても良い。 つまり、このコードではsubscribe(監視)で、変数contentOffsetを監視している。変数contentOffsetは、tableView.rx.contentOffsetなのでscrollというイベントが発生するたびにsubscribe(監視)を行いprintされることがわかる。
Dispose
Observableをsubscribeしたときに返されるのがDisposable。disposableは購読を解除するもので、disposeメソッドを呼ぶことで購読を解除する。もし、このdisposeを行わないとメモリーリークに繋がる。
private let disposeBag = DisposeBag() let contentOffset = tableView.rx.contentOffset contentOffset .subscribe(onNext: { print("next") }, onError: { _ in print("error") }, onCompleted: { print("completed") }) .Disposed(disposeBag)
上記に書いた、コードにdisposeを表すとこのように書くことができる。つまり、tableView.rx.contentOffsetのイベントの購読がonNextで行われその値の解放を一番最後に書くことでメモリリークを防いでいる。
Subject
subjectは、observableでもありobserverでもある。つまり、イベントを検知することもできイベントを発生させることもできる。observableは、独自でイベントを発生させることができない。Subjectは、subscribeを行えばobservableとしての機能を持ちつつ、onNext,onError,onCompletedメソッドを呼ぶことでイベントを発生させることができる。subjectクラスには、4つのクラスがある。 ・PublishSubject ・replaySubject ・BehaviorSubject ・Variable の4つがある。
publishSubject
publishSubjectをつかったコード
let disposeBag = DisposeBag() let subject = PublishSubject<String>() //ここで、subjectはPublishSubjectのstring型であることを書いている。 subject.onNext("a") //ここにsubjectのonNext(“a”)を流しても、printはされない。 subject.subscribe({ event in print(event) }) .addDisposableTo(disposeBag) subject.onNext("a") subject.onNext("b") ``` onNextメソッドを呼ぶことでイベントを発生させることができ、subscribeすることで購読し、イベントを検知した際の処理を定義することができる。 Observableは、基本的に外部からのイベントを通知するなどのことができる。 ・replaySubject ・BehaviorSubject ・Variable に関しては今後書いていきます。