iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
Try! Swift 2016での発表『文化を調和させる: 関数型プログラミング、プロトコル志向プログラミング、オブジェクト指向プログラミングの優れたテクニックを取り入れる』の手法をXcodeの新規プロジェクト(iOS / Application / Master-Detail Application)で作成されたソースコードに適用してたことをシリーズで発表する。
発表では、データをViewControllerのインスタンス変数として保持したが、自分流では必ずModel(Dataコントローラ)を用意しているので、今回もそうした。
class Document: NSObject {
static let sharedInstance: Document = {
let instance = Document()
return instance
}()
private override init() {
}
private let hand = Hand()
}
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
let document = Document.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add,
target: self, action:
#selector(MasterViewController.addNewCard(sender:)))
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController
= (controllers[controllers.count-1]
as! UINavigationController).topViewController
as? DetailViewController
}
}
:
データをViewControllerのプロパティにしてしまうと、データ管理ができないので、自分は必ずDocumentクラスを用意するようにしている。
publicとinternal、privateの事。
Swiftを紹介する際、ガードのため、安全のため、という説明をされる事が多い。
あえて物議を醸し出すが、危険な経験があるか。そして、スマートフォンの場合、クラスを作るのも、それを使うのも自分という場合があるが、本当に安全のため?
著者は、privateは、そのクラスのインタフェースを確認する際、無視していいよ程度の意味だと思っている。
なので、Privateとpublicの順で記述している。
class Demo {
public var name;
public func play() {
playGame();
}
private var address;
private func playGame() {
}
}
そう考えると、Objective-Cはよかったじゃん。
@interface Document : NSObject
@property (strong, nonatomic) NSString *version;
+ (Document *)sharedDocument;
- (void)load;
- (void)save;
@end
@interface Document ()
- (void)_clearDefaults;
- (void)_updateDefaults;
- (void)_loadDefaults;
- (NSString*)_modelDir;
- (NSString*)_modelPath;
@end
@implementation Document
@synthesize version = _version;
+ (Document *)sharedDocument;
{
static Document *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[Document alloc] init];
});
return _sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
_version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
}
return self;
}
- (void)dealloc
{
self.version = nil;
}
- (void)load
{
[self _loadDefaults];
NSString *modelPath = [self _modelPath];
if ((! modelPath) || (! [[NSFileManager defaultManager] fileExistsAtPath:modelPath])) {
return;
}
}
- (void)save
{
[self _updateDefaults];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *modelDir = [self _modelDir];
if (![fileManager fileExistsAtPath:modelDir]) {
NSError *error = nil;
[fileManager createDirectoryAtPath:modelDir
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
NSString *modelPath = [self _modelPath];
[NSKeyedArchiver archiveRootObject:self.indexArray toFile:modelPath];
}
- (void)_clearDefaults
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"version"];
}
}
- (void)_updateDefaults
{
NSString *versionString = nil;
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
[[NSUserDefaults standardUserDefaults] setObject:self.version forKey:@"version"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
- (void)_loadDefaults
{
NSString *versionString = nil;
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
/* バージョン不一致対応 */
}
else {
/* 読み出し */
}
}
- (NSString*)_modelDir
{
NSArray* paths;
NSString* path;
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if ([paths count] < 1) {
return nil;
}
path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:@".model"];
return path;
}
- (NSString*)_modelPath
{
NSString* path;
path = [[self _modelDir] stringByAppendingPathComponent:@"model.dat"];
return path;
}
@end
関連情報ではClassだと値を変更したら、他で参照しているところにも影響を与えるのでStructを勧めていたが、Model(Dataコントローラ)の実装次第だと思っている。ただ、これは次の関数型プログラミングの導入のためだと思うので、素直に従う。
C++のことを考えると誤解が発生しそうなので、利用するのが躊躇してしまう、リファレンス型と値というのがSwiftにあって、Structは値型のため、=で代入するとコピーとなる。ただ、自分がここで混乱してしまったが、冷静に考えると、SwiftのオブジェクトはObjective-Cのid型で、ARCでメモリ管理されているはずで、Structでは=演算子がコピーするようになっているということだろう。
値型でコピーとなるとコストの問題が気になるが、Swiftでは配列はCopy-On-Writeという考えで実装されていて、=演算子で代入する際は、idのコピーで、値の変更が派生する際に、中身のコピーが発生するので、気にする必要はないようだ。
ClassからStructに変更すると、プロパティを変更するメソッドにはmutatingキーワードをつけなければいけない。参考にしている発表では、これが格好悪いのでつける必要がない方法を選択したい。それが関数型プログラミング。SwiftではCopy-On-Writeに実装されていものがあるので、コストを気にしなくてもという事のようだ。
struct Deck {
public func nextCard() -> Card {
let card = Card()
return card
}
}
struct Hand {
private let deck = Deck()
private var cards = [Card]()
public init(deck: Deck, cards: [Card]) {
self.deck = deck
self.cards = cards
}
}
Handのプロパティcardsにカードを追加する場合、プロパティに変更を加えるメソッドを用意するのではなくて、内容をコピーして、カードを追加されたハンドを生成して返す。それとハンドを差し替えればいいじゃないの、という考えだ。
private func insertCard(card: Card, at index: Int) -> Hand {
var mutableCards = cards
mutableCards.insert(card, at: index)
return Hand(deck: deck, cards: mutableCards)
}
これを利用すれば、新規カードの追加はこうなる。
public func addNewCard(at index: Int) -> Hand {
return insertCard(card: deck.nextCard(), at: index)
}
カードの削除はこうだ。
public func deleteCard(at index: Int) -> Hand {
var mutableCards = cards
mutableCards.remove(at: index)
return Hand(deck: deck, cards: mutableCards)
}
これらを組み合わせれば、カードの移動もこうなる。
public func moveCard(fromIndex: Int, toIndex: Int) -> Hand {
return deleteCard(at: fromIndex).insertCard(card: cards[fromIndex], at: toIndex)
}
単なる配列操作だが、美しさを取るか!君はどっちだ!
今回のテーマは、少し取り上げにくい。なんとか頑張ってみる。
カスタムセルの実装をMVVM (Model - View - ViewModel)でと説明されているが、ここがスムーズにいかない。
MVVMは、Microsoftの.Netフレームワーク側のWPF(Windows Presentation Foundation)やSilverlightの設計方法で、WindowsのXAMLでViewを構成する場合にあったやり方。なので、異なるフレームワークのCocoaでもあるし、MVCやモジュール化、関数化の流れの物だと思うので、わざわざ、Cocoaの世界でMVVMというのは。
MVVM化として説明されているのが、セルに値を設定するメソッド。
class CardCell: UITableViewCell {
@IBOutlet var titleLabel: UILabel?
public func fillWith(card: Card) {
titleLabel?.text = card.title
}
}
セルの設定でビューコントローラのプロパティを使うのを避けたいという事のようだが、自分の場合は、Model(Dataコントローラ)を用意しているので、ちょっと状況が異なる。
これ以外に、自分の知らなかった、役立つ手法が紹介されていた。
先ずは、guard let。
guard 条件節 else { /* breakやreturn */ }
func demo() -> Cell {
guard let cell = crateCell else {
fatalError("Could not create cell")
}
cell.setTitle("表題")
return cell
}
Eiffel言語の事前条件のような利用を想定しているようで、ブロックの頭で、条件に合わない場合は弾く処理を記述するようの構文のようだ。
次は、subscript。
class Hand {
private var cards = [Card]()
subscript(index: Int) -> Card {
return cards[index]
}
}
var card = Hand[0]
配列は辞書の添字の多重定義、といったところか。例の場合は分かりにくくなっているが、C言語のマクロで簡素化していたコードなどをこれで置き換えることができそうだ。
12/5に開催された勉強会『Cocoa Study at Ikebukuro #6 (BUKURO.swift)』について報告する。
場所はいつもの池袋コワーキングスペース OpenOffice FOREST。池袋駅から少し離れた場所だ。座席代は夜間ドロップインで一人1,000円。一時、渋谷でも開催していたが、会場の都合から池袋での開催となったが、利用しやすいので助かっている。
発表内容を簡単にまとめると以下の通り。
構想しているアプリケーションの参考として発表者が探してきたのが、GNUstepのサンプルコード"MyWiki.app"。GNUstepだというのが渋い。そして、手を加えないといけなかったが、最新のmacOSでも動作。見た目は、iWebを思い出させる、ちょっと懐かしい感じのUI。OPENSTEPの流儀で画像データをコードで記述する方法。なるほどねでした。
こちらも先ほどの発表者が見つけてきたソースコード。素朴なメモ帳という感じだ。印刷に対応というのが、今の時代、懐かしい!
新型MacBook Proで話題のTouchBar。これを非対応のMacintoshで利用できるようにするのが、このソースコード。その感じから、OS9が動作してPowerBookの思い出していしまいました。
Try! Swift 2016での発表『文化を調和させる: 関数型プログラミング、プロトコル志向プログラミング、オブジェクト指向プログラミングの優れたテクニックを取り入れる』の手法を実際に試してみた事の発表。ボリュームがあり、最後に到達できなかったが、都築は、次回、勉強会で、出そうだ。
今回は、macOSのプログラミングが発表が多かったが、iOSの時代。新鮮に感じ、かつ、面白い!これからはmacOS向けAppStoreが熱いかな!?
そもそもUITableViewはUITableViewDataSourceプロトコルとUITableViewDelegateプロトコルの二つのプロトコルを利用するということから、複数のクラスで更生できる物だが、見通しが良く、記述が楽ということから、ベースとなるビューコントローラで実装されている場合が多いと思う。
今回のデータソースに分割するお話は、クラス構成から素直に実装とも言えると思う
まずは、データソースを切り離してみる。
class DataSource: NSObject, UITableViewDataSource {
private var document = Document.sharedInstance
func addItemTo(tableView: UITableView) {
if document.numberOfCards < 5 {
document.addNewCard(at: 0)
insertTopRowIn(tableView: tableView)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return document.numberOfCards
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
let card = document.getCard(at: indexPath.row)
cell.fillWith(card: card)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
document.deleteCard(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
func insertTopRowIn(tableView: UITableView) {
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .fade)
}
func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
}
}
class MasterViewController: UITableViewController {
private var dataSource = DataSource()
var detailViewController: DetailViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.dataSource = dataSource
self.navigationItem.leftBarButtonItem = self.editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MasterViewController.addNewCard(sender:)))
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction private func addNewCard(sender: UIBarButtonItem) {
dataSource.addItemTo(tableView: tableView)
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let card = Document.sharedInstance.getCard(at: indexPath.row)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = card
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
}
テーブルビュー関連のコードを切り離す。
SourceTypeというプロトコルを作り、プロトコルエクステンションとして中身を実装する。
protocol SourceType: UITableViewDataSource {
func insertTopRowIn(tableView: UITableView)
func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView)
}
extension SourceType {
func insertTopRowIn(tableView: UITableView) {
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .fade)
}
func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
}
}
データソースクラスに組み込む。
class DataSource: NSObject, UITableViewDataSource, SourceType {
private var document = Document.sharedInstance
func addItemTo(tableView: UITableView) {
if document.numberOfCards < 5 {
document.addNewCard(at: 0)
insertTopRowIn(tableView: tableView)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return document.numberOfCards
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
let card = document.getCard(at: indexPath.row)
cell.fillWith(card: card)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
document.deleteCard(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
モデルのHandをDataTypeプロトコル適用ということにして、項目名をCardから汎用的なItemに変更する。
protocol DataType {
var numberOfItems: Int {get}
func addNewItem(at index: Int) -> Self
func deleteItem(at index: Int) -> Self
func moveItem(fromIndex: Int, toIndex: Int) -> Self
}
struct Hand: DataType {
private var deck = Deck()
private var cards = [Card]()
public init() {
}
public init(deck: Deck, cards: [Card]) {
self.deck = deck
self.cards = cards
}
public var numberOfItems: Int {
return cards.count
}
public func addNewItem(at index: Int) -> Hand {
return insertCard(card: deck.nextCard(), at: index)
}
private func insertCard(card: Card, at index: Int) -> Hand {
var mutableCards = cards
mutableCards.insert(card, at: index)
return Hand(deck: deck, cards: mutableCards)
}
public func deleteItem(at index: Int) -> Hand {
var mutableCards = cards
mutableCards.remove(at: index)
return Hand(deck: deck, cards: mutableCards)
}
public func moveItem(fromIndex: Int, toIndex: Int) -> Hand {
return deleteItem(at: fromIndex).insertCard(card: cards[fromIndex], at: toIndex)
}
subscript(index: Int) -> Card {
return cards[index]
}
}
今回は難航した。元の発表はViewControllerでHandなどのデータ型に依存したコードを取り除くという内容だったが、自分のコードは、Model(Dataコントローラ)を用意しているので、データ型に依存していない。無理やり、Modelに適用してみたが、ちょっと、辛い。
DataTypeによって、CardがItemと、特定のデータ型に依存しないものになる。
class Document: NSObject {
private var dataObject: DataType = Hand()
public var numberOfItems: Int {
return dataObject.numberOfItems
}
public func addNewItem(at index: Int) {
dataObject = dataObject.addNewItem(at: index)
}
public func getItem(at index: Int) -> Card {
guard let hand = dataObject as? Hand else {
fatalError("Could not create Card Cell or Hand instance")
}
return hand[index]
}
public func deleteCard(at index: Int) {
dataObject = dataObject.deleteItem(at: index)
}
}
かなり、辛い。
protocol SourceType: UITableViewDataSource {
//var dataObject: DataType {get set}
func insertTopRowIn(tableView: UITableView)
func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView)
}
extension SourceType {
func insertTopRowIn(tableView: UITableView) {
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .fade)
}
func deleteRowAtIndexPath(indexPath: NSIndexPath, from tableView: UITableView) {
tableView.deleteRows(at: [indexPath as IndexPath], with: .fade)
}
}
class DataSource: NSObject, UITableViewDataSource, SourceType {
private var document = Document.sharedInstance
func addItemTo(tableView: UITableView) {
if document.numberOfItems < 5 {
document.addNewItem(at: 0)
insertTopRowIn(tableView: tableView)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return document.numberOfItems
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardCell
let card = document.getItem(at: indexPath.row)
cell.fillWith(card: card)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
document.deleteCard(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
class MasterViewController: UITableViewController {
private var dataSource = DataSource()
var detailViewController: DetailViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.dataSource = dataSource
self.navigationItem.leftBarButtonItem = self.editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MasterViewController.addNewCard(sender:)))
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction private func addNewCard(sender: UIBarButtonItem) {
dataSource.addItemTo(tableView: tableView)
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let card = Document.sharedInstance.getItem(at: indexPath.row)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = card
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
}
今回は、効果がよく分からないものになってしまった。
かなり無理をしている状態になってきたが、それだけ、試行錯誤しないといけないので、得るものはある。
今回は、計算済みプロパティの追加だ。今回も、無理がありそう!
やはり、これは無理。
protocol SourceType: UITableViewDataSource {
//var dataObject: DataType {get set}
//var conditionForAdding: Bool {get}
}
Model(Dataコントローラ)に計算済みプロパティを追加することに変更。
class Document: NSObject {
private var dataObject: DataType = Hand()
var conditionForAdding: Bool {
return dataObject.numberOfItems < 5
}
}
すると、こうなる。
class DataSource: NSObject, UITableViewDataSource, SourceType {
private var document = Document.sharedInstance
func addItemTo(tableView: UITableView) {
if document.conditionForAdding {
document.addNewItem(at: 0)
insertTopRowIn(tableView: tableView)
}
}
}
条件判定はプロパティになっている。
今回も辛いはず!
DataSourceを継承したHandDataSourceを作る。
class HandDataSource: DataSource {
override init() {
super.init()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as? CardCell else {
return UITableViewCell()
}
let card = document.getItem(at: indexPath.row)
cell.fillWith(card: card)
return cell
}
}
ハンドビューコントローラがこれを利用するようにする。
class HandVC: UITableViewController {
private var dataSource = HandDataSource()
}
スーパークラスを変更する。
class DataSource: NSObject, UITableViewDataSource, SourceType {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
fatalError("This method must be overriden")
}
}
継承されなかったら、エラーにするということだ。
ギブアップ。そもそもの構造が違うので、発表のとおりの修正は無理。
発表では、DataSourceに総称型を導入。
class DataSource: NSObject, UITableViewDataSource, SourceType {
var dataObject: DataType
init(dataObject: A) {
self.dataObject = dataObject
}
}
そうすると、初期化は以下のようになる。
class HandDataSource: DataSource {
init() {
super.init(dataObject: Hand())
}
}
ただ、何度も言い訳をして苦しいが、自分のプロジェクトでは、Model(Dataコントローラ)のクラスを用意しているので、こうはならない。
本件、プロジェクトの構成の違いから、最後はグダグダになってしまったので、一通り巡ったということで、その中で気になったことを調べてみることにする。
プロトコルでSelfを返すように変更したが、何となく意味は分かるのだが、何なんだろう。The Swift Programming Languageを読み返してみたが、それらしい記述がない。検索したところ、おそらく、WWDCの発表からだろうか。Self要件と説明しているサイトを見つけた。
protocol DataType {
var numberOfItems: Int {get}
func addNewItemAtIndex(index: Int) -> Self
func deleteItemAtIndex(index: Int) -> Self
func moveItem(fromIndex: Int, toIndex: Int) -> Self
}
このプロトコルを適用したクラスの型として扱われるということのようだ。
ただ、これはプロトコルでのみ許されているようで、適用したクラスでは、そのクラスで宣言しないとエラーとなった。
struct Hand: DataType {
public var numberOfItems: Int {
return cards.count
}
public func addNewItem(at index: Int) -> Hand {
return insertCard(card: deck.nextCard(), at: index)
}
private func insertCard(card: Card, at index: Int) -> Hand {
var mutableCards = cards
mutableCards.insert(card, at: index)
return Hand(deck: deck, cards: mutableCards)
}
public func deleteItem(at index: Int) -> Hand {
var mutableCards = cards
mutableCards.remove(at: index)
return Hand(deck: deck, cards: mutableCards)
}
public func moveItem(fromIndex: Int, toIndex: Int) -> Hand {
return deleteItem(at: fromIndex).insertCard(card: cards[fromIndex], at: toIndex)
}
}
Self要件によって、プロトコルは、総称型のように、適用した型を抽象的に記述できるということのようだ。
今回は、出だしは少し辛口。
MacOS時代、クラスライブラリMacAppを使用したプログラミングの手法として、オブジェクト・プログラミングというのがあった。これは、利用側が、必ずしもオブジェクト指向プログラミングをする必要がない。オブジェクト指向で実装されたライブラリを利用すればいいということだったと思う。
この考えは、NeXTstepでもあったと思っていて、継承以外のクラスを拡張する方法としてデリゲートが用意されていたり、InterfaceBuilderはクラスでなく、インスタンスを生成するところなどがそうではないだろうか?
自分はたいした職歴でないが、過去を思い出すと、継承を多用したプロジェクトは、必ず、スパゲッティになっていた。何が問題なのかなとボンヤリと思うと、継承をコードの共通化として利用し、オブジェクトとしての構成がよくなかったのではないか?では、そこを頑張ればいいのかというと頑張ると変な方向に行ってしまうような。
なかなか、この考えに考え抜いたスパゲティを生産される方は、その主張がパッと聞くと正論のように思われ、止めることはできず。逆に止めようとすると、論争になり、ますます、泥沼にはまっていたよな〜。
恐ろしいのは、この設計論争は、何もモノを生み出さない。趣味ですね。
Swiftでclass出なくstructを。継承でなくプロトコルを勧めているのも、多くの方々が、このスパゲティ・クラスに疲れ果ててしまったからかな思う。
イカンイカン。脱線しているので本題に戻る。
今回取り上げるのはWWDC2015のセッション『Protocol-Oriented Programming in Swift』。
課題を3つ挙げている。
一つ目は、リファレンス型のことを言っているようで、値を変えると、他で所有しているところで驚くという内容だ。ただ、これは、以前も説明した通りモジュール化の問題で、Model(Dataコントローラ)を用意するとか、必要な複製は行うとかすれば問題ないと考えている。
二つ目は、継承の問題で、パッと思いついたのは、親でreadonlyにしたが、一部の子でwriteしたくなり、そのためにそれ専用のメソッドを用意したり、逃げで苦労するということか。継承しすぎ、Baseクラスやめたら、ですね。
最後は型の問題で、親子で方がうまくいかず、他の子のことを考慮したり、親で子を考慮したりということか。
ということで、推奨されたのが以下の7つ。
詳細は割愛するが、それでプロトコル指向プログラミングということだが、何とか指向というと、スパゲティの悪夢が蘇って腰が引けてしまうが、まずは、自称オブジェクト指向プログラミングを制限するというのは、いいかも。
これで、悲劇が繰り返さないことを願う。
業務上、CやC++、Objective-C、Swift、Java、C#を同時に利用するという機会があるのだが、それで得られた感想は、プログラミング言語の違いによる差はない、だ。単に記法が異なるだけ。
違いが出るのは、どんなフレームワークを使っているかで、そういう意味から、Objective-CとSwiftの差はないと思う。
○○言語の方が簡単で効率的!といわれる場合があるが、ワインバーグさんが言っているように、これは経営側からの視点の言葉だと思う。○○言語の方が安全といわれる場合もあるが、これはあるレベルのスキルがある関係ないと思う。今後定年がなくなると言われる状況で、ずっとプロでやって行くことを考えると、安全なプログラミング言語でないと危険な仕事をしているようではと思う。
自分の経験から、自分がまだプログラミングの仕事ができているのは、以下を学ぶことができたからかなと考えている。
Swiftを知って、あらためてC言語の素晴らしさを実感しているが、C言語のスキルを得るために、Swift主流の時代ににSwiftしか知らないのでObjective-Cを学ぶのは難しいとは思う。かと行って、hello, worldレベルの学習をしてもしょうがない。難しい状況だな。
Cocoaのような本格的で大規模なGUIライブラリを知っておくのは、大事だと思う。違うものが主流になっても、その経験は生きるので。
参加していた勉強会の運営に変更があり、今年度は、主に池袋と松戸で定期的に勉強会が解されるようになった。
渋谷で開催された勉強会は、以下のとおり。
渋谷の会場が都合で使えなくなったため、池袋で開催されるようになった。
松戸でも開催されるようになった。松戸では、今後は、初心者向け、かつ、macOS関連の発表を充実させて行く予定だ。ただ、会の内容を決めるのは発表者なので、参加者次第となるが。
今年度から、NPO法人MOSAの理事になり、MOSA自習室という勉強会を開催。
どんな内容なのかは、各勉強会のページに発表資料のリンクがあるので、それで確認してほしい。
年明けも、以下の勉強会の開催が決まっているので、興味がある方は、申し込みを。
自分が考えている発表は、一つは、よりCoolなSwiftによるプログラミングを調べた『Swiftyを試してみる』と、macOSの開発をしたくなったので、近年、大きく変わったmacOS関連の基本的なアプリケーションについてを考えている。