Rodhos Soft

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

カスタムビューのXCode表示

@IBDesignable class MyView: UIView {

    @IBInspectable var cornerRadius : CGFloat = 0.0;
    
    
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
        self.layer.cornerRadius = cornerRadius
        if cornerRadius > 0 {
            self.clipsToBounds = true;
        }
    }
    

}

木構造的なもの

LISPのようなものを作ろうとしたら単に文字列の結合を木構造でやるということになっていた。でもクラスについてだいぶ勉強になった。

//
//  RDLisp.hpp
//  RDLisp
//
//  Created by KatagiriSo on 2017/09/26.
//  Copyright © 2017年 RodhosSoft. All rights reserved.
//

#ifndef RDLisp_hpp
#define RDLisp_hpp

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

namespace RDLisp {
    
    template<class T>
    using Share = std::shared_ptr<T>;

    
    class Printable {
    public:
        virtual std::string description() = 0;
    };
    
    
    class S : public Printable{
    public:
    };
    
    class Value;
    
    class Pair : public S {
    public:
        Share<S> car;
        Share<S> cdr;
        Pair(Share<S> car, Share<S> cdr) {
            this->car = car;
            this->cdr = cdr;
        }
        
        std::string description();
        bool isFunc();
        bool isValue();
    };
    
    
    class Nil : public S {
    public:
        Nil();
        std::string description();
    };
    
    class Symbol;
    class Value;
    
    class Atom  : public S {
    public:
        std::string value;
        Atom(std::string value) {
            this->value = value;
        };
        std::string description();
    };
    
    
    class Value : public Atom {
    public:
        Value(std::string value) : Atom(value) {
            
        };
    };
    
    /// symbol is function
    class Symbol : public Atom {
    public:
        Symbol(std::string value) : Atom(value) {
            
        };
    };
    

    Share<S> eval(Share<S> s);
    Share<S> apply(Share<Symbol> sy, Share<S> target);
}



#endif /* RDLisp_hpp */
//
//  RDLisp.cpp
//  RDLisp
//
//  Created by KatagiriSo on 2017/09/26.
//  Copyright © 2017年 RodhosSoft. All rights reserved.
//

#include "RDLisp.hpp"

#include <vector>
#include <string>

namespace RDLisp {
    
    template <class T>
    Share<T> scast(Share <S> s) {
        return std::dynamic_pointer_cast<T>(s);
    }
    
    Share<Value> add(Share<S> target, int n) {
        std::cout <<std::string(n, ' ') << "+"<< "\n";

        if (auto v = scast<Value>(target)) {
            std::cout << std::string(n, ' ')<< v->description() << "\n";
            return v;
        }
        
        if (auto p = scast<Pair>(target)) {
            
            if (auto s = scast<Symbol>(p->car)) {
                auto cdr_after = apply(s, p->cdr);
                if (auto v = scast<Value>(cdr_after)) {
                    return v;

                } else {
                    return nullptr;
                }
            }
            
            if (auto f = scast<Value>(p->car)) {
                auto l = add(p->cdr, n+1);
                if (f&&l) {
                    auto v = std::make_shared<Value>(f->value + l->value);
                    
                    std::cout <<std::string(n, ' ')<< "["<< v->description() << "]"<< "\n";
                    return v;
                } else {
                    return nullptr;
                }
            }
            
            if (auto l = scast<Value>(p->cdr)) {
                auto f = add(p->car, n+1);
                if (f&&l) {
                    auto v = std::make_shared<Value>(f->value + l->value);
                    
                    std::cout <<std::string(n, ' ')<< "["<< v->description() << "]"<< "\n";
                    return v;
                } else {
                    return nullptr;
                }
            }
            
            
            auto f = add(p->car, n+1);
            auto l = add(p->cdr, n+1);
            if (f&&l) {
                auto v = std::make_shared<Value>(f->value + l->value);
                
                std::cout <<std::string(n, ' ')<< "["<< v->description() << "]"<< "\n";
                return v;
            } else {
                return nullptr;
            }
        }
        
        std::cout <<std::string(n, ' ')<< "nullptr"<< "\n";
        return nullptr;
    }
    

    
    Share<S> eval(Share<S> exp) {
        auto pair = scast<Pair>(exp);
        if (pair) {
            if (pair->isFunc()) {
                auto sy = std::dynamic_pointer_cast<Symbol>(pair->car);
//                auto crd_after = eval(pair->cdr);
//                std::cout << "crd_after" + crd_after->description() + "\n";
                return apply(sy, pair->cdr);
//                return apply(sy, crd_after);

            }
        }
        
        return exp;
    }
    
    Share<S> apply(Share<Symbol> sy, Share<S> target) {
        
        if (sy->value == "+") {
            if (auto v = scast<Value>(target)) {
                return target;
            }
            
            if (auto p = scast<Pair>(target)) {
                auto exp = add(target, 0);
                if (exp) {
                    return exp;
                }
            }
            
        }
        
        return std::make_shared<Pair>(sy,target);
    }

    
    bool Pair::isFunc() {
        auto sy = std::dynamic_pointer_cast<Symbol>(this->car);
        if (sy) {
            return true;
        }
        return false;
    }
    


    
    std::string Pair::description() {
        return "(" + this->car->description() + " " + this->cdr->description() + ")";  
    }
    
    std::string Atom::description() {
        return this->value;
    }

    std::string Nil::description() {
        return "";
    }

    

}

実行は次のようにする。面倒。

//
//  main.cpp
//  RDLisp
//
//  Created by KatagiriSo on 2017/09/26.
//  Copyright © 2017年 RodhosSoft. All rights reserved.
//

#include <iostream>
#include "RDLisp.hpp"

int main(int argc, const char * argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";
    
    using namespace RDLisp;
    using namespace std;
    
    auto a1 = make_shared<Value>("a1");
    auto a2 = make_shared<Value>("a2");
    auto pair_a = make_shared<Pair>(a1,a2);
    auto b = make_shared<Value>("b");
    auto c = make_shared<Value>("c");
    auto d = make_shared<Value>("d");
    auto add = make_shared<Symbol>("+");
    auto add2 = make_shared<Symbol>("+");
    auto pair_ab = make_shared<Pair>(pair_a,b);
    auto pair_cd = make_shared<Pair>(c,d);
    auto pair = make_shared<Pair>(add, pair_ab);
    auto pair2 = make_shared<Pair>(add, pair_cd);
    auto pair3 = make_shared<Pair>(pair, pair2);
    auto pair4 = make_shared<Pair>(add2, pair3);
    
    std::cout << pair4->description() << "\n";
    
    auto res = eval(pair4);
    
    std::cout << res->description() << "\n";

    
    return 0;
}

結果は

Hello, World!
(+ ((+ ((a1 a2) b)) (+ (c d))))
+
 +
+
 +
  +
  a2
 [a1a2]
[a1a2b]
 +
+
 +
 d
[cd]
[a1a2bcd]
a1a2bcd
Program ended with exit code: 0

練習 文字列等

文字列等の練習にコマンドと引数の対をとるコードを書いてみた。引数には対自体を入れることができるとする。

結果はこんな感じになる。

>+ * 100
(+ (* ( 100)))
#ifndef RDCalc_hpp
#define RDCalc_hpp

#include <stdio.h>
#include "RDConfig.hpp"

class Command {
public:
    Command() = default;
    ~Command() = default;
    
    std::string name;
    std::string description();
};


class Value;

class Term {
public:
    Term() = default;
    ~Term() = default;
    std::shared_ptr<Command> command;
    std::shared_ptr<Term> value;
    std::string name;
    std::string description();
};

class Value : public Term {
public:
    Value();
    ~Value() = default;
};




class RDCalc {
public:
    RDCalc();
    ~RDCalc();
    void start();
    std::string calc(std::string);
    std::shared_ptr<Term> state;
private:
    std::shared_ptr<Term> getTerm(std::vector<std::string>);
    
};

#endif /* RDCalc_hpp */
//
//  RDCalc.cpp
//  RDCalcSystem
//
//  Created by KatagiriSo on 2017/09/26.
//  Copyright © 2017年 RodhosSoft. All rights reserved.
//

#include <sstream>
#include <iostream>
#include "RDCalc.hpp"
#include<string>
#include<vector>

RDCalc::RDCalc() {
    
}

RDCalc::~RDCalc() {
    std::cout << "RDCalc dec.\n";

}

void RDCalc::start() {
    std::string input;

    while(true) {
        std::cout << ">";
        
        std::string str;
        std::vector<std::string> v;

        std::getline(std::cin, str);
        
        auto output = RDCalc::calc(str);
        std::cout << output << std::endl;
    }
}

std::vector<std::string> splitString(std::string input) {
    std::stringstream ss;
    ss << input;
    
    std::vector<std::string> v;
    
    std::string com;
    while(ss >> com) {
        v.push_back(com);
    }
    return v;
}




std::string RDCalc::calc(std::string input) {
    
    std::vector<std::string> v = splitString(input);
    
    this->state = getTerm(v);
    
    if (this->state == nullptr) {
        return "?\n";
    }
    
    return this->state->description();
}

std::shared_ptr<Term> RDCalc::getTerm(std::vector<std::string> v) {
    
    auto term = std::make_shared<Term>();
    auto command = std::make_shared<Command>();
    
//    if (m=="+") {
//
    auto m = v.front();
    v.erase(v.begin());
    if (v.empty()) {
        term->command = command;
        term->command->name = "";
        auto value = std::make_shared<Value>();
        value->name = m;
        term->value = value;
    } else {
        command->name = m;
        
        term->command = command;
        term->value = getTerm(v);
    }
    
    
    return term;
}


std::string Term::description() {
    if (this->value == nullptr) {
        return this->name;
    }
    return "(" + this->command->description() + " " + this->value->description() + ")";
}

std::string Command::description() {
    return this->name;
}

Value::Value() {
    command = std::make_shared<Command>();
    command->name = "";
}

たまに使うショートカット

⌘ {  でタブ移動
⌘デリート でその文頭からカーソルまで削除
⌘→ 行末へ移動
エスケープ 補完リスト表示
⌘T 新しいタブ作成



キー

⌘ コマンド

⌥ オプション

⇧ シフト

⌃ コントロール

エスケープは○に左上に棒が入っているマーク

OS X Yosemite: メニューに表示される記号

コマンドライン

参考
yamaimo.hatenablog.jp


コンパイルする場合はswiftcを使う。

$ swiftc -help
OVERVIEW: Swift compiler

USAGE: swiftc [options] <inputs>

MODES:
  -dump-ast        Parse and type-check input file(s) and dump AST(s)
  -dump-parse      Parse input file(s) and dump AST(s)
  -dump-scope-maps <expanded-or-list-of-line:column>
                   Parse and type-check input file(s) and dump the scope map(s)
  -dump-type-refinement-contexts
                   Type-check input file(s) and dump type refinement contexts(s)
  -emit-assembly   Emit assembly file(s) (-S)
  -emit-bc         Emit LLVM BC file(s)
  -emit-executable Emit a linked executable
  -emit-ir         Emit LLVM IR file(s)
  -emit-library    Emit a linked library
  -emit-object     Emit object file(s) (-c)
  -emit-sibgen     Emit serialized AST + raw SIL file(s)
  -emit-sib        Emit serialized AST + canonical SIL file(s)
  -emit-silgen     Emit raw SIL file(s)
  -emit-sil        Emit canonical SIL file(s)
  -parse           Parse input file(s)
  -print-ast       Parse and type-check input file(s) and pretty print AST(s)
  -typecheck       Parse and type-check input file(s)

OPTIONS:
  -application-extension  Restrict code to those available for App Extensions
  -assert-config <value>  Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement.
  -continue-building-after-errors
                          Continue building, even after errors are encountered
  -driver-time-compilation
                          Prints the total time it took to execute all compilation tasks
  -D <value>              Marks a conditional compilation flag as true
  -embed-bitcode-marker   Embed placeholder LLVM IR data as a marker
  -embed-bitcode          Embed LLVM IR bitcode as data
  -emit-dependencies      Emit basic Make-compatible dependencies files
  -emit-module-path <path>
                          Emit an importable module to <path>
  -emit-module            Emit an importable module
  -emit-objc-header-path <path>
                          Emit an Objective-C header file to <path>
  -emit-objc-header       Emit an Objective-C header file
  -fixit-all              Apply all fixits from diagnostics without any filtering
  -fixit-code             Get compiler fixits as code edits
  -framework <value>      Specifies a framework which should be linked against
  -F <value>              Add directory to framework search path
  -gdwarf-types           Emit full DWARF type info.
  -gline-tables-only      Emit minimal debug info for backtraces only
  -gnone                  Don't emit debug info
  -g                      Emit debug info. This is the preferred setting for debugging with LLDB.
  -help                   Display available options
  -import-underlying-module
                          Implicitly imports the Objective-C half of a module
  -index-store-path <path>
                          Store indexing data to <path>
  -I <value>              Add directory to the import search path
  -j <n>                  Number of commands to execute in parallel
  -L <value>              Add directory to library link search path
  -l<value>               Specifies a library which should be linked against
  -module-cache-path <value>
                          Specifies the Clang module cache path
  -module-link-name <value>
                          Library to link against when using this module
  -module-name <value>    Name of the module to build
  -nostdimport            Don't search the standard library import path for modules
  -num-threads <n>        Enable multi-threading and specify number of threads
  -Onone                  Compile without any optimization
  -Ounchecked             Compile with optimizations and remove runtime safety checks
  -output-file-map <path> A file which specifies the location of outputs
  -O                      Compile with optimizations
  -o <file>               Write output to <file>
  -parse-as-library       Parse the input file(s) as libraries, not scripts
  -parse-sil              Parse the input file as SIL code, not Swift source
  -parseable-output       Emit textual output in a parseable format
  -profile-coverage-mapping
                          Generate coverage data for use with profiled execution counts
  -profile-generate       Generate instrumented code to collect execution counts
  -sanitize-coverage=<type>
                          Specify the type of coverage instrumentation for Sanitizers and additional options separated by commas
  -sanitize=<check>       Turn on runtime checks for erroneous behavior.
  -save-temps             Save intermediate compilation results
  -sdk <sdk>              Compile against <sdk>
  -serialize-diagnostics  Serialize diagnostics in a binary format
  -static-executable      Statically link the executable
  -static-stdlib          Statically link the Swift standard library
  -suppress-warnings      Suppress all warnings
  -swift-version <vers>   Interpret input according to a specific Swift language version number
  -target-cpu <value>     Generate code for a particular CPU variant
  -target <value>         Generate code for the given target
  -tools-directory <directory>
                          Look for external executables (ld, clang, binutils) in <directory>
  -use-ld=<value>         Specifies the linker to be used
  -version                Print version information and exit
  -v                      Show commands to run and use verbose output
  -warnings-as-errors     Treat warnings as errors
  -whole-module-optimization
                          Optimize input files together instead of individually
  -Xcc <arg>              Pass <arg> to the C/C++/Objective-C compiler
  -Xlinker <value>        Specifies an option which should be passed to the linker

そしてswiftというコマンドもある。

$ swift -h
OVERVIEW: Swift compiler

USAGE: swift [options] <inputs>

OPTIONS:
  -assert-config <value> Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement.
  -continue-building-after-errors
                         Continue building, even after errors are encountered
  -D <value>             Marks a conditional compilation flag as true
  -framework <value>     Specifies a framework which should be linked against
  -F <value>             Add directory to framework search path
  -gdwarf-types          Emit full DWARF type info.
  -gline-tables-only     Emit minimal debug info for backtraces only
  -gnone                 Don't emit debug info
  -g                     Emit debug info. This is the preferred setting for debugging with LLDB.
  -help                  Display available options
  -index-store-path <path>
                         Store indexing data to <path>
  -I <value>             Add directory to the import search path
  -j <n>                 Number of commands to execute in parallel
  -L <value>             Add directory to library link search path
  -l<value>              Specifies a library which should be linked against
  -module-cache-path <value>
                         Specifies the Clang module cache path
  -module-link-name <value>
                         Library to link against when using this module
  -module-name <value>   Name of the module to build
  -nostdimport           Don't search the standard library import path for modules
  -num-threads <n>       Enable multi-threading and specify number of threads
  -Onone                 Compile without any optimization
  -Ounchecked            Compile with optimizations and remove runtime safety checks
  -O                     Compile with optimizations
  -sdk <sdk>             Compile against <sdk>
  -static-executable     Statically link the executable
  -static-stdlib         Statically link the Swift standard library
  -suppress-warnings     Suppress all warnings
  -swift-version <vers>  Interpret input according to a specific Swift language version number
  -target-cpu <value>    Generate code for a particular CPU variant
  -target <value>        Generate code for the given target
  -use-ld=<value>        Specifies the linker to be used
  -version               Print version information and exit
  -v                     Show commands to run and use verbose output
  -warnings-as-errors    Treat warnings as errors
  -Xcc <arg>             Pass <arg> to the C/C++/Objective-C compiler
  -Xlinker <value>       Specifies an option which should be passed to the linker

Swiftは対話モードで実行できる。

$ swift
Welcome to Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42). Type :help for assistance.
  1> 3+2
$R0: Int = 5
  2> class A { 
  3.     var hoge:Int? = nil 
  4.     func hello() { 
  5.         print("hello \(hoge)") 
  6.     } 
  7. } 
  8> let a = A()
a: A = {
  hoge = nil
}
  9> a.hoge = 100
 10> a.hello()
hello Optional(100)
 11>  

クラスとtrait

traitは入れ子にできる。

<?php

class BaseClass {

}

trait Helloable {
  public function hello() {
    echo "hello\n";
  }
}

class MyClass extends BaseClass {

  use Helloable;

  public $hoge = "ABC\n";

  public function display() {
    echo $this->hoge;
  }
}

$ins = new MyClass();
$ins->display();
$ins->hello();

?>

文字列ストリーム風

以下でStream(Array(src.characters))としてやれば文字列stream風になる。

    class Stream<T> {
        let m_value:[T]
        var count = 0
        init(_ v:[T]) {
            m_value = v
        }
        
        func get()->T? {
            if count < m_value.count {
                let ret = m_value[count]
                count = count + 1
                print("\(ret)")
                return ret
                
            } else {
                return nil
            }
        }
        
        func unget() {
            count = count - 1
            print("unget")
            if count < 0 {
                count = count + 1
            }
        }
    }

GCDの例

SwiftでGCDのgroup等の使い方のサンプルを書いてみた。

//
//  ViewController.swift
//  GCDSample
//
//  Created by KatagiriSo on 2017/09/19.
//  Copyright © 2017年 RodhosSoft. All rights reserved.
//

import UIKit

class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource{
    
    
    struct Menu {
        let name:String
    }
    
    let menu:[Menu] = [Menu(name: NSStringFromSelector(#selector(AsyncBarrier))),
                       Menu(name: NSStringFromSelector(#selector(GroupWait))),
                       Menu(name: NSStringFromSelector(#selector(GroupNotify))),
                       Menu(name: NSStringFromSelector(#selector(GroupEnterLeave))),
                       Menu(name: NSStringFromSelector(#selector(SemaphoExample)))

        
                       ]
    
    func makeEqualConstraint(item:Any, toItem:Any, attribute:NSLayoutAttribute) ->  NSLayoutConstraint{
        return NSLayoutConstraint(item: item, attribute: attribute, relatedBy: .equal, toItem: toItem, attribute: attribute, multiplier: 1.0, constant: 0)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let tableView:UITableView = UITableView(frame: self.view.bounds)
        tableView.delegate = self
        tableView.dataSource = self
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")

        
        self.view.addSubview(tableView)
        
        
        let l = makeEqualConstraint(item: tableView, toItem: self.view, attribute: .left)
        let r = makeEqualConstraint(item: tableView, toItem: self.view, attribute: .right)
        let t = makeEqualConstraint(item: tableView, toItem: self.view, attribute: .top)
        let b = makeEqualConstraint(item: tableView, toItem: self.view, attribute: .bottom)
        
        l.isActive = true
        r.isActive = true
        t.isActive = true
        b.isActive = true

    }
    
    

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return menu.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = menu[indexPath.row].name
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selector = NSSelectorFromString(menu[indexPath.row].name)
        self.perform(selector)
    }
    
    func AsyncBarrier() {
        print("\(#function)")
        
        let queue = DispatchQueue(label: "asyncBarrierTest")
        [1,2,3,4,5,6,7,8,9,10].forEach { n in
            queue.async {
                print("done \(n)")
            }
        }
        
        queue.async(flags:.barrier) {
            print("all done")
        }
        
        print("\(#function) end")
    }
    
    func GroupWait() {
        print("\(#function)")
        
        let queue = DispatchQueue(label: "groupWaitTest")
        let group = DispatchGroup()
        
        for n in 1...100 {
            queue.async(group: group) {
                print("done \(n)")
            }
        }
        
        print("group.wait")
        let _ = group.wait(timeout: DispatchTime.distantFuture)
        
        
        print("\(#function) end")
    }
    
    func GroupNotify() {
        print("\(#function)")
        
        let queue = DispatchQueue(label: "GroupNotify")
        let group = DispatchGroup()
        
        for n in 1...100 {
            queue.async(group: group) {
                print("done \(n)")
            }
        }
        
        group.notify(queue: queue) {
            print("done")
        }
        
        print("\(#function) end")
    }
    
    func GroupEnterLeave() {
        print("\(#function)")
        
        let queue = DispatchQueue(label: "GroupNotify")
        let group = DispatchGroup()
        
        for n in 1...100 {
            group.enter()
            queue.async {
                print("done \(n)")
                group.leave()
            }
        }
        
        group.notify(queue: queue) {
            print("done")
        }
        
        print("\(#function) end")
    }
    
    func SemaphoExample() {
        
        print("\(#function)")
        
        let semapho = DispatchSemaphore(value: 0)
        
//        semapho.wait()

        DispatchQueue.global().async {
            for n in 1...100 {
                print("count \(n)")
            }
            
            semapho.signal()
        }
        
        
        print("wait")
        semapho.wait()
        
        
        print("\(#function) end")
    }

    


}

View

rederによってViewがレンダリングされる。その部分をみてみる。

public function render($view = null, $layout = null)
    {
// すでにレンダリングされているなら終わり
        if ($this->hasRendered) {
            return null;
        }

// レイアウトがメソッドの引数に指定されているならそれをレイアウトとして使う
        $defaultLayout = null;
        if ($layout !== null) {
            $defaultLayout = $this->layout;
            $this->layout = $layout;
        }

        $viewFileName = $view !== false ? $this->_getViewFileName($view) : null;

        if ($viewFileName) {
            $this->_currentType = static::TYPE_TEMPLATE;
/// レンダリングイベント通知
            $this->dispatchEvent('View.beforeRender', [$viewFileName]);

/// _render関数でレイアウトし、ブロックのcontentとしてsetする。
            $this->Blocks->set('content', $this->_render($viewFileName));

/// レンダリング終了通知
            $this->dispatchEvent('View.afterRender', [$viewFileName]);
        }

/// レウアウトがあるならrenderLayoutでレンダリングし、ブロックのcontentとしてセットする。
        if ($this->layout && $this->autoLayout) {
            $this->Blocks->set('content', $this->renderLayout('', $this->layout));
        }
        if ($layout !== null) {
            $this->layout = $defaultLayout;
        }

        $this->hasRendered = true;

/// ブロックのcontentを取得し返却する。
        return $this->Blocks->get('content');
    }


_render関数、ここが本体になる。テンプレートの継承などもここで解決する。

    protected function _render($viewFile, $data = [])
    {
// 引数で$dataが指定されていなければviewVarsを$dataとして使う。
        if (empty($data)) {
            $data = $this->viewVars;
        }
        $this->_current = $viewFile;
        $initialBlocks = count($this->Blocks->unclosed());

// イベント通知
        $this->dispatchEvent('View.beforeRenderFile', [$viewFile]);

// テンプレートを_evaluateする。ここでファイル内のコードが実行される?
        $content = $this->_evaluate($viewFile, $data);

// イベント通知
        $afterEvent = $this->dispatchEvent('View.afterRenderFile', [$viewFile, $content]);
        if ($afterEvent->getResult() !== null) {
            $content = $afterEvent->getResult();
        }

// ここで継承しているテンプレートがあればアサインし
        if (isset($this->_parents[$viewFile])) {
            $this->_stack[] = $this->fetch('content');
            $this->assign('content', $content);

//  親のレンダリング
            $content = $this->_render($this->_parents[$viewFile]);
            $this->assign('content', array_pop($this->_stack));
        }

        $remainingBlocks = count($this->Blocks->unclosed());

        if ($initialBlocks !== $remainingBlocks) {
            throw new LogicException(sprintf(
                'The "%s" block was left open. Blocks are not allowed to cross files.',
                $this->Blocks->active()
            ));
        }

        return $content;
    }


$this->Blocksのところ、ビューブロックをおさえる必要がある。

ViewBuilder

ビューを作る。

build()のところで作っている。

public function build($vars = [], ServerRequest $request = null, Response $response = null, EventManager $events = null)
    {
        $className = $this->_className;
        if ($className === null) {
            $className = App::className('App', 'View', 'View') ?: 'Cake\View\View';
        }
        if ($className === 'View') {
            $className = App::className($className, 'View');
        } else {
            $className = App::className($className, 'View', 'View');
        }
        if (!$className) {
            throw new MissingViewException(['class' => $this->_className]);
        }

        $data = [
            'name' => $this->_name,
            'templatePath' => $this->_templatePath,
            'template' => $this->_template,
            'plugin' => $this->_plugin,
            'theme' => $this->_theme,
            'layout' => $this->_layout,
            'autoLayout' => $this->_autoLayout,
            'layoutPath' => $this->_layoutPath,
            'helpers' => $this->_helpers,
            'viewVars' => $vars,
        ];
        $data += $this->_options;

        return new $className($request, $response, $events, $data);
    }

builderのbuild()はViewVarsTraitのcreateView($viewClass = null)で呼ばれている。

ViewVarsTraitはControllerで使われている。

Controllerのrender($view = null, $layout = null)関数。

    public function render($view = null, $layout = null)
    {
// ビュービルダーを取得
        $builder = $this->viewBuilder();
// ビルダーにテンプレートパスが設定されてないならコントローラの_viewPath()で設定する。
        if (!$builder->getTemplatePath()) {
            $builder->setTemplatePath($this->_viewPath());
        }
// リクエストにbareパラムがあるならAutoLayoutを切る
        if ($this->request->getParam('bare')) {
            $builder->enableAutoLayout(false);
        }
        $builder->getClassName($this->viewClass);
// コントローラのautoRenderを切る
        $this->autoRender = false;

// beforeRenderイベントを通知
        $event = $this->dispatchEvent('Controller.beforeRender');
        if ($event->getResult() instanceof Response) {
            return $event->getResult();
        }
        if ($event->isStopped()) {
            return $this->response;
        }

// ビルダーにテンプレートが設定されなく、リクエストにアクションが設定されているなら、そのアクションからテンプレートを引っ張ってきて設定する。
        if ($builder->getTemplate() === null && $this->request->getParam('action')) {
            $builder->setTemplate($this->request->getParam('action'));
        }
// ビューを生成する。
        $this->View = $this->createView();

// ビューをレイアウトを使ってレンダリングする。
        $this->response->body($this->View->render($view, $layout));

        return $this->response;
    }

renderはControllerのactionで明示的に呼ぶこともできるがautoRenderを設定しているときはactionが呼ばれたときに自動的に呼ばれている。その機構はActionDispatcherの_invoke(Controller $controller)にある。

protected function _invoke(Controller $controller)
    {
//  invokeControllerイベントを通知する。
        $this->dispatchEvent('Dispatcher.invokeController', ['controller' => $controller]);

        $result = $controller->startupProcess();
        if ($result instanceof Response) {
            return $result;
        }

// コントローラのinvokeActionを呼ぶ ここでコントローラのアクションが呼ばれる。
        $response = $controller->invokeAction();

// アクションからのレスポンスがなくてautoRenderがtrueならレンダリングしてそのresponseを返却する。
        if ($response !== null && !($response instanceof Response)) {
            throw new LogicException('Controller actions can only return Cake\Http\Response or null.');
        }

        if (!$response && $controller->autoRender) {
            $response = $controller->render();
        } elseif (!$response) {
            $response = $controller->response;
        }

        $result = $controller->shutdownProcess();
        if ($result instanceof Response) {
            return $result;
        }

        return $response;
    }

この_invoke関数はdispatch(ServerRequest $request, Response $response)関数から呼ばれる。

public function dispatch(ServerRequest $request, Response $response)
    {
        if (Router::getRequest(true) !== $request) {
            Router::pushRequest($request);
        }

//  beforeDispatchを通知
        $beforeEvent = $this->dispatchEvent('Dispatcher.beforeDispatch', compact('request', 'response'));

        $request = $beforeEvent->getData('request');
        if ($beforeEvent->getResult() instanceof Response) {
            return $beforeEvent->getResult();
        }

// コントローラを生成しているらしい。。

        // Use the controller built by an beforeDispatch
        // event handler if there is one.
        if ($beforeEvent->getData('controller') instanceof Controller) {
            $controller = $beforeEvent->getData('controller');
        } else {
            $controller = $this->factory->create($request, $response);
        }

// 生成したコントローラに対して_invokeを実行。
        $response = $this->_invoke($controller);

// requestにreturnが設定されていたらresponseを返却
        if (isset($request->params['return'])) {
            return $response;
        }

// afterDispatchを通知
        $afterEvent = $this->dispatchEvent('Dispatcher.afterDispatch', compact('request', 'response'));

// 
        return $afterEvent->getData('response');
    }

このdispatchはDispatcherのdispatchで呼ばれる。

    public function dispatch(ServerRequest $request, Response $response)
    {
        $actionDispatcher = new ActionDispatcher(null, $this->eventManager(), $this->_filters);
        $response = $actionDispatcher->dispatch($request, $response);
        if (isset($request->params['return'])) {
            return $response->body();
        }

        return $response->send();
    }

DispatcherはDispatcherFactoryのcreate関数で作られる。

    public static function create()
    {
        $dispatcher = new Dispatcher();
        foreach (static::$_stack as $middleware) {
            $dispatcher->addFilter($middleware);
        }

        return $dispatcher;
    }

アクションがどのように呼ばれるのか

このあたりで、CakePHPでどのようにアクションが呼ばれるのかについて調べていることを自覚した。

webrootのindex.phoを調べてみる。

// Bind your application to the server.
$server = new Server(new Application(dirname(__DIR__) . '/config'));

// Run the request/response through the application
// and emit the response.
$server->emit($server->run());

このあたりがポイントか。このApplicationはBaseApplicationを継承している。BaseApplicationではgetDispatcherでActionDispatcherを作り、__invokeでdispatchしている。

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
    {
        return $this->getDispatcher()->dispatch($request, $response);
    }


というわけで

BaseApplicationの__invokeでActionDispatcherがdispatchし、その関数内でコントローラに_invokeする。
_invokeはautoRenderがtrueだとコントローラのrender()関数を呼び、render()関数はビュービルダーを使ってビューをビルドする。という流れのようだ。

Server

サーバは以下の方法でRunnerにApplicationとミドルウェアを追加してrunし、Runnerは__invokeして回るのでリクエストが処理されていくようだ。

public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
    {
        $this->app->bootstrap();
        $response = $response ?: new Response();

// リクエスト取得
        $request = $request ?: ServerRequestFactory::fromGlobals();

// ミドルウェア
        $middleware = $this->app->middleware(new MiddlewareQueue());
        if (!($middleware instanceof MiddlewareQueue)) {
            throw new RuntimeException('The application `middleware` method did not return a middleware queue.');
        }
// ミドルウェア生成イベント通知
        $this->dispatchEvent('Server.buildMiddleware', ['middleware' => $middleware]);

// まずミドルウェアとしてApplicationを登録
        $middleware->add($this->app);

// runnerにrunしレスポンスを取得
        $response = $this->runner->run($middleware, $request, $response);

        if (!($response instanceof ResponseInterface)) {
            throw new RuntimeException(sprintf(
                'Application did not create a response. Got "%s" instead.',
                is_object($response) ? get_class($response) : $response
            ));
        }

// レスポンスを返却
        return $response;
    }

このrunはMiddlewareDispatcherにてexecute($request)されるようだ。

public function execute($request)
    {
        try {
            $reflect = new ReflectionClass($this->_class);
            $app = $reflect->newInstanceArgs($this->_constructorArgs);
        } catch (ReflectionException $e) {
            throw new LogicException(sprintf(
                'Cannot load "%s" for use in integration testing.',
                $this->_class
            ));
        }

        // Spy on the controller using the initialize hook instead
        // of the dispatcher hooks as those will be going away one day.
        EventManager::instance()->on(
            'Controller.initialize',
            [$this->_test, 'controllerSpy']
        );

        $server = new Server($app);
        $psrRequest = $this->_createRequest($request);

        return $server->run($psrRequest);
    }

View

AppViewが基底でViewを継承している。

自作のヘルパーなどはinitialize内で $this->loadHelper('MyUtils');してやることで使えるようになる。

View

EventDispatcherInterfaceを実装している。

使用しているtrait

CellTrait

EventDispatcherTrait;

LogTrait;

RequestActionTrait;

ViewVarsTrait;

変数

_helpers

Blocks

plugin

name ビューを作ったコントローラの名前

helpers ビルトインされているヘルパー

templatePath テンプレートのパス

template テンプレートの名前

layout 使用しているレイアウト

layoutPath 使用しているレイアウトの名前

autoLayout 自動レイアウトのonoff

_ext テンプレートの拡張子 デフォルトはctp

subDir

theme ビューのテーマ

hasRendered ビューがすでにレンダリングされているか

uuids 生成されたDOMのuuid

request

response

elementCache

_passedVars 関連するコントローラからの変数

_paths

_pathsForPlugin

_parents ビューの継承元。View::extend()で使用。

_current

_currentType

_stack ネストされたテンプレート。View::extend()で使用。

TYPE_TEMPLATE

TYPE_ELEMENT

TYPE_LAYOUT

メソッド

initialize()

templatePath($path = null) テンプレートのセットとゲット

layoutPath($path = null) レイアウトのセットとゲット

autoLayout($autoLayout = null)

theme($theme = null)

template($name = null)

layout($name = null)

element($name, array $data = , array $options = )  エレメントの描画。_renderElementを使う。

cache(callable $block, array $options = )

elementExists($name) エレメントがあるかのチェック

render($view = null, $layout = null) 与えられたテンプレートとレイアウトでレンダリングする。

renderLayout($content, $layout = null) レイアウトのレンダリング

getVars() $this->viewVarsの名前を返す

get($var, $default = null) view変数を取得

blocks() ビューブロックのキーを返す。$this->Blocks->keys();

start($name) ビューブロックのスタート $this->Blocks->start($name);

append($name, $value = null) ビューブロックの追加 $this->Blocks->concat($name, $value);

prepend($name, $value) $this->Blocks->concat($name, $value, ViewBlock::PREPEND);

assign($name, $value) ビューブロックのセット $this->Blocks->set($name, $value);

reset($name) ビューブロックのリセット $this->assign($name, '');

fetch($name, $default = '') ビューブロックのコンテンツを取得 $this->Blocks->get($name, $default);

end() ビューブロックのエンド $this->Blocks->end();

exists($name) ビューブロックの存在確認 $this->Blocks->exists($name);

extend($name)  テンプレートの継承

uuid($object, $url) uuidの取得

getCurrentType()

__get($name)

__set($name, $value)

loadHelpers() ヘルパーの読み込み

_render($viewFile, $data = ) レンダリング

_evaluate($viewFile, $dataForView)

helpers() ヘルパー

loadHelper($name, array $config = []) ヘルパーの読み込み

_getViewFileName($name = null)

_inflectViewFileName($name)

_checkFilePath($file, $path)

pluginSplit($name, $fallback = true)

_getLayoutFileName($name = null)

_getElementFileName($name, $pluginCheck = true)

_getSubPaths($basePath)

_paths($plugin = null, $cached = true)

_elementCache($name, $data, $options)

_renderElement($file, $data, $options)