iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
Apple Lisaコンピュータのアプリケーションは、Lisa Toolkitというライブラリを使って開発され、このToolkitライブラリはClascalと呼ばれるPascal言語を拡張したオブジェクト指向言語で書かれていたそうだ。
『オブジェクト指向プログラミング for the Macintosh』によると、Clascalのコードは以下のようになる。
{TViewクラスの子クラス}
TQuadView = SUBCLASS OF TView
{インスタンス変数}
window:TQuadWindow;
{作成メソッド}
FUNCTION TQuadView.CREATE(object:TObject;heap:THead;itsExtent:LRect;itsPanel:TPanel):TQuadView;
{選択メソッド}
PROCEDURE TQuadView.MousePress(mouseLPt:LPoint);OVERRIDE;
FUNCTION TQuad TQuadView.NewSelection(quad:TQuad):TQuadSelection;ABSTRACT;
END;{TQuadView}
Pascal派生の言語ということでC言語とは異なる部分があるが、iOSしか知らなくてもコードの内容が理解できると思うし、Swiftなど、逆に近づいているように感じる部分があって面白いと思ったが、どうだろうか?
このLisa ToolkitとClascalは、Macintoshの開発環境に影響を与えている。MacintoshのAPIはオブジェクト指向から手続き型のToolboxライブラリになっているが、MacAppクラスライブラリが用意され、このMacAppはObject Pascalというオブジェクト指向プログラミング言語で記述されていた。
こちらもコードを『オブジェクト指向プログラミング for the Macintosh』から引用してみる。
TYPE TFoo = OBJECT(TSnark)
fFirstObject: TEmployee;
fNumber: INTEGER;
PROCEDURE TFoo.IFoo;
PROCEDURE TFoo.Free;OVERRIDE;
END;
PROCEDURE TFoo.IFoo;
VAR tempObject:TEmployee;
BEGIN
SELF.ISnark;
NEW(tempObject);
tempObject.IEmployee;
SELF.fFirstObject:=tempObject;
END;
PROCEDURE TFoo.Free;
BEGIN
FreeObject(SELF.fFirstObject);
INHEFITED Free;
END;
Lisaコンピュータが発売されたのが1983年。Macintoshコンピュータが1984年。MacAppクラスライブラリは1985年。NeXT創業が1985年。NeXTワークステーションとNeXTSTEPオペレーティングシステムが発表されたのが1988年。
アプリケーションは、Application Kitソフトウェアキットを利用して開発され、Objective-Cで記述されていた。『Xウィンドウとその仲間たち』によると以前のクラス名は頭にNXが付いていて、その後のNSと異なっている。NeXTSTEPからOPENSTEPに変わる際に変わったのかな?(NSはNeXT Softwareの略と聞いたことがある)
C++でなくObjective-Cが選択された理由の一つに、当時、C++は研究室で利用されるもので、実際の現場で利用可能になっていたのはObjective-Cだったからという話を聞いたことがあるが、今となってみれば、C++でなくてよかったのではないか!?
『オブジェクト指向プログラミング for the Macintosh』に古い形式のObjective-Cのコードが掲載されていたので引用する。
= Point:Object (Geometry, Primitive) { short, xLoc, yLoc; }
+ x:(int)anX y:(int)aY {
id myNewPoint = [self new];
[myNewPoint x:anX];
[myNewPoint y:aY];
return myNewPoint;
}
- x:(int)anX { xLoc = anX; return self; }
- y:(int)aY { yLoc = aY; return self; }
=:
1996年、NeXTはAppleに買収された。OPENSTEPはRhapsodyというコードネームでMacintoshに移植された。RhapsodyにはMatintosh ToolboxをエミュレーションするBlue Boxと、OPENSTEPのYellow Boxで構成されていた。
RhapsodyはMac OS X Server 1.0としてリリースされたのち、2001年にMac OS Xとしてリリースされ、v10.8 (Mountain Lion) からMacが取れてOS Xとなった。そして、RhapsodyのBlue BoxはCarbonに、Yellow BoxはCocoaという名前となり、現在でも、OS XやiOS、watchOS、tvOSの主要なフレームワークとなっている。
Objective-C 2.0と呼ばれるものから、大幅に機能が増えたが、当初のObjective-Cは簡素で、C言語をマスターしたプログラマーなら30分程度でマスターできる言語だった。ただし、言語の仕様を簡素というだけで、Cocoaフレームワークを使いこなせるようになるには、当初から難しかったが。
今回の投稿では、初期のCocoaでのObjective-Cについて説明する。これによって、基本的にとても簡素な仕様の言語だとうことが分かると思うので。
クラス定義は、以下の通り。通常、ヘッダーファイルに記述される。
#import <Cococa/Cocoa.h>
@interface Song : NSObject {
NSString *title;
}
+ (id)sharedInstance;
- (id)init;
- (void)dealloc;
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
@end
クラスは必ずNSObjectまたは、その子クラスを継承する必要がある。
クラスの実装は、以下の通り。通常、サフィックスが.mのファイルに記述される。
#import "Song.h"
@implementation Song
+ (id)sharedInstance
{
static Song* song = nil;
if (! song) {
song = [[Song alloc] init];
}
return song;
}
- (id)init
{
if (self = [super init]) {
title = nil;
}
return self;
}
- (void)dealloc
{
if (title) {
[title release];
title = nil;
}
[super dealloc];
}
- (NSString *)title
{
return title;
}
- (void)setTitle:(NSString *)aTitle
{
[title autorelease];
title = [aTitle retain];
}
@end
+で始まるのはクラスメソッド。-で始まるのはインスタンスメソッド。親クラスのインスタンスはselfとなる。
既存のクラスはカテゴリーを使って拡張できる。
#import "Song.h"
@interface Song (Album)
- (NSString *)albumTitle;
@end
#import "Album.h"
@implementation Song (Album)
- (NSString *)albumTitle
{
return @"Trout Mask Replica";
}
@end
多重継承に対応しない代わりに、プロトコルという機能を用意している。
@protocol Playable
- (void)play;
- (void)stop;
@end
Songクラスがこれに対応してみる。
#import <Cococa/Cocoa.h>
#import "Playable.h"
@interface Song : NSObject <Playable> {
NSString *title;
}
- (void)play;
- (void)stop;
呼び出し側はプロトコルに対応しているか判定することができる。
if ([song conformsTo:@protocol(Playable)]) {
[song play];
}
else {
}
『tvOS for Developers』の情報から、tvOS用のXcodeプロジェクトの内容について探ってみよう。
DemoBotsというサンプルコードは、OSXとiOS、tvOSのプロジェクトが収められているので、tvOS固有の内容が分かりやすいのではないかと判断。
当たり前だが画面の方向関連の項目がない。
ファイルの種類は、iOSと似ている。
複数のStoryboardがある場合、どうしてtvOSだと分かるのだろうか。
ターゲットに関連付けられているものということだった。
WWDCでの約束通り、Swiftがオープンソース化された。
Swiftが登場してからC言語について意識するようになったが、Swiftを知れば知る程、C言語の素晴らしさに気付かられる。また、C++言語についても問題も多いが、C++が適切な分野はあると思う。例えば、Swift自身はC++で記述されているし。でも、C++でなくてもいいじゃんというシチュエーションに遭遇することは多い。
Appleの事だから、言語趣味的な事はなく、実用的なアプリケーション開発のプロが使う道具として割り切ったものだと思うので、そういう部分は好感を持っている。今後に期待しているので、裏切らないでよ!
Swiftのオープンソース化については、他にも投稿があると思うので、ちょっと変わった視点でのお話を。
マルチプラットフォーム向けのプロダクトだと思うが、ビルドシステムに何を使っているのだろうか?調べてみると、Ninjaビルドシステムというのを使っているようだ。
説明を読むと、軽量なシステムということだが、期待していいのかな?
tvOSアプリケーション開発の日本語文書が公開された。これは助かる。
ざっと、自分にとってポイントになる点を列挙してみる。
本日は電車トラブルで帰宅が深夜となってしまったので、続きは後日。
WWDCは、Worldwide Developers Conferenceの略で、Appleが年に一回、世界中の開発者を集めて、新技術の紹介や開発にあたっての細かな技術を説明するためのイベントだ。2002年まではAppleの本社があるSan Joseで開催されていたが、2003年からはSan FranciscoのMoscone Westという会場で開催されている。
日本国内で開催される技術系のイベントは、どうしても海外の技術をお勉強するための場という感じになってしまうが、WWDCの特徴は担当部分の責任者であったり、実装を行っているAppleの技術者とやりとりでき、最新の情報が入手できるとともに、それが発表されるタイミングで自分の製品に組み込んだものをリリースできるという利点がある。また、どの分野に注目が集まっているのか、逆にトレンドから外れそうなものなど、流れを感じることができるという利点もある。
開催時期は以前は春から夏の間で変動があったが、最近では6月の第2週あたりに開催されるようになった。月曜日から金曜日に開催され、日本の土曜日に出発すると現地の土曜日に到着し、日曜日で時差ボケの体を慣らして月曜日からのセッションに備え、帰国は現地の土曜日に出発し、日本に日曜日に到着するというのがスタンダードなスケジュールになるのかな?
WWDC参加費以外にかかる費用をどう抑えるかだが、航空機代は頑張るしかない。日本の航空会社にこだわらなくてもかな。宿泊代だが、私のオススメはユースホステル。WWDC期間中は参加さが多く宿泊しており不安も少なく、多少の不便があっても一泊3千円程度は魅力的だ。
Google for Mobileに参加してきたので、興味があった点についてレポートする。
サブタイトルは『Develop. Engage. Earn』。これはセッションカテゴリにもなっていて、各セッションはこれのどれかに属しているものとなっている。そして会場はDevelop TrackとEngage & Earn Trackの2つ用意して、そのカテゴリに沿ったセッションが開催された。
この場で発表される新しいことはなく、Googleから日本の状況に対応した情報が伝えられる場という印象を持った。その中で気になったのが、アプリ申請の審査について。以下の点について気にして欲しいということのようだ。
■関東第76回Cocoa勉強会のご案内
日時:2016/1/9(土) 13:00-17:00
会場:東京都千代田区岩本町2-14-2 イトーピア岩本町アネックス3階 会議室
集合:現地(セキュリティカードが必要なため、受付後入室になります)
会費:無料
tvOSでも、TabBarControllerやNavigationControllerは使えるが、TVのユーザインタフェースということを考えると、独自のコンテナViewControllerが必要になる場合があると思う。そこで、以前、iOS向けに検討したコンテナViewControllerをtvOSに組み込んでみた。
コンテナビューコントローラに子ビューコントローラを登録。
self.addChildViewController(titleViewController!)
self.addChildViewController(gameViewController!)
最初の画面を表示させる。
selectedViewController = titleViewController
self.view.addSubview(selectedViewController!.view)
画面を切り替えるコード。
func toGameViewController() {
transitionFromViewController(titleViewController!, toViewController: gameViewController!, duration: 1.0, options: .TransitionCrossDissolve, animations: nil, completion: { (finished: Bool) -> Void in self.selectedViewController = self.gameViewController })
}
MOSA Tech Meetingに参加してきたので、その内容をざっと紹介する。
今回のテーマは『iPad Pro祭』。発表はtvOS向け開発について、iPad Pro向け開発について、iPad Proのプレゼンでの活用方法、iBeaconやEddystoneについて、iPad Proで楽しめるアプリ紹介の5本だ。
Bluetooth LE関連については、自分も注目している分野なので、後で、詳しく説明してみる予定。
先日の第12回 MOSA Tech Meeting
で得られた情報だ。
以前は、iBeacon機器の情報はApple Developerサイトで公開されていなかった為、AndroidアプリケーションをiBeaconに対応させる為には、ネットで得られる情報を信じて実装するしかなかった。
それが、iBeacon for Developersサイトの『Download Artwork and Specifications』のリンクから入手できるようになった。
当然の結果だが、『[iOS][Android]すれちがい通信(その8)』で紹介した実装で問題なかった。
詳しく説明したいのだが、仕様書ダウンロードの際に確認があるので、残念ながら、控えることにする。
Swiftは安全なプログラミングを提供!という話を聞くと少しモヤモヤしてます。なので、この手の話題に関わりたくないので、ちょっと、違う視点から。
Swiftでもpublicやprivateが指定できるようになりがっかりした側なのだが、そもそも、それで何をしたいのか?隠すためだというが、Swiftのようなインタフェース部と実装部が分かれていない言語では、丸見えじゃん!と思ってしまう。
class Person {
private var name = "名無しの権兵衛"
func rename(newName: String) {
name = newName;
}
private func secretWork() {
}
}
では、C++はいいのかというと、privateなメンバーも、通常ヘッダーに記述するクラス定義で宣言しないといけないため、知りたくもない内部実装を見せられて、これもイマイチ。
class Person {
std::string name;
void secretWork();
public:
void rename(std::string newName);
};
void Person::secretWork()
{
}
void Person::rename(std::string newName)
{
name = newName;
}
それを考えると、Objective-C 2.0は自分にとって理想的だ。
@interface Person : NSObject {
NSString *name;
}
- (void)rename:(NSString *)newName;
@interface Person ()
- (void)secretWork;
@end
@implementation
- (void)rename:(NSString *)newName
{
if (name) [name autorelease];
name = [newName retain];
}
- (void)secretWork
{
}
@end
ヘッダーには見せたいことだけが記述されている。素晴らしい。
ただ、Swiftに慣れると、インタフェース部と実装部のファイルが分かれているのが、煩わしく感じてしまう気持ちも否定できないが。
以前、発表したプロセス間通信(Distributed Notifications)をSwiftで実装してみた。やってみてわかったのだが、NSConnectionはSwift未対応のようだ。使うのを控えてくれということか。
通知を受け取る側(DistributedServer)のコードは以下のとおり。
import Foundation
import Cocoa
protocol RemoteObjectProtocol {
func receiveString(string: String)
}
class AppDelegate: NSObject, NSApplicationDelegate, RemoteObjectProtocol {
@IBOutlet var window: NSWindow?
@IBOutlet var label: NSTextField?
func applicationDidFinishLaunching(notification: NSNotification) {
debugPrint(__FUNCTION__)
_registerForNotes()
_registerForDistributedObjects()
}
func _registerForNotes() {
debugPrint(__FUNCTION__)
let dnc = NSDistributedNotificationCenter.defaultCenter()
dnc.addObserver(self, selector: "_handleDistributedNote:", name: "DemoDistributedNote", object: nil)
}
func _handleDistributedNote(note: NSNotification) {
debugPrint(__FUNCTION__, "Recieived Distributed Notification!:", note)
if let label = self.label {
label.stringValue = "Recieived Distributed Notification!"
}
}
func _registerForDistributedObjects() {
debugPrint(__FUNCTION__)
/*
let conn = NSConnection.defaultConnection()
conn.rootObject = self
if !conn.registerName("DistributedServer") {
debugPrint(__FUNCTION__, "error")
}
*/
}
func receiveString(string: String) {
debugPrint(__FUNCTION__)
if let label = self.label {
label.stringValue = string
}
}
}
通知を送る側(DistributedClient)のコードは以下のとおり。
import Foundation
import Cocoa
protocol RemoteObjectProtocol {
func receiveString(string: String)
}
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow?
func applicationDidFinishLaunching(notification: NSNotification) {
debugPrint(__FUNCTION__)
// _postNotes()
}
@IBAction func postNotes(sender: AnyObject) {
debugPrint(__FUNCTION__)
_postNotes()
}
@IBAction func postForDistributedObjects(sender: AnyObject) {
debugPrint(__FUNCTION__)
_postForDistributedObjects()
}
func _postNotes() {
debugPrint(__FUNCTION__)
let dnc = NSDistributedNotificationCenter.defaultCenter()
dnc.postNotificationName("DemoDistributedNote", object: nil)
}
func _postForDistributedObjects() {
debugPrint(__FUNCTION__)
/*
let remoteObject = NSConnection.rootProxyForConnectionWithRegisteredName("DistributedServer", host: "")
remoteObject.setProtocolForProxy("RemoteObjectProtocol")
remoteObject.receiveString(NSDate.date().description)
*/
}
}
DistributedServerを起動した後に、DistributedClientを起動すると、アプリケーション間で通知が送られることが確認できる。
Xcodeの新規プロジェクトで生成されるSpriteKitを使ってプロジェクトをGameコントローラを使用したものに変更してみる。
Gameコントローラの内容は以下のとおり。
import Foundation
import SpriteKit
import GameplayKit
class Game {
var gameScene: GameScene?
let random: GKRandomSource?
init() {
random = GKRandomSource()
}
deinit {
}
var scene: GameScene? {
get {
if gameScene == nil {
gameScene = GameScene(fileNamed: "GameScene")
}
return gameScene
}
}
}
以下がViewControllerの変更前。
class GameViewController: UIViewController {
:
override func viewDidLoad() {
print(NSStringFromClass(self.dynamicType), __FUNCTION__)
super.viewDidLoad()
if let scene = GameScene(fileNamed: "GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
:
}
以下がViewControllerの変更後。
class GameViewController: UIViewController {
:
override func viewDidLoad() {
print(NSStringFromClass(self.dynamicType), __FUNCTION__)
super.viewDidLoad()
game = Game()
if let aGame = game {
let scene = aGame.scene
scene!.scaleMode = .AspectFill
let skView = view as! SKView
skView.presentScene(scene)
skView.ignoresSiblingOrder = true
skView.showsFPS = true
skView.showsNodeCount = true
}
}
:
}
複数の非公開具象クラスをまとめた公開抽象クラス。例を挙げるとNSNumberクラス。内部には、整数や浮動小数毎に実装されたクラスを用意しているが、インタフェーストして公開しているのは数値に対応したNSNumberクラス。
クラスクラスタの欠点を挙げるとすれば、公開抽象クラスのサブクラスを定義することが困難。
クラスファクトリメソッドの名前は、「+ (type)className...」となる。例を挙げると、例えば、NSDateクラスの場合は以下の通り。
+ (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
ARCが広く使われるようになった現状では、allocとinitの二つのメソッドを呼ばないといけないので楽という利点しか感じられなくなったが、このメソッドの呼び出し側はオブジェクトを所有しない。つまり、autoreleaseされたインスタンスを返すという意味のメソッドだ。
デリゲート(委譲)は、サブクラスを作成しないで、あるオブジェクトの処理を外に出す実装方法。
データソースは、デリゲートと似ていて、デリゲートがインタフェースの制御を委譲するものだが、データソースはデータの制御を委譲するものだ。
イントロスペクションは、オブジェクトの内容を調べる機能のことで、例えば、あるクラスのサブクラスなのかだとか、あるメソッドを実装しているか、あるプロトコルに純処しているか等だ。
ちょっと、純粋なオブジェクト指向言語に反する部分があると思うのだが、実用主義らしい、これによって柔軟な実装ができる便利な機能だと思う。
他の純粋なオブジェクト指向言語と異なり、インスタンスの生成などが言語でなくフレームワークで定義されているというのが、Cocoaの面白いところだ。
「オブジェクトの割り当て」と重複するが、C++でコンストラクタ(構築子)と呼ばれるものは、NSObjectの実装ルールとなっている。
Lisa Toolkitから受け継がれているAppleのクラスライブラリで重要視しているデザインパターン。著者がCocoaを始めた時は、具体的にどうするのかがピンとこなかったが、現在では、対応するクラスがフレームワークで用意されているなどで、このデザインパターンに沿いやすくなったと思う。
初期のCocoaではモデルに対応するクラスがなかったが、CocoaバインディングやCoreDataが出た頃から、モデルの実装をサポートする機能が充実してきた。
MutableとMutableでないクラスが用意されていることだ。
Interface Builderで接続される、他のオブジェクトの参照。インスタンス変数のことだ。
キー値監視(KVO)などを使った、あるイベントに対応した処理を呼び出すようなもののこと。
対象となるオブジェクトのメソッドを呼び出す仕組み。例を挙げるとInterface Builderでコントローラでイベントが発生すると、接続したインスタンスのメソッドを呼び出すことだ。
Core FoundationとFoundationで、相互にデータを利用出来る仕組みで、Toll-Free Bridgingと呼ばれている。
大晦日になって、自分が知らなかったことに気がついてしまった。
マルチスレッドの環境でシングルトンを実装する場合、ロックが思っていたが、Swiftでは状況が異なるようだ。
Objective-Cで実装する場合、以下の通り。
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
Swiftだとstatic変数はクラスアクセス時に生成されるので、以下のように記述できる。
class Singleton {
static let sharedInstance = Singleton()
}
シングルトンなインスタンス生成時に設定が必要な場合は以下となる。
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}
もし、シングルトン以外のインスタンス生成を許したくない場合は、C++でコンストラクタをpriateにするのと同様に、privateなinit()関数を用意すればいい。
private init() {
// setup code
}