Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

Objective-C++でクラス

JSONを使った受け渡しを想定して書いてみた。非効率かも。

#import <Foundation/Foundation.h>

@class Engine;

typedef NSDictionary JSON;

@protocol EngineProtocol <NSObject>
- (void)engine:(nonnull Engine *)engine message:(nonnull JSON *)message;
@end

@interface Engine : NSObject
+ (nonnull instancetype)engine:(nonnull id <EngineProtocol>)delegate;
- (void)tellMessage:(nonnull JSON *)message;
@end

#include <stdio.h>
#include <iostream>

class EngineCpp;




class EngineCpp {
public:
    EngineCpp();
    ~EngineCpp();
    void hello();
    typedef std::shared_ptr<EngineCpp> Ptr;
    std::string message;
    std::string proc(std::string src);
};

EngineCpp::EngineCpp() {
    std::cout << "EngineCpp new" << std::endl;
}

EngineCpp::~EngineCpp() {
    std::cout << "EngineCpp delete" << std::endl;
}

void EngineCpp::hello() {
    std::cout << "EngineCpp hello" << std::endl;
}

std::string EngineCpp::proc(std::string src) {
    return "{\"Hello\":" + src + "}";
}

@interface NSJSONSerialization(String)
+ (NSString *)jsonString:(NSDictionary *)dic;
+ (NSDictionary *)jsonString_r:(std::string &)str;
@end

@implementation NSJSONSerialization(String)
+ (NSString *)jsonString:(NSDictionary *)dic {
    NSData *d = [NSJSONSerialization dataWithJSONObject:dic
                                                options:NSJSONWritingPrettyPrinted
                                                  error:nil];
    NSString *sd = [[NSString alloc] initWithData:d
                                         encoding:NSUTF8StringEncoding];
    return sd;
}

+ (NSDictionary *)jsonString_r:(std::string &)str {
    NSData *data = [NSData dataWithBytes:str.c_str() length:str.length()];
    
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data
                                                        options:NSJSONReadingMutableContainers                                                          error:nil];
    return dic;
}

@end




@interface Engine()
@property (nonatomic) id <EngineProtocol> delegate;
@property (nonatomic, assign) EngineCpp::Ptr engineCpp;
@end


@implementation Engine {
}

- (void)dealloc {
    NSLog(@"Engine dealloc");
}

+ (instancetype)engine:(id<EngineProtocol>)delegate {
    Engine *engine = [Engine new];
    engine.delegate = delegate;
    engine.engineCpp = std::make_shared<EngineCpp>();

    return engine;
}


- (void)tellMessage:(JSON *)message {

    std::string str = [NSJSONSerialization jsonString:message].UTF8String;
    
    std::string result = self.engineCpp->proc(str);
    
    NSDictionary *dic = [NSJSONSerialization jsonString_r:result];
    
    [self.delegate engine:self message:dic];
}

@end

リーダー選出アルゴリズム

リーダー選出のアルゴリズム

後続(successor)がダウンしている場合、飛ばして次のプロセスにElectionメッセージを送信する。
このメッセージには自分のプロセス番号をつける。
後続者は次々と自分の後続者にElectionメッセージを自分のプロセス番号を追加してまわしていく。
一周してElectionメッセージがもどってきたら(メッセージに自分のプロセスIDが入っているかどうかで判断する)、メッセージのタイプをCoordinatorに変更、これを回覧する。
すべてのプロセス番号がそろっているのでCoordinatorが誰かがわかる。
結果、2周する。

UIWindow

自作アラートのような使い方ができる。
UIWindow.rootViewControllerを設定
UIWindow.windowLevelはノーマルかアラートの+幾つかを設定するとより前に出てくる。
UIWindow.makeAndVisibleで表示される。
参照を破棄してmakeAndVisibleで消える。

一文字づつ区切る

NSStringEnumerationByComposedCharacterSequencesを用いる。

        NSString *string= @"ハローワールド🗾";
        
        NSMutableArray *list = [NSMutableArray array];
        
        [string enumerateSubstringsInRange:NSMakeRange(0,string.length)
                                                   options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
                                                       [list addObject:substring];
                                                   }];
        for (NSString *sub in list) {
            NSLog(@"%@", sub);
            for (int i=0;i<sub.length;i++) {
                NSLog(@"%04x", [sub characterAtIndex:i]);
            }
                  
        }

Unicode block

Unicode block - Wikipedia

Basic Latin

UTF8では1バイトで表現される。
Basic Latin (Unicode block) - Wikipedia

Latin-1 Supplement

U+0080–U+00FF

Halfwidth and fullwidth forms

Halfwidth and fullwidth forms - Wikipedia
U+FF00–FFEF 

  1. U+FF01–FF5EはASCIIの全角
  2. U+FF65–FFDCはカタカナ、ハングルの半角

UTF、Unicode

Unicode

ユニコードスカラ値(非負。16進数で書く。prefixにU+) 例 U+143FC
基本多言語面(BMP)は4桁。補助多言語面は5,6桁使用。
ユニコードスカラ値のシーケンスが文字列。スカラの値のある場所が符号ポイント。符号ポイントのつらなりをブロックと呼ぶ。

文字列符号化方式

  1. 文字符号化スキーム(CES)
  2. 文字符号化方式(CEF) → UTF-8UTF-16UTF-32

UTF8

  1. 可変長(1-4バイト)の8ビット符号単位
  2. ASCIIと同じ部分は1バイト(ASCIIに対して上位互換)
  3. その他の部分を2-6バイトで符号化
  4. 文字の境界が明確

UTF-16

  1. BMP文字を16ビット符号単位
  2. 他をサロゲートペアを使って16ビット符号単位二つで表す。
  3. ファイルの先頭にバイト順マーク (BOM) が付与。(U+FEFF) → FF FEならリトリエンディアン、FE FFならビッグエンディアン

UTF-32

  1. Unicodeのすべての符号位置を単一長の符号単位として32ビットで表現する
  2. ファイルのサイズが大きくなる
  3. BOM FF FE 00 00ならリトルエンディアン00 00 FE FFならビッグエンディアン

Hello World 4回目

アプリ作る。cakephp 3.5

composer create-project --prefer-dist cakephp/app MyApp2017

中に入る

cd MyApp2017/

sqlite3のdb作成

sqlite3 MyApp2017.db
sqlite> .tables
sqlite> .q

コンフィグでデータベースをsqlite3に。atomは好きなエディタで。

atom ./config/app.php
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Sqlite',
            'persistent' => false,
            'database' => 'MyApp2017.db',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,

ここでサーバを立ち上げて確認する。

./bin/cake server

http://localhost:8765/にアクセス。databaseにつながっていることを確認。

ここでDBのテーブルを考える。
ログインしたユーザが書き込める掲示板という発想。

ユーザーテーブルを作ってみる。

rodhos.hatenadiary.jp

./bin/cake bake migration CreateUsers name:string description:text created modified


/config/Migrationsフォルダに20170911144034_CreateUsers.phpファイルができる。

    public function change()
    {
        $table = $this->table('users');
        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->addColumn('description', 'text', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->addColumn('modified', 'datetime', [
            'default' => null,
            'null' => false,
        ]);
        $table->create();
    }

実際に作るには

./bin/cake migrations migrate

確認する。

sqlite3 MyApp2017.db
.tables
.schema

たしかにusersができている。idというautoincrementな主キーは自動で追加されるようだ。

次にUsersのモデル(テーブルとエンティティ)を作る。

./bin/cake bake model Users

これで

src/Model/Table/UsersTable.php

src/Model/Entity/User.php

ができた。

class UsersTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('users');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

        $this->addBehavior('Timestamp');
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->scalar('name')
            ->allowEmpty('name');

        $validator
            ->scalar('description')
            ->allowEmpty('description');

        return $validator;
    }
}
class User extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * Note that when '*' is set to true, this allows all unspecified fields to
     * be mass assigned. For security purposes, it is advised to set '*' to false
     * (or remove it), and explicitly make individual fields accessible as needed.
     *
     * @var array
     */
    protected $_accessible = [
        '*' => true,
        'id' => false
    ];
}

調子に乗ってユーザーコントローラを作る。

./bin/cake bake controller Users

これで
/src/Controller/UsersController.php ができた。

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Users Controller
 *
 * @property \App\Model\Table\UsersTable $Users
 *
 * @method \App\Model\Entity\User[] paginate($object = null, array $settings = [])
 */
class UsersController extends AppController
{

    /**
     * Index method
     *
     * @return \Cake\Http\Response|void
     */
    public function index()
    {
        $users = $this->paginate($this->Users);

        $this->set(compact('users'));
        $this->set('_serialize', ['users']);
    }

    /**
     * View method
     *
     * @param string|null $id User id.
     * @return \Cake\Http\Response|void
     * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
     */
    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);

        $this->set('user', $user);
        $this->set('_serialize', ['user']);
    }

    /**
     * Add method
     *
     * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->getData());
            if ($this->Users->save($user)) {
                $this->Flash->success(__('The user has been saved.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The user could not be saved. Please, try again.'));
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Edit method
     *
     * @param string|null $id User id.
     * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->getData());
            if ($this->Users->save($user)) {
                $this->Flash->success(__('The user has been saved.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The user could not be saved. Please, try again.'));
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Delete method
     *
     * @param string|null $id User id.
     * @return \Cake\Http\Response|null Redirects to index.
     * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $user = $this->Users->get($id);
        if ($this->Users->delete($user)) {
            $this->Flash->success(__('The user has been deleted.'));
        } else {
            $this->Flash->error(__('The user could not be deleted. Please, try again.'));
        }

        return $this->redirect(['action' => 'index']);
    }
}


最後にtemplateをbakeする。

./bin/cake bake template Users

src/Template/Users/index.ctp

src/Template/Users/view.ctp

src/Template/Users/add.ctp

src/Template/Users/edit.ctp

が生成された。

このあたりでもう一度サーバーを立ち上げみてみる。

http://localhost:8765/users にアクセスするとユーザー一覧が出てくる。

続く。

型を消す

親クラスはジェネリクスをもたない。メソッドがジェネリクスになっている。
子クラスはジェネリクスになっている。

書いてみた。実行時エラーになるのはいやだ。。

import Foundation

class A {
    let b:B
    init<T>(_ v:T) {
        self.b = C(v)
    }
    
    /// 問題点 なんでも良いことになってしまっている。
    func get<T>() -> T {
        return b.get()
    }
}

class B {
    func get<T>()->T {
        let this = self as! C<T>
        return this.get()
    }
}

class C<T> :  B {
    let v:T
    
    init(_ v:T) {
        self.v = v
    }
    
    func get()->T {
        return v
    }
}

func sample() {
    let a = A("Hello")
    let b = A(3)
    
    let c:String = a.b.get()
    let d:Int = b.b.get()
    
    let e:Int = a.b.get() // 実行時エラーになる。。orz
    
    
}

右辺値参照

参照とは

宣言と同時に値を入れる必要がある。

右辺値参照

値型をコピーせずに持っておくことができる。

ユニバーサル参照

テンプレートを使う際にそれが右辺値参照なのか参照なのかが展開されるかわからない。
そこで展開に応じてよしなに右辺値参照、参照とわけてくれるようにしてもらう。

コメント

シェアポインタを使うのも手

move

左辺値参照を右辺値参照に持っていく

参考になるところ

Cppの虫

メソッド交換

デバッグではまったのでメソッドの交換を試してみた。以下はそのコード。

extension CLLocationManager {
    func my_requestWhenInUseAuthorization() {
        print("requestWhenInUseAuthorization")
    }
    
    func my_requestAlwaysAuthorization() {
        print("requestAlwaysAuthorization")
    }
    
    func switchReq() {
        sw(from:#selector(requestAlwaysAuthorization), to:#selector(my_requestWhenInUseAuthorization))
        sw(from:#selector(requestWhenInUseAuthorization), to:#selector(my_requestWhenInUseAuthorization))
    }
    
    func sw(from:Selector, to:Selector) {
        let method = class_getInstanceMethod(CLLocationManager.self, from)
        let method2 = class_getInstanceMethod(CLLocationManager.self, to)
        method_exchangeImplementations(method, method2)
    }
}

framework作成

フレームワーク作成

1. CocoaTouch frameworkを作る。Objective-C, 名前はSampleと仮定。
2. クラスを作成。(MyServiceとする。)
3. 公開するヘッダーをProjectのTargetsのBuild PhasesのHeaderにHeadersのPublicに加える。
4. Sample.h(アンブレラヘッダー)にそのpublic headerをimport (#import )

他のプロジェクト(Swift想定)から使う。

ここではプロジェクトに依存関係を作り、フレームワークもビルドしつつ使用することを仮定。
1. プロジェクトのtarget依存性にフレームワークのプロジェクト追加 → 違) embeded projectかも。
2. フレームワーク追加(これは必要?)
3. Objective-Cフレームワークなのでブリッジヘッダーを作る。
4. ブリッジヘッダーでimport (#import )
5. swiftファイルで自由に使えるようになる。

Swiftの場合、クラスなどにpublicをつけておかないとみえない。ファイルにて適宜importして使うことになる。

lipo 複数アーキテクチャのライブラリをまとめる

iOSシミュレータ用と実機用の2つのスタティックライブラリを一つにまとめる。

lipo -output sample.a -create ./Debug-iphoneos/libSample.a  ./Debug-iphonesimulator/libSample.a 

labs.torques.jp
daybysay.hatenablog.com

結合後、ファイルタイプを見る

file sample.a
sample.a: Mach-O universal binary with 2 architectures: [x86_64: current ar archive random library] [arm64: current ar archive random library]
sample.a (for architecture x86_64):	current ar archive random library
sample.a (for architecture arm64):	current ar archive random library