SwiftのRangeの仕組みが理解出来ず苦戦しています


どうしてもRangeの仕組みが理解できず、開始インデックス～取得文字数を算出する方法が分かりません。  
実装したい内容としては、開始インデックス～終了インデックスまでを取得するという内容です。  
JAVAで実装している以下の構文をSwiftで実装する方法が分かりません。  
教えて頂けますでしょうか。

[code]

    // argContentHTML = ホームページのHTML
    // HTMLをParse
    // 先頭・最後インデックスを取得
    intIDSearchIndex = argContentHTML.indexOf("tag1");
    intIDFootSearchIndex = argContentHTML.indexOf("tag2",
                                                intIDSearchIndex + "tag1".length());
    
    // インデックスが見つかったか判定
    if(intIDSearchIndex == -1 ||
       intIDFootSearchIndex == -1){
    
        // どちらかのインデックスが見つからなかった場合、データ追加しない
    }else{
        // 正常の場合、データ設定
        // Entry部からIDを取得
        strRet = argContentHTML.substring(intIDSearchIndex + "tag1".length(),
                                          intIDFootSearchIndex);
    }
    
[/code]




Swift2/Xcode7を前提として、

[code]

    import Foundation
    
    let str = "AAAtag2BBBtag1CCCtag2DDD"
    if let
        tag1 = str.rangeOfString("tag1"),
        tag2 = str.rangeOfString("tag2", range: tag1.endIndex ..< str.endIndex) {
    
            let strRet = String(str[tag1.endIndex ..< tag2.startIndex])
            // -> "CCC"
            // str.substringWithRange(tag1.endIndex ..< tag2.startIndex) も可
    }
    
[/code]

`str.rangeOfString("tag1")` で `str` 中の `"tag1"` が現れる範囲（`Range`）を得ます。  
`str.rangeOfString("tag2", range: tag1.endIndex ..< str.endIndex)` は `"tag2"`
が現れる範囲を`str`の `"tag1"` の終了位置から最後までの範囲で検索します。  
で、標準の `subscript` を使ってスライスを得るのですが、これの型は `String`ではなく、 `String.CharacterView`
なので、`String()` イニシャライザで `String` 型に変換します。

Foundationに頼らずにSwift標準ライブラリだけでやるならこんな感じでしょうか:

[code]

    let str = "AAAtag2BBBtag1CCCtag2DDD"
    
    let tag1 = "tag1".characters
    let tag2 = "tag2".characters
    let str_ = str.characters
    
    if let
        start = str_.indices.indexOf({str_.suffixFrom($0).startsWith(tag1)})?.advancedBy(tag1.count),
        end = (start ..< str_.endIndex).indexOf({str_.suffixFrom($0).startsWith(tag2)}) {
    
            let strRet = String(str_[start ..< end])
            // -> "CCC"
    }
    
[/code]

可読性ひどいですね。  
もうちょっと可読性高めるために、サブストリングの開始インデックスを探すところをファクターアウトして:

[code]

    extension CollectionType where SubSequence.Generator.Element: Equatable {
        /// `self` 中で `sub` の開始インデックスを返す。見つからなければ `nil`。
        /// デフォルトでは `self` 全体から、rangeが与えられたときはその範囲で検索する。
        func startIndexOf(sub: SubSequence, range: Range<Index>? = nil) -> Index? {
            let range = range ?? indices
            return range.indexOf {
                self.suffixFrom($0).startsWith(sub)
            }
        }
    }
    
    let str = "AAAtag2BBBtag1CCCtag2DDD"
    
    let tag1 = "tag1".characters
    let tag2 = "tag2".characters
    let str_ = str.characters
    
    if let
        start = str_.startIndexOf(tag1)?.advancedBy(tag1.count),
        end = str_.startIndexOf(tag2, range: start ..< str_.endIndex) {
    
            let strRet = String(str_[start ..< end])
            // -> "CCC"
    }
    
[/code]

* * *

せっかくなのでベンチマーク  
質問と関係ないですが自分の備忘録のために書かせてください

[code]

    import XCTest
    
    extension CollectionType where SubSequence.Generator.Element: Equatable {
        /// `self` 中で `sub` の開始インデックスを返す。見つからなければ `nil`。
        /// デフォルトでは `self` 全体から、rangeが与えられたときはその範囲で検索する。
        func startIndexOf(sub: SubSequence, range: Range<Index>? = nil) -> Index? {
            let range = range ?? indices
            return range.indexOf {
                self.suffixFrom($0).startsWith(sub)
            }
        }
    }
    
    // オリジナル回答
    func find_rangeOfString(str: String, s1: String, s2: String) -> String? {
        if let
            tag1 = str.rangeOfString(s1),
            tag2 = str.rangeOfString(s2, range: tag1.endIndex ..< str.endIndex) {
                return String(str[tag1.endIndex ..< tag2.startIndex])
        }
        return nil
    }
    
    // 標準ライブラリのみ String.CharacterView で検索
    func find_characters(str: String, s1: String, s2: String) -> String? {
        let s1 = s1.characters
        let s2 = s2.characters
        let str = str.characters
        if let
            start = str.startIndexOf(s1)?.advancedBy(s1.count),
            end = str.startIndexOf(s2, range: start ..< str.endIndex) {
                return String(str[start ..< end])
        }
        return nil
    }
    
    // 標準ライブラリのみ String.UTF16View で検索
    func find_utf16(str: String, s1: String, s2: String) -> String? {
        let s1 = s1.utf16
        let s2 = s2.utf16
        let str = str.utf16
        if let
            start = str.startIndexOf(s1)?.advancedBy(s1.count),
            end = str.startIndexOf(s2, range: start ..< str.endIndex) {
                return String(str[start ..< end])
        }
        return nil
    }
    
    // NSScanner で検索
    func find_scanner(str: String, s1: String, s2: String) -> String? {
        let scanner = NSScanner(string: str)
        var result: NSString?
        scanner.scanUpToString(s1, intoString: nil)
        scanner.scanString(s1, intoString: nil)
        scanner.scanUpToString(s2, intoString: &result)
        return scanner.atEnd ? nil : result as? String
    }
    
    // NSRegularExpression で検索
    func find_regex(str: String, s1: String, s2:String) -> String? {
        let regex = try! NSRegularExpression(
            pattern: NSRegularExpression.escapedPatternForString(s1)
                + "(.*?)"
                + NSRegularExpression.escapedPatternForString(s2),
            options: []
        )
        if let range = regex.firstMatchInString(str, options: [], range: NSMakeRange(0, str.utf16.count))?.rangeAtIndex(1) {
            return (str as NSString).substringWithRange(range)
        }
        return nil
    }
    
    class StringTestTests: XCTestCase {
    
        private func doTest(fn: (String, String, String) -> String?) {
            let str = "私の好物は<em>焼き魚</em>です。"
            let tag1 = "<em>"
            let tag2 = "</em>"
            XCTAssertEqual(fn(str, tag1, tag2), "焼き魚")
            measureBlock {
                for _ in 0 ..< 10000 {
                    fn(str, tag1, tag2)
                }
            }
        }
    
        func testRangeOfString() { doTest(find_rangeOfString) }
        func testCharacters() { doTest(find_characters) }
        func testUTF16() { doTest(find_utf16) }
        func testScanner() { doTest(find_scanner) }
        func testRegex() { doTest(find_regex) }
    }
    
[/code]

結果 iPhone6 / iOS9.1 / Releaseビルド（複数回実行して同じ傾向 ）

[code]

    testRangeOfString: 0.060 sec
    testCharacters:    0.453 sec
    testUTF16:         0.020 sec
    testScanner:       0.144 sec
    testRegex:         0.498 sec
    
[/code]

当然、元文字列の質にも依りますので、どれかが常に一番速いわけではないはずですが、このパターンではこんな結果でした。  
正規表現版は毎回パターンを作っているので遅いのは当たり前なんですが、意外にも `find_utf16` が最速。`CharacterView` を使うか
`UTF16View`
を使うかでこれだけの違いが出ます。何を使うべきかというのも、やりたいことと元文字列の性質によるのでなんとも言えないですが、今回のバリエーションでは、微妙に機能的な違いがあります。

[code]

    find_rangeOfString("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    find_characters("私の好物は焼き魚です。", s1: "", s2: "") // -> nil
    find_utf16("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    find_scanner("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    find_regex("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    
    find_rangeOfString("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    find_characters("私の好物は焼き魚です。", s1: "", s2: "") // -> nil
    find_utf16("私の好物は焼き魚です。", s1: "", s2: "") // -> nil
    find_scanner("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    find_regex("私の好物は焼き魚です。", s1: "", s2: "") // -> "焼き魚"
    
[/code]

なんでこうなるかというと、CharacterView では 連続する国旗Emojiは 一文字として扱われるからなのですが、詳しくは
<https://stackoverflow.com/q/26862282/3804019>

