iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
以前、Cocoa LifeというCocoa勉強会の会誌で、ジョイスティックをアプリケーションで扱うお話として、HID Device Interface入門という記事を書いたが、これをSwiftで挑戦してみようと思う。
今回対象となるのは、SmartScroll。左手で扱うトラックボール。知人がドライバを書いていたのだが、最新のOSに対応していないので、挑戦することにした。
以前の開発環境には、USB Proberというアプリが存在したが、今の開発環境ではなくなった。そこで、当時から存在していたIORegistryExplorerを使うことにする。これは、別途、Additional Toolsとしてダウンロードしてインストールするものだ。
以下が、その結果。
赤い枠で囲まれた値が、機器の特定に必要となる情報だ。
今回、デモ用ということでコマンドラインツールとしてアプリを製作することにしたのだが、戸惑ったのは、main()関数をSwiftではどう書くか?結論は、main()関数は書かないということのようだ。
import Foundation
import CoreFoundation
import IOKit
import IOKit.usb.IOUSBLib
exit(0)
main()関数の戻り値は、exit()関数の引数で渡すということになる。
IORegistryExplorerで取得した値のVendor IDとProduct IDを使って機器を特定することにするのだが、それを定数として定義する。
let kOurVendorID = 0x56a /* Vendor ID of the USB device */
let kOurProductID = 0x50 /* Product ID of device */
機器を探す条件の辞書を用意する。
var masterPort: mach_port_t = 0
let usbVendor: Int32 = Int32(kOurVendorID)
let usbProduct: Int32 = Int32(kOurProductID)
let kr: kern_return_t = IOMasterPort(mach_port_t(MACH_PORT_NULL), &masterPort)
var matchingDict = IOServiceMatching(kIOUSBDeviceClassName) as? [String: Any]
matchingDict![kUSBVendorID] = usbVendor
matchingDict![kUSBProductID] = usbProduct
検索条件を設定して、RunLoopで待つことにする。
private var gNotifyPort: IONotificationPortRef? = nil
private var gAddedIter: io_iterator_t = 0
gNotifyPort = IONotificationPortCreate(masterPort)
let runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort).takeUnretainedValue()
private var gRunLoop: CFRunLoop? = CFRunLoopGetCurrent()
CFRunLoopAddSource(gRunLoop, runLoopSource, CFRunLoopMode.defaultMode)
let _ = IOServiceAddMatchingNotification(gNotifyPort, kIOFirstMatchNotification, matchingDict! as CFDictionary, DeviceAdded, nil, &gAddedIter)
DeviceAdded(nil, gAddedIter)
CFRunLoopRun()
機器を見つけると呼ばれる関数の定義は以下の感じだ。
func DeviceAdded(_ refCon: UnsafeMutableRawPointer?, _ iterator: io_iterator_t) {
print(#function)
}
これで動作確認してみたのだが、うまく動かない。申し訳ないが今回は時間切ということで続きは勉強会で!
017年12月6日(水)の夜間、東京都豊島区の池袋コワーキングスペース OpenOffice FORESTにおいて、「BUKURO.swift 2017-12」を開催致した。
発表は、以下の通り。
卒業研究のタイトルは『拘束のある力学系の制御に関する研究(先端が限定された面上を動くマニピュレータの運動制御の基礎研究)』だったビッツの村上です。全く制御から離れたmacOS/iOSのアプリケーション・プログラマをやっているのだが、今回は、思い切った制御工学に挑戦する。
学生時代から言われたいたのは、制御工学は使われていない。確かにそのようだ。
最初の仕事は人工心肺装置の企画なのだが、病院研修で心臓外科のオペの手伝いをやっていた。心臓手術は心臓に戻ってきた血液を人工心肺装置が取り込み、それを心臓の出口に返して心臓に血液が流れない状態にして行うのだが、循環している血液を冷やすと心臓は止まり、オペ終了時に血液を温めると心臓は動き出す。この血液の温度の調整を水槽で行うのだが、これは、指定された温度になるとヒータが止まり、下がるとヒータがつく。そう、自動制御をやっている方はお分かりだと思うが発信する。これはそういうものだということで特に改善されることはなく、自動制御の出番はなかった。
その後に選んだ職業がプログラマ。またまた、書店で手に取った『神経回路網の数理』に興味を持ち、電子計算機の世界に飛び込んだ。
あるギネスにも載っている巨大なゲートウェイシステムの構築に関係した際のことだ。自分は方式Gというアーキテクトを扱うチームに配属されたのだが、そこで行なっていた流量制御はこんな感じだ。予想される処理コストを決定し、それで性能測定を行い。その値で流量を規制する。そう、ここでも自動制御は活用されていない。予想が外れると性能限界を超えてしまった。内心フィードシステムだ!と思っていたのだが、学生時代に習ったことをプログラミングで、どう表現すればいいのか分からない。
そんな自分にとって、これはと思ったのが、オライリー社の『エンジニアのためのフィードバック制御入門』だ。まえがきに、しっかりと書かれている。自動制御を行うためには電子計算機の力が必要だ。でも、プログラミングの現場では、自動制御が疎遠となっている。この書籍は、この問題に対しての著者からの回答で、ソフトウエアの世界の出来事を自動制御の対象として適用する例が豊富となっている。
また、使っているのもPythonとgnuplot。学生時代、大型計算機でFortranでリカッチ方程式を解いていたのは何だったのか?手元のMacBook Proで演算できちゃいます!
ちなみに、ひねくれ者の自分は、書籍の通り動かすのではなく、SwiftとAccelerate Frameworkを使って、挑戦している。
以前、tvOSアプリケーション開発を試したことがあるが、あれから時間が経過したのと、tvOSのAdvent Calendarに投稿する機会に恵まれたということで、再挑戦することにした。幸い、他の投稿記事には入門的なものはないようなので、ちょうどいいと思っている。
tvOSのアプリケーションには二種類ある。従来型とクライアント-サーバ型だ。
後者はメディアのストリーミングを行うアプリケーションで従来のWeb技術を活用するものだ。マークアップ言語のTVMLでインターフェイスを実装し、JavaScriptで挙動を記述する。TVMLKitフレームワークはネイティブコードとの橋渡しをするものだ。
この投稿では、前者の従来型を取り上げる。Swiftで実装するアプリケーションだ。
サンプル・アプリケーションを用意したので、これを使って説明する。独自のコンテナViewControllerを使って画面を切り替えている。
ContainerViewControllerは子となるViewControllerをインスタンス変数で保持する。
class ContainerViewController: UIViewController {
var titleViewController: TitleViewController?
var gameViewController: GameViewController?
Storyboardから子となるViewControllerを取得し、子ViewControllerに親となるコンテナViewController を設定する。
override func viewDidLoad() {
super.viewDidLoad()
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
titleViewController = mainStoryboard.instantiateViewController(withIdentifier: "TitleViewController") as? TitleViewController
titleViewController!.containerViewController = self
gameViewController = mainStoryboard.instantiateViewController(withIdentifier: "GameViewController") as? GameViewController
gameViewController!.containerViewController = self
self.addChildViewController(titleViewController!)
self.addChildViewController(gameViewController!)
titleViewController!.didMove(toParentViewController: self)
gameViewController!.didMove(toParentViewController: self)
最初に表示するViewControllerを設定する。
selectedViewController = titleViewController
self.view.addSubview(selectedViewController!.view)
タイトル画面でStartボタンが選択されるとコンテナViewControllerの画面遷移メソッドを呼ぶ。
class TitleViewController: UIViewController {
var containerViewController: ContainerViewController?
:
@IBAction func startButtonTapped(_: AnyObject) {
containerViewController!.toGameViewController()
}
}
コンテナViewControllerのコードは以下のとおり。
func toGameViewController() {
transition(from: titleViewController!, to: gameViewController!, duration: 1.0, options: .transitionCrossDissolve, animations: nil, completion: { (finished: Bool) -> Void in self.selectedViewController = self.gameViewController })
}
遷移したゲームViewControllerはSpriteKitを使ってゲーム・シーンを表示している。
class GameViewController: UIViewController {
var containerViewController: ContainerViewController?
var game: Game?
tvOSはiOSから派生しているということで、iOSと同様に記述できている。
ほぼ同じコードが複数の箇所に存在している場合、それを共通化すること自体は推奨されることだと思う。ただ、共通化の手法としてクラスの継承を使い、基底クラスに共通コードを実装する場合は手を動かす前に考えた方がいいと思う。オブジェクト指向という名前からも関連があるものがクラス化されるものだと思うが、共通化目的の場合、関連がないものも継承関係に含めてしまっていないかという事を。
これから失敗の事例を説明していく。
共通コードを〜Baseという名前で規定クラスで実装する。
class MyBase {
func aaa() {
bbb();
}
func bbb() {
}
}
class Xxx : MyBase {
override bbb() {
}
}
class Yyy : MyBase {
override bbb() {
}
}
派生クラスの異なる部分をbbb()メソッドで実装する。この瞬間はうまくいっているように見える。
派生クラスXxxとYyyは関係が薄い場合、異なる方向で仕様が変わったとする。そして、上記のコードで都合が悪くなったとする。そんな時、以下のような修正をしてしまわないか?
class MyBase {
func aaa() {
if n == abc {
bbb(2);
}
else {
bbb(1);
}
}
func bbb(kind: Int) {
}
}
class Xxx : MyBase {
override bbb(kind: Int) {
}
}
class Yyy : MyBase {
override bbb(kind: Int) {
}
}
基底クラスに派生クラスを考慮したコードが入ってしまう。
この問題を解消するために、基底クラスと派生クラスの間に中間の基底クラスを作成してしまう。そしてくクラスの数の大爆発と派生クラスのコードを読んでも意味が分からない。
この問題の回避は難しい。指摘しても、コードは共通化するものだ。逆にこの手の事をやってしまう人は、積極的にコード構成に意見する場合があるし。
こんな事を投稿してしまった場、マサカリがブーメランとなって自分のところに飛んでこない事を祈っている。
本日(12/11)に開催された、2017年 AKIBA.swift忘年回 に参加したので、その内容を報告する。
今回は、忘年回?ということで、設けたテーマに沿った発表が行われた。
自分も1995年から続く団体や2003年から続く勉強会に参加しているのだが、発表内容のレベルの高さや面白さには驚かされている。
おそらく、今、Swift界隈はとても面白い状況だと思う。メインストリームであることによる高いクオリテイと、近年肩身が狭かったクライアント・サイドのプログラミングをモダン化したSwiftによって趣味性が強い面もあり、これらの相乗効果によって、この状況が生まれたのであろうか?
この幸せな状況をただ単に受け取るだけでなく、より充実していくよう、協力できればと思っている。
先日のAKIBA.swiftで、macOSでURLを開くAPIが以下なのだが、NSWorkspaceって何?となったので調べてみた。
let urlString = "http://www.bitz.co.jp/"
if let url = URL(string: urlString) {
NSWorkspace.shared().open(url)
}
NSWorkspaceのWorkspaceは、macOSのFinderに相当する、NeXTSTEPのWorkspace Managerからきている。
macOSでは"workspace"サービスを提供するのがNSWorkspaceクラスで、Finderの操作の機能をて提供している。
Swift、それはプログラマに残された最後の開拓地である。そこにはプログラマの想像を絶する新しい文法、新しいフレームワークが待ち受けているに違いない。
これはプログラマ最初の試みとしてn年間の調査プログラミングに飛び立った、宇宙船Xcode号の驚異に満ちた物語である。
プログラマ35年定年説というのがあるが、それを超えて働いていると、様々な炎上プロジェクトに遭遇する。そこで知った興味深い事象について報告する。
プロジェクトが炎上すると、人を投入してなんとかするというのは常套手段である。そうやって間に合わない部分、足りない部分を助っ人達に作らせるのだが、炎上がなんとか収まると助っ人達は撤退する。その後は、元のチームのメンバーが助っ人達が作ったコードを保守することになる。
面白いのは、元のチームのメンバーに助っ人のコードのことを聞くと、酷い、というより、知らない、という反応を示すことだ。一切関わりたくないことのようだ。助っ人のコードが本当に酷いのかは分からないが、その後、元のチームのメンバーが、俺には関係ない!という態度で扱えば、そこからバグは発生する者だ。このコードはチームのお荷物となる。
興味深いのは、プロジェクトが長期の場合、元のチームのメンバーが殆ど入れ替わることは驚くべきことではない。でも、元も助っ人も関係ない彼らに助っ人コードのことを聞くと、俺には関係ない!という反応を示すことだ。
助っ人を投入する場合、チームの関係に気を使わないと、そのツケは長期にわたって払わないといけなくなるという、良い教訓だと思う。
今回、新たなスタイルに挑戦してみたが、自分にマサカリが飛んでこないことを願っている。
Metalについては素人なため入門記事しか書けないので申し訳ない。
まず、サンプルプログラムの名前について説明する。
MetalといえばHeavy Metal。ヘビメタのギターといえばIbanezのDestroyer。なのでサンプルプログラムの名前はDestroyer。
調べてみたところ、駆逐艦という意味があるそうだ。これには驚いた。
話をサンプルに戻す。まず、MTKViewの派生クラスを用意する。
import Foundation
import MetalKit
class DestroyerView: MTKView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
}
StoryboardでCustom Viewを貼り付け、それのクラスを先ほど作成したクラスに変更する。
これで、DestroyerViewで描画されるようになった。次は、Metal関連のコードを記述していく。
頂点と色の情報の構造体を定義する。
struct Vertex {
var position: vector_float4
var color: vector_float4
}
DestroyerViewクラスのメソッドdraw()に描画コードを記述子ていく。
デバイスを作成して設定する。
self.device = MTLCreateSystemDefaultDevice()
三角形の頂点と色情報を用意
/* 三角形の頂点と色情報を用意 */
let vertexData = [Vertex(position: [-0.8, -0.8, 0.0, 1.0], color: [1.0, 0.0, 0.0, 1.0]),
Vertex(position: [ 0.8, -0.8, 0.0, 1.0], color: [0.0, 1.0, 0.0, 1.0]),
Vertex(position: [ 0.0, 0.8, 0.0, 1.0], color: [0.0, 0.0, 1.0, 1.0]),]
let vertexBuffer = device?.makeBuffer(bytes: vertexData,
length: MemoryLayout.size(ofValue: vertexData[0]) * vertexData.count,
options: [])
シェーダ・ライブラリを取得する。
guard let library = device?.makeDefaultLibrary() else {
return
}
シェーダーを設定する。
let vertexFunction = library.makeFunction(name: "vertex_func")
let fragmentFunction = library.makeFunction(name: "fragment_func")
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexFunction = vertexFunction
renderPipelineDescriptor.fragmentFunction = fragmentFunction
RGBA 各8bit形式のピクセルを設定する。
renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
パイプラインステートメントを作成する。
let renderPipelineState = try device?.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
レンダーパス記述子
guard let renderPassDescriptor = self.currentRenderPassDescriptor, let drawable = self.currentDrawable else {
return
}
クリアする色を設定
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.8, 0.7, 0.1, 1.0)
コマンドキューを生成し、コマンドキューからコマンドバッファを生成する
let commandBuffer = device?.makeCommandQueue()?.makeCommandBuffer()
シェーダへデータを送る
let renderCommandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
renderCommandEncoder?.setRenderPipelineState(renderPipelineState!)
renderCommandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderCommandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
renderCommandEncoder?.endEncoding()
コマンドバッファのコミット
commandBuffer?.present(drawable)
commandBuffer?.commit()
シャーダーは、単純に入力値をそのまま返している。
#include >metal_stdlib<
using namespace metal;
struct Vertex {
float4 position [[position]];
float4 color;
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],
uint vid [[vertex_id]]) {
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]) {
float3 inColor = float3(vert.color.x, vert.color.y, vert.color.z);
float4 outColor = float4(inColor.x, inColor.y, inColor.z, 1);
return outColor;
}
以下の通り、描画される。
CoreMLではモデルは開発者で用意しないといけない。TensorFlowの利用を考えて、インストールしてみる。
公式サイトの情報によると、macOSでは以下の選択肢がある。
お勧めはvirtualenvということなので、素直に従ってみる。
virtualenvをインストールする。
$ sudo easy_install pip
$ pip install --upgrade virtualenv
あれ、二個目のコマンドでエラーになる。パーミッション関連なので、sudoつけてみた。
$ sudo pip install --upgrade virtualenv
成功した。
virtualenv 環境を作成する。標準のPythonは2.7.10なので、以下のコマンドとなる。
$ virtualenv --system-site-packages ~/tensorflow
次はアクティベート。bashなので、以下のコマンドとなる。
$ source ~/tensorflow/bin/activate
(targetDirectory) $
pip 8.1がインストールされていることを確認する。
$ easy_install -U pip
TensorFlowをインストール。
$ pip install --upgrade tensorflow
動作確認する。
$ python
Python 2.7.10 (default, Jul 15 2017, 17:16:57)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> # Python
... import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
2017-12-15 22:40:19.713684: I tensorflow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.2 AVX AVX2 FMA
>>> print(sess.run(hello))
Hello, TensorFlow!
>>>
成功したみたいだ。
仕事効率化のアプリケーションには興味があって、タスク管理アプリを作ろうと考えている。その為の第一歩として、EventKitを試してみる。macOSとiOSの両方を考えているが、サンプルはiOSで。
iOSでは、Info.plistに以下のような定義が必要だ。
NSCalendarsUsageDescription
Access the data of the calendar.
NSRemindersUsageDescription
Access the data of the reminder.
macOSでは、com.apple.security.personal-information.calendars エンタイトルメントを含めるみたいだが、まだ試していないので紹介に止める。
初期化処理としてイベントストアへの接続というのがあるのだが、ユーザへの認証が必要となる。
let store = EKEventStore()
let status = EKEventStore.authorizationStatus(for: .event)
var isAuth = false
switch status {
case .notDetermined:
isAuth = false
case .restricted:
isAuth = false
case .denied:
isAuth = false
case .authorized:
isAuth = true
}
if !isAuth {
store.requestAccess(to: .event, completion: {
(granted, error) in
if granted {
}
else {
return
}
})
}
デフォルトのカレンダーに対して、過去一年分のイベントを取得する。
let startDate = Date(timeIntervalSinceNow: -365.0 * 24.0 * 60.0 * 60.0)
let endDate = Date()
let defaultCalendar = store.defaultCalendarForNewEvents
let predicate = store.predicateForEvents(withStart: startDate, end: endDate, calendars: [defaultCalendar!])
let events = store.events(matching: predicate)
print(events)
以下の通り、取得できた。
前回の続きで、イベントの追加と削除を試す。
イベントを追加する。
let event = EKEvent(eventStore: store)
event.title = "BUKURO.swift 2017-12"
event.startDate = Calendar.current.date(from: DateComponents(year: 2017, month: 12, day: 6, hour: 19, minute: 30, second: 00))
event.endDate = Calendar.current.date(from: DateComponents(year: 2017, month: 12, day: 6, hour: 22, minute: 00, second: 00))
event.calendar = store.defaultCalendarForNewEvents
do {
try store.save(event, span: .thisEvent)
}
catch let error {
print(error)
}
イベントを削除する。
do {
try store.remove(event, span: .thisEvent)
}
catch let error {
print(error)
}
動作確認が原因で関係者に通知が飛んでしまった。申し訳ない。
三次元グラフィックスについて触れる機会も増えているということで、OpenGLに再挑戦。
『OpenGLの神髄』のサンプルプログラムを試してみた。
#import <iostream>
#import <sstream>
#import <Foundation/Foundation.h>
#import <OpenGL/gl.h>
#import <OpenGL/glu.h>
#import <GLUT/glut.h>
int gWidth = 600;
int gHeight = 500;
const int QUIT_VALUE(99);
GLuint gListID; /* ディスプレイリストID */
void display(void)
{
/* カラーバッファの初期化 */
glClear(GL_COLOR_BUFFER_BIT);
/* モデリング変換、z軸の負の方向に幾何形状を4単位移動する。 */
glLoadIdentity(); /* 単位行列 */
glTranslatef(0.0f, 0.0f, -4.0f);
/* 幾何形状を描画する。 */
glCallList(gListID);
/* バッファの入れ替え */
glutSwapBuffers();
assert(glGetError() == GL_NO_ERROR);
}
void resize(int w, int h)
{
/* ウィンドウ・サイズとOpenGLの座標を対応づける */
glViewport(0, 0, w, h);
/* 投影行列とアスペクト比を更新する */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(50.0, (GLdouble)w / (GLdouble)h, 1.0, 10.0);
/* 表示ルーチン用にモデルビューモードに設定する */
glMatrixMode(GL_MODELVIEW);
assert(glGetError() == GL_NO_ERROR);
gWidth = w;
gHeight = h;
}
void keyboard(unsigned char key, int x, int y)
{
DBGMSG(@"%s", __func__);
}
void special(int key, int x, int y)
{
DBGMSG(@"%s", __func__);
}
void mouse(int button, int state, int x, int y)
{
DBGMSG(@"%s", __func__);
}
void motion(int x, int y)
{
DBGMSG(@"%s", __func__);
}
void idle(void)
{
glutPostRedisplay();
}
void main_menu_callback(int value)
{
if (value == QUIT_VALUE)
exit(EXIT_SUCCESS);
}
void init(void)
{
DBGMSG(@"%s", __func__);
/* ディザ処理を無効にする */
glDisable(GL_DITHER);
std::string ver((const char*)glGetString(GL_VERSION));
assert(! ver.empty());
std::istringstream verStream(ver);
int major, minor;
char dummySep;
verStream >> major >> dummySep >> minor;
const bool useVertexArrays = ((major >= 1) && (minor >= 1));
const GLfloat data[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
if (useVertexArrays) {
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLAT, 0, data);
}
/* ディスプレイリストを作成する。 */
gListID = glGenLists(1);
glNewList(gListID, GL_COMPILE);
if (useVertexArrays) {
glDrawArrays(GL_TRIANGLES, 0, 3);
//glDisableClientState(GL_VERTEX_ARRAY);
}
else {
glBegin(GL_TRIANGLES);
glVertex3fv(&data[0]);
glVertex3fv(&data[3]);
glVertex3fv(&data[6]);
glEnd();
}
glEndList();
assert(glGetError() == GL_NO_ERROR);
/* 描画 */
glutDisplayFunc(display);
/* リサイズ処理 */
glutReshapeFunc(resize);
/* キーボード */
glutKeyboardFunc(keyboard);
/* 特殊キー */
glutSpecialFunc(special);
/* マウス */
glutMouseFunc(mouse);
/* ドラッグ */
glutMotionFunc(motion);
/* バックグランド処理 */
glutIdleFunc(idle);
/* コンテキスト・メニュー */
glutCreateMenu(main_menu_callback);
glutAddMenuEntry("Quit", QUIT_VALUE);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
glutInit(&argc, (char **)argv);
/* RGBカラーモード ダブルバッファ */
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
/* 初期ウィンドウ・サイズ */
glutInitWindowSize(gWidth, gHeight);
/* 初期ウィンドウ位置 */
glutInitWindowPosition(500, 100);
/* タイトルバー */
glutCreateWindow("IRIS GL");
/* 初期化 */
init();
/* 主ループ(イベント駆動) */
glutMainLoop();
}
return 0;
}
glDrawArraysで「Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)」が発生。なぜだ!
iOS/macOSのプログラミングの勉強会です。 Swift/UIKit /AppKit/Objective-Cの技術的な話題を取り上げます。
日時:2019/01/19(金) 19:30-22:00
会場:池袋コワーキングスペース OpenOffice FOREST http://co-forest.com
3階の受付で、swift勉強会又はcocoa勉強会ですとお伝えください。人数によって会場の席が変わる時があります。
集合:現地
座席代:夜間ドロップイン 一人1,000円(各自でお支払いください)
申込:https://cocoa-kanto.connpass.com/event/75006/
発表:募集中です。conpassのアンケート機能で適当に発表内容を書いてもらえば、こちらで以下に追加します。
* 「アウトライプロセッサ進捗会議」(macOS/Swift) 成田
* 「Developer's Tech Lab」(その他) 質問コーナーです。iOS/macOSのプログラミングの質問に答えます。
Cocoa勉強会 関東について:
以下のFacebookグループから、ご参加下さい。
https://www.facebook.com/groups/cocoa.kanto/
INTER-Mediator勉強会2017-#6 に参加してきたので報告する。
タイムテーブルは以下の通り。
18:00 ~ 開場・交流会
19:00 ~ プレゼン
『クライアントサイドでの書式指定』
『Docker for Macを使ってINTER-Mediatorを試用する』
21:00 ~ クロージング
21:00 ~ 有志による忘年会
今年度は地方での開催が何度か行われた。自分は、残念ながら参加できなかったのだが、来年こそは地方での勉強会に開催できたらと考えている。
前回の続き。
まず、分かったのは、こまめにglGetError()でエラーが発生しているのか確認すること。その結果、glVertexPointer()の呼び出しで、GL_INVALID_ENUM が発生していることが分かった。
そして、OpenGLのバージョンが2.1だったのが、以下の設定で4.1になることが分かった。設定してしまうと、古いAPIが使えなくなるので、一旦、見送るが。
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_3_2_CORE_PROFILE);
エラーの内容から、バッファオブジェクトを使用しない方法だったが、使用する方法でないと弾かれるのではと考え、試してみることにした。
GLuint bufferID;
void init(void)
{
assert(glGetError() == GL_NO_ERROR);
/* ディザ処理を無効にする */
glDisable(GL_DITHER);
assert(glGetError() == GL_NO_ERROR);
std::string ver((const char*)glGetString(GL_VERSION));
assert(! ver.empty());
std::istringstream verStream(ver);
int major, minor;
char dummySep;
verStream >> major >> dummySep >> minor;
const bool useVertexArrays = ((major >= 1) && (minor >= 1));
NSLog(@"OpenGL Ver. %d.%d", major, minor);
/* 三角形の頂点を定義する */
const GLfloat data[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
if (useVertexArrays) {
/* バッファIDを取得する */
glGenBuffers(1, &bufferID);
/* バッファオブジェクトをバインドする */
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
/* 配列の値をバッファオブジェクトにコピーする */
glBufferData(GL_ARRAY_BUFFER, 3 * 3 * sizeof(GLfloat), data, GL_STATIC_DRAW);
glEnableClientState(GL_VERTEX_ARRAY);
assert(glGetError() == GL_NO_ERROR);
glVertexPointer(3, GL_FLAT, 0, bufferObjectPtr(0));
GLenum glErrorCode = glGetError();
NSLog(@"%s (error code:0x%x)", __func__, glErrorCode);
assert(glErrorCode == GL_NO_ERROR);
}
assert(glGetError() == GL_NO_ERROR);
/* ディスプレイリストを作成する。 */
gListID = glGenLists(1);
glNewList(gListID, GL_COMPILE);
assert(glGetError() == GL_NO_ERROR);
if (useVertexArrays) {
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, &gListID);
}
else {
glBegin(GL_TRIANGLES);
glVertex3fv(&data[0]);
glVertex3fv(&data[3]);
glVertex3fv(&data[6]);
glEnd();
}
assert(glGetError() == GL_NO_ERROR);
glEndList();
//NSLog(@"%s (error code:0x%x)", __func__, glGetError());
assert(glGetError() == GL_NO_ERROR);
/* 描画 */
glutDisplayFunc(display);
/* リサイズ処理 */
glutReshapeFunc(resize);
/* キーボード */
glutKeyboardFunc(keyboard);
/* 特殊キー */
glutSpecialFunc(special);
/* マウス */
glutMouseFunc(mouse);
/* ドラッグ */
glutMotionFunc(motion);
/* バックグランド処理 */
glutIdleFunc(idle);
/* コンテキスト・メニュー */
glutCreateMenu(main_menu_callback);
glutAddMenuEntry("Quit", QUIT_VALUE);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
ダメだ。やはり、glVertexPointer() を呼んだ後にエラーだ。
前回のおさらいから。
ADC文書によると、iCloudストレージを利用する方法は以下となる。
前回、iCloudドキュメントストレージに読み書きしたが、macOSだと ~/Library/Mobile Documents/iCloud~バンドルID/ 配下だった。
iCloud Drive配下でないのだが、どうやればiCloud Driveに読み書きできるのか?調べてみた。
CapabilitiesでiCloudを有効にし、Info.plist に NSUbiquitousContainersキー を追加すればいいようだ。
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.example.MyApp</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
<key>NSUbiquitousContainerName</key>
<string>アプリ名など</string>
</dict>
</dict>
NSUbiquitousContainersキー には、コンテナ識別子を設定する。
二年前に勉強会の運営方法が変わり一時は活動が停止しそうな状況となりましたが、皆さんのご協力のもと何とか継続してきました。最近は開催頻度を下げ発表の質を高め、他勉強会にも積極的に参加し、内容を見直しして結果なのか、新たな参加者も増えてきました。
来年も、勉強会の活動を通じてプログラマーが楽しくプログラミングできるよう目標を掲げて活動していきたいと思いますので、宜しくお願いします!