Amarronの日記

iOSやMac、Web系の記事を書きます。

Swift 文法

f:id:Amarron:20141231160353j:plain

概要

Swiftの入門的な部分をまとめてみました。
初心者向けに文法の部分を中心に書きました。
また、SwiftObjective-Cより直感的ですっきりしているので、
iOSの開発を始めたい方はSwiftでの開発がオススメです。

目次

  1. 変数宣言(var,let)
  2. 型(Int,String...)
  3. 文字列操作
  4. 配列(Array)
  5. 辞書(Dictionary)
  6. タプル(Tuple)
  7. 繰り返し(for,while)
  8. 条件分岐(if,switch)
  9. 関数(func)
  10. クラスと構造体(class,struct)
  11. オプショナル型(Optional)
  12. おすすめの勉強法

1. 変数宣言(var,let)

  • 変数の宣言には「var」と「let」がある
    • varは、変数
    • letは、定数
// 型を指定して生成
var varInt:Int      // 宣言:var 変数名:型
varInt = 0          // 代入:変数名=値
var varInt2:Int = 0 // 宣言+代入:var 変数名:型=値
// 型推論(型を指定せずに生成)
var varInt3 = 3     // 値からInt型の変数が生成される

let pi = 3.14
// let pi2:Double   // 宣言のみだとエラー(値の代入も宣言時に必要)
// pi = 3.1415926   // 値を変更するとエラー

2. 型(Int,String...)

  • マイナス値がない「UInt」型がある
    • 添字などで使うと安全性が高まる
  • 型指定なしの「Any」型がある
名前 説明
Int 整数型
UInt 符号なし整数型
Float 単精度少数型
Double 倍精度少数型
Bool 論理型
Charecter 文字型
String 文字列型
Any 型指定なし
AnyObject 型指定なし(Classに限定)

3. 文字列操作

  • Stringの文字列は「+」で結合できる
    • var str:String = "Hello"+"World"
  • String内に処理の実行結果を代入する時は「\(処理)」で出来る
    • var str:String = "1+1=\(1+1)"

4. 配列(Array)

// 型を指定
var varArray:[String]
// 型推論(型を指定せずに生成)
var varArray2 = ["var0", "var1", "var2"]
// 空の配列
var varArray3 = [String]()
var varArray4:[String] = Array() 
// 要素の追加
varArray2.append("var3")

5. 辞書(Dictionary)

// 型を指定
var varDict:Dictionary<String, String>
// 型推論(型を指定せずに生成)
var varDict2 = ["key0":"var0", "key1":"var1", "key2":"var2"]
// 空の辞書
var varDict3 =  Dictionary<String, String>()
var varDict4:Dictionary<String, String> = Dictionary()
// 要素の追加
varDict2["key3"] = "var3"
// 値の取得
if let value = varDict2["key3"] {
    print(value)
}
// キーと値の取得例
for (dictName, dictValue) in varDict2 {
    print("キーは\(dictName)で、値は\(dictValue)です。")
}

6. タプル(Tuple)

  • タプルは複数の値をグループとして扱える(「x,y,width,height」や「name,age」とかをグループにしたい時に便利)
  • タプル内の値は、異なる型の値を設定出来る
// 値を設定
let http404Error = (404, "Not Found")
// 値を取得
print("The status code is \(http404Error.0)")

// 他の変数に設定、取得
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 変数名をつけて設定、取得
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")

7. 繰り返し(for,while)

var roopArray = ["var0","var1","var2"]
// for-in ループ
for i in 0..<roopArray.count {
    print(roopArray[i])
}
// 条件文付きforループ
for var i = 0; i < roopArray.count; i++ {
    print(roopArray[i])
}
// 代入forループ
for value in roopArray {
    print("\(value)")
}
// while
var roopIndex = 0
while roopIndex < roopArray.count {
    print(roopArray[roopIndex])
    roopIndex++
}

8. 条件分岐(if,switch)

  • ifの条件式に()は、書かなくてもOK
  • switchは、breakを書かなくてもbreakする
  • switchでも、条件が複数指定できる
  • switchでも、細かい条件も指定できる
var varStr:String = "Hello World"
// if
if varStr != "" {
    print(varStr)
}
// if条件分岐+代入
var varStr2:String?
if let varStr3 = varStr2 {
    print(varStr3) // varStr2がnilじゃない場合、varStr3にvarStr2を代入
} else {
    print("varStr2 is nil")
}

// if条件分岐+代入(三項演算子) その2
var varInt:Int = 1
var varStr3:String = (1 == varInt) ? "1" : ""

// switch
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress": // 複数条件
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"): // 細かい条件
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

9. 関数(func)

  • 戻り値が複数設定出来る
// 関数を呼び出す
// func 関数名(引数: 型) -> 戻り値
func square(num: Int) -> Int {
    return num * num
}
// 関数を使う
print("計算結果は\(square(2))です")
// 引数が複数の場合、引数を「,」区切りで宣言
func sum(num1: Int, num2: Int) -> Int {
    return num1 + num2
}
// 戻りが複数の場合、戻り値を「,」区切りで宣言
func tax(num: Float) -> (five: Float, eight: Float) {
    return (five: num*1.05, eight: num*1.08)
}
var taxPrice = tax(100.0)
// 複数の戻りを取り出す場合、「.」で指定する
print("消費税5%だと、\(taxPrice.five)円です")
print("消費税8%だと、\(taxPrice.eight)円です")

10. クラスと構造体(class,struct)

  • クラスと構造体は、変数や関数等をまとめたプログラム
  • クラスと構造体の違いは、「型」として性質(セマンティクス)が異なる
    • クラス(class):参照型(コピーした時に、コピー元の値を変更するとコピー先も同じものを参照しているので値が変わる)
    • 構造体(struct):値型(コピーしてもそれぞれにインスタンスされる)

クラス(class)

  • 本格的に作る場合に向いている
    • 継承を使える
    • ARCが使える
  • インスタンスのライフサイクルが長いもに向いている
  • 例)UIColor,UIImage等(殆どがクラス)
class RectClass {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rectC = RectClass()
rectC.x = 0;
rectC.width = 100;
print("最大幅は\(rectC.maxX())です");

構造体(struct)

  • 軽く作る場合に向いている
  • プロパティ数が少ないもに向いている
  • 例)CGRext,CGPoint,CGSize,Array等
struct RectStruct {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rect0 = RectStruct()
var rect1 = RectStruct(x:0.0, y:0.0, width:100.0,height:200.0)
print("最大幅は\(rect1.maxX())です");

11. オプショナル型(Optional)

  • オプショナル型
    • 「?」をつける
    • nilになる可能性があることを示している
    • ラップされた状態
  • 非オプショナル型
    • 「!」をつける
    • nilになる可能性がないことを示している
    • アンラップされた状態
  • オプショナル連鎖
    • オプショナル型のメソッドや変数にアクセスする時は、オプショナルでアクセスする必要がある
// NSURLクラスでの例
NSURL.h
var scheme: String? { get }
var host: String? { get }
var path: String? { get }
var query: String? { get }

MyClass
var url = NSURL(string: "http://www.apple.com")!
var host: String? = url.host // ラップ
var host2: String = url.host! // アンラップ
// var host3: String = url.query // クエリーを設定していないので実行時エラーになる(宣言元にラップ状態の明示が必要)
var host3: String? = url.query
// host.hasPrefix("www") // オプショナル連鎖(?が必要)
host?.hasPrefix("www")

12. おすすめの勉強法

  1. Swift - Overview - Apple Developerをざっくり読む
  2. The Swift Programing Languageを読む(Swift言語ブログが日本語で解説されていて分かりやすい)
    • A Swift Tour: Playground fileからSampleCodeをダウンロード(1.The Swift Programing Languageがplaygroundで閲覧出来る)
    • 気になる点をplaygroundで書く
  3. Using Swift with Cocoa and Objective-Cを読む(CocoaObjective-Cswiftの違いが書かれている)
  4. オススメの書籍

文法もわかりやすく、rssアプリを作成出来ます。基礎の入門書としてオススメです。

他に参考になる資料(2015.12追記)

その他

  • IBM Swift Sandbox(swiftがウェブプラザで動かせるサイト。マックを持ってないけど軽く触ってみたい人にオススメ。)

Web系 システム 開発 セキュリティ 備忘録

f:id:Amarron:20141124135656j:plain

概要

ウェブアプリケーションのセキュリティの基礎や入門的な部分をざっくり書き出しました。
ログイン機能を使う場合やインフラよりのセキュリティは書いていません。

  1. 脆弱性の原因と攻撃パターン
  2. 脆弱性一覧
  3. 入力処理の脆弱性
  4. 表示処理の脆弱性
  5. SQL脆弱性
  6. まとめ

1. 脆弱性の原因と攻撃パターン

脆弱性が生まれる理由

  1. バグによるもの
  2. チェック機能不足によるもの
    • ディレクトリ・トラバーサル等

能動的攻撃と受動的攻撃

  • 能動的攻撃:攻撃者がwebサーバーに対して直接攻撃すること(SQLインジェクション等)
  • 受動的攻撃:攻撃者がwebサーバーに対して直接攻撃せず、Webサイトの利用者に罠を仕掛ける
    • 単純な受動的攻撃:ユーザーが怪しいサイト閲覧してウィルスに感染
    • 正規サイトを悪用する受動的攻撃:攻撃者がwebサーバーに対して不正操作をして、ユーザーが正規サイトに訪れるとウィルスに感染
    • サイトをまたがった受動的攻撃:①ユーザーが罠サイトを閲覧②ユーザーが正規サイトに攻撃のリクエスト送信③正規サイトからJSなどの仕掛けを含むレスポンスが返却

2. 脆弱性一覧

  • 脆弱性には出力に起因するものと処理に起因するものがある
  • 出力に起因する脆弱性には「インジェクション」という単語がつくものが多い
    • インジェクションとは、「"」「'」等の終端マークを混入させ処理や出力の構造を変化させる行為
# 処理の流れ
クライアントブラウザ
   ↓ HTTPリクエスト
サーバー(入力値検証→処理→出力)
   ↓ HTTPレスポンス
クライアントブラウザ

機能と脆弱性の対応

処理 出力 対応アプリ 発生する脆弱性の種類 起因する脆弱性
処理 表示/HTML ブラウザ クロスサイト・スクリプティング
HTTPヘッダ・インジェクション
出力
処理 DB/SQL RDB SQLインジェクション 出力
処理 外部コマンド シェル OSコマンド・インジェクション 出力
処理 メール メール メールヘッダ・インジェクション 出力
処理 ファイル ファイル ディレクトリ・トラバーサル 処理
重要な処理 - - クロスサイト・リクエストフォージェリ 処理
認証 - - セッションフィクセーション 処理
許可 - - 認可不備 処理

インジェクション系脆弱性

脆弱性 インターフェース 悪用手口 データの終端
クロスサイト・スクリプティング HTML JS等の注入 < " など
HTTPヘッダ・インジェクション HTTP HTTPレスポンスヘッダの注入 改行
SQLインジェクション SQL SQL命令文注入 ' など
OSコマンド・インジェクション シェルスクリプト コマンドの注入 ; | など
メールヘッダ・インジェクション sendmailコマンド メールヘッダ、本文の注入・改変 改行

3. 入力処理の脆弱性

  • Webアプリケーションの入力には、HTTPリクエストとしてわたらせるパラメータ(GET,POST,cookieなど)がある
  • 入力処理には次の3つの検証・変換が必要
    1. 文字エンコーディングの妥当性検証
    2. 文字エンコーディングの変換(必要な場合のみ)
    3. パラメータ文字列の妥当性検証(アプリケーションの仕様に基づいた検証)
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力| ←ここ
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ

文字エンコーディングの変換

言語 自動変換 スクリプト記述
PHP pjp.iniなど mb_convert_encoding
Perl - Encode::decode
Java setCharacterEncoding Stringクラス
ASP.NET web.config -

4. 表示処理の脆弱性

  • 表示処理が原因で発生する問題には次の2つがある
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ ←ここ

クロスサイト・スクリプティング

  • クロスサイトスクリプティングとは、ウェブページの部分をユーザからの入力をそのままエコーバック(おうむ返し)することによって生成しているアプリケーションのセキュリティ上の不備を利用して、サイト間を横断して悪意のあるスクリプトを注入する攻撃のこと
  • クロスサイト・スクリプティングではユーザーに対して次のような被害がある
    • 攻撃者のスクリプトでクッキー値を盗まれる
    • 攻撃者のスクリプトでWebアプリケーションの機能を悪用される
    • 偽の入力フォームでのフィッシングで個人情報が盗まれる

クロスサイト・スクリプティング対策

  • 必須対策(個別):HTMLの要素内容について[<]や[&]をエスケープする、属性値は「"」で囲い[<]や[&]をエスケープする
  • 必須対策(共通):HTTPレスポンスに文字エンコーディングを明示する
  • 保険的対策:入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効

5. SQL脆弱性

  • SQL脆弱性にはSQLインジェクションがある
  • SQLインジェクションとは、SQLの呼び出しに不備がある場合発生する脆弱性SQLが開発者の意図しない形にに改変され実行されること
  • SQLインジェクション脆弱性がある場合、次のような影響を受ける可能性がある
    • データベース内の全ての情報が盗まれる
    • データベースの内容が書き換えられる
    • 承認を回避される(IDとパスワードを用いずにログインされる)
    • その他、データーバースサーバー上のファイルの読み書き、プログラム実行などが行われる
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力| ←ここ
-----
  ↓ HTTPレスポンス
クライアントブラウザ

脆弱性が生まれる原因

文字列リテラル

  • SQLの標準では、文字列リテラルは「'」で囲む
  • 「'」を使いはみ出した部分で脆弱性が生まれる
# 文字列リテラルの例(BOOKSテーブルから著者がO'Reillyのデータを取得)
SELECT * FROM BOOKS WHERE AUTHOR = 'O'Reilly'
'O'が文字列リテラルで、Reilly'が文字れるリテラルからはみ出した部分
(「SELECT * FROM BOOKS WHERE AUTHOR = 'O';DELETE FROM BOOKS;」みたいな感じで悪用できる)

数値リテラル

  • 文字列リテラル同様、「;」で区切りそのあとに意図しない形のSQLを実行できる
# 数値リテラルの例
SELECT * FROM BOOKS WHERE MONEY <= 20000;DELETE FROM BOOKS
20000が数値リテラル、それ以降が脆弱性を含む意図しない形のSQL

対策

  • プレースホルダSQL分の「?」に値が代入されるやつ)でSQLを組み立てる
  • アプリケーション側でSQLを組み立てる際にリテラルを正しく構成するなどSQL文を変更されないようにする

6. まとめ

  • 入力値のインジェクション対策はかならず行う(「"」「'」「;」等を含んだ入力値でテストする)
  • HTMLの要素内容について[<]や[&]をエスケープする
  • HTTPレスポンスに文字エンコーディングを明示する
  • 入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効
  • おすすめ書籍

セキュリティの事がひと通り書いてあり、サイトを作成する前に考慮すべき考え事がわかりやすく書いてあります。

iOS SNS シェア 実装方法

概要

SNS等のシェア機能の実装方法の説明です。 実装内容はシェアボタン押下時にアクションシートを表示し、シェアしたい内容を選びシェアするような実装内容です。
Webページを表示しシェアしていますが、それ以外でも同じような実装方法でシェア可能です。

シェア先は次の6個!(mixiは登記簿謄本が必要な為今回は対象外としました)

実装結果イメージ(「safari」と「URLをコピー」はおまけ)

f:id:Amarron:20140828232150p:plain

この記事では、「UIApplication sharedApplication」で実装しています。
iOS6移行はUIActivityViewControllerで実装するのが楽だと思うので、iOS6移行は他の記事を参考にする方がお勧めです!

前提条件

TWITTERFACEBOOKiOS6以上)

Xcodeのライブラリーに「Social.framework」を追加する必要があります。
(TARGETS->MyApp->Build Phases->Link Binary With Libraries)

Google+EverNote

Google+EverNoteは、SDKやサービスの登録が必要となります。
EverNoteはサービス登録が必要になるので、少し時間と手間がかかります)

簡単な概要は次の通りです。

Google+

名称 Google+ Platform
URL https://developers.google.com/+/
必要なライブラリ Security.framework,SystemConfiguration.framework
手順 1. APIs Console プロジェクトを作成する
2. SDK をダウンロードして、サンプル アプリを実行する
3. SDK のダウンロードとインストール
4. プログラム修正
参考URL スタートガイド(iOS)
Googleでログイン
インタラクティブな投稿の作成
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。

EverNote

名称 EverNote Developers
URL https://dev.evernote.com/intl/jp/
必要なライブラリ Security.framework
手順 1.サンドボックスのアカウント登録(アプリごとで共通のアカウントを利用する)
2. Evernote API キー取得(アプリごと別々に取得)
3. SDK のダウンロードとインストール
4.プログラム修正・サンドボックス上のEverNoteでテスト
5.サンドボックスからプロダクションに変更依頼(アクティベーション
6.プログラム修正(プロダクションに変更(ホストを「BootstrapServerBaseURLStringSandbox」→「BootstrapServerBaseURLStringUS」))
参考URL スタートガイド
サンドボックス
アクティベーション(プロダクションAPIキーの有効化)
よくある質問
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。
アクティベーションの申請時にはgoogleアカウントは使用できないので個人名・個人用メールアドレスで依頼
アクティベーションの承認待ち時間は約2〜3日

プログラム

流れ

  1. アクションシートの生成
  2. アクションシートの表示(シェアボタン押下時)
  3. アクションシートの選択した内容でシェア

サンプルコード

#import <Social/Social.h>
#import "EvernoteSession.h"
#import "EvernoteUserStore.h"
#import "EvernoteNoteStore.h"
#import "GTLPlusConstants.h"
#import "GPPShare.h"

@implementation MainController
    UIActionSheet *_actionsheet;
    UIWebView *_webView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // 1. アクションシートの生成
    _actionsheet = [[UIActionSheet alloc] initWithTitle:@"共有"
                                               delegate:self
                                      cancelButtonTitle:@"キャンセル"
                                 destructiveButtonTitle:nil
                                      otherButtonTitles:@"LINE", @"Twitter", @"Facebook", @"Google+", @"メール", @"Safari", @"Evernote", @"URLをコピー", nil];
    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.co.jp"]]];

}

#pragma mark - IBAction
// 2. アクションシートの表示(シェアボタン押下時)
- (IBAction)shareAction:(id)sender
{
    if([[UIDevice currentDevice].model isEqualToString:@"iPad"]){
        [_actionsheet showFromBarButtonItem:sender animated:YES];
    } else {
        [_actionsheet showInView:self.view];
    }
}

#pragma mark UIActionSheetDelegate
// 3. アクションシートの選択した内容でシェア
-(void)actionSheet:(UIActionSheet*)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
    NSURL *url = [NSURL URLWithString:[_webView stringByEvaluatingJavaScriptFromString:@"document.URL"]];
    NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; 

    if (0 == buttonIndex) { // LINE
        NSString *contentURL = [self _escapeURL:[url absoluteString]];
        NSString *contentTitleText = [self _escapeURL:title];
        NSURL *lineURL = [NSURL URLWithString:[NSString stringWithFormat: @"http://line.naver.jp/R/msg/text/%@?%@", contentURL, contentTitleText]];
        [[UIApplication sharedApplication] openURL:lineURL];
           
        
    } else if (1 == buttonIndex) { // TWITTER
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];
        
    } else if (2 == buttonIndex) { // FACEBOOK
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];

    } else if (3 == buttonIndex) { // Google+
        id<GPPShareBuilder> shareBuilder = [[GPPShare sharedInstance] shareDialog];
        [shareBuilder setPrefillText:title];
        [shareBuilder setURLToShare:url];
        [shareBuilder open];
        
    } else if (4 == buttonIndex) { // メール
        NSString *conSbjText = [self _escapeURL:title];
        NSString *conBodyText = [self _escapeURL:[url absoluteString]];
        NSString *mail = [NSString stringWithFormat:@"mailto:%@?Subject=%@&body=%@", @"", conSbjText, conBodyText];
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mail]];
        
    } else if (5 == buttonIndex) { // Safari
        [[UIApplication sharedApplication] openURL:url];
        
    } else if (6 == buttonIndex) { // EverNote
        EvernoteSession *session = [EvernoteSession sharedSession];
        [session authenticateWithViewController:self completionHandler:^(NSError *error) {
            if (!error && session.isAuthenticated){
                // We're authenticated!
                EvernoteUserStore *userStore = [EvernoteUserStore userStore];
                [userStore getUserWithSuccess:^(EDAMUser *user) {
                    // success
                    NSLog(@"Authenticated as %@", [user username]);
                    
                    EDAMNote *note = [[EDAMNote alloc] init];
                    
                    note.title = title;
                    
                    NSString *conDes = [_webView stringByEvaluatingJavaScriptFromString:
                                          @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].name=='description') return l[i].content}};a();"];
                    NSString *conImg = [_webView stringByEvaluatingJavaScriptFromString:
                                        @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].getAttribute('property')=='og:image') return l[i].content}};a();"];
                    
                    NSString *enNote = [NSString stringWithFormat:@"<p>%@</p><a href='%@'></a>%@<img src='%@' />",
                                        conDes,
                                        [self _escapeXML:[url absoluteString]],
                                        [self _escapeXML:[url absoluteString]],
                                        conImg];
                    note.content = [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\"><en-note>%@</en-note>", enNote];
                    
                    EvernoteNoteStore *store = [[EvernoteNoteStore alloc] initWithSession:[EvernoteSession sharedSession]];
                    [store createNote:note success:^(EDAMNote *note) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteに保存しました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    } failure:^(NSError *error) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    }];
                } failure:^(NSError *error) {
                    [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];

                } ];
            }
        }];
    } else if (7 == buttonIndex) { // コピー
        [[UIPasteboard generalPasteboard] setValue:[url absoluteString] forPasteboardType:@"public.text"];
        [[[UIAlertView alloc] initWithTitle:@"URLをコピーしました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

#pragma mark - private methods
- (NSString *)_escapeURL:(NSString *) url
{
    return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                         (__bridge CFStringRef)url,
                                                                         NULL,
                                                                         (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                         kCFStringEncodingUTF8 );

}
- (NSString *)_escapeXML:(NSString *) xml
{
    xml = [xml stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"\""withString:@"&quot;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"'"withString:@"&apos;"];
    xml = [xml stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"];
    return xml;
}

エンコードについて

Line、メール、EverNoteは、直接URLをシェア出来ません。
シェアするためには、htmlのエスケープが必要になります。
urlのエスケープ(line,mail)→16進表記(文字参照
xmlのエスケープ(EverNote)→実態参照

文字 実体参照 10進表記(文字参照 16進表記(文字参照
"&" &amp; &#38; &#x26;
""" &quot; &#34; &#x22;
"<" &lt; &#60; &#x3C;
">" &gt; &#62; &#x3E;
" " &nbsp; &#160; &#xA0;

App Store の iOSアプリ を 公開停止 する 方法 

AppStore の アプリ を 公開停止 する 方法 

概要

AppStoreに公開中のiOSアプリを取り下げる方法の説明です。
(申請(公開前のレビュー依頼)を取り下げたい場合、Developer Rejectで対応してください)
アプリの公開地域を全て解除することでAppStoreで公開されなくなります。

手順

  1. iTunes Connectへログイン
  2. Manage Your Appsを選択
  3. 公開停止したいアプリを選択
  4. 「Rights and Pricing」を押下
  5. Appの使用可能日・価格帯・地域の設定画面が表示されるので公開地域を解除します
    1. 「特定の地域」リンクを押下(公開中の地域が表示される)
    2. 「すべての選択を解除」ボタンを押下(全ての地域のチェックが外れる)
    3. 「Save」ボタン押下
  6. Statusが「Ready for Sale」から「Developer Removed From Sale」となり、App Infomationに「Delete App」ボタンが表示されます。(「Delete App」を選択しなくてもAppStoreからの公開は停止されました。)

f:id:Amarron:20140731170541p:plain

f:id:Amarron:20140731170550p:plain

f:id:Amarron:20140731170557p:plain

その他

  • 私の場合、公開停止の手続きをしてから30分くらいでAppStoreから公開されなくなりました。
  • AppStoreに一度も公開されなかったアプリは、公開地域を全解除しても「Delete App」が表示されません。(間違って作ってしまった場合、アプリを消したいけど消せなくて結構辛いです。。。)

Mac で よく使う キーボードショートカット まとめ

f:id:Amarron:20110722135432j:plain

MacOS X)で個人的によく使うキーボードショートカット(コマンド)を用途別にまとめてみました。

参考URL: OS X のキーボードショートカット

ターミナル

ターミナルでの操作

ショートカットキー 説明 詳細
control + A 行頭に移動 現在の行/段落の先頭に移動
control + E 行末に移動 現在の行/段落の末尾に移動
option + 右矢印キー 単語の末尾に移動 テキスト挿入ポイントを次の単語の末尾に移動
option + 左矢印キー 単語の先頭に移動 テキスト挿入ポイントを前の単語の先頭に移動
control + W 単語先頭まで削除 テキスト挿入ポイントから前の単語の先頭まで削除
control + D 1文字削除、ログアウト カーソル文字を1文字削除、ログアウト
control + K 右側全て削除 カーソルの右側の文字から行/段落の末尾までを削除
control + U 左側全て削除 カーソルの左側の文字から行/段落の先頭までを削除
control + L 再表示 画面をクリアしてカレント行を再表示する
tab 予測補完 コマンドやディレクトリ名を補完する
control + C 処理中断 実行中の処理を中断する
control + S 出力停止 画面への出力を中断する
control + Q 出力再開 画面への出力を再開する
control + Z 処理一時停止 実行中の処理を一時停止する

テキスト編集

テキストエディタで文章編集時の操作(viやemacsではない)

ショートカットキー 説明 詳細
command + A 全選択 前面に表示されている Finder ウインドウ (ウインドウが開かれていない場合はデスクトップ) のすべての項目を選択
command + C コピー 選択した項目/テキストをクリップボードにコピー
command + V ペースト クリップボードの内容をペースト
command + X 切り取り 選択した項目/テキストをクリップボードにコピーしテキストは削除
command + Z 動作を戻す 取り消す/やり直す
command + Y 動作を進める command + Zと反対の動作
command + S 保存 ドキュメントを保存
command + F 検索 検索ウインドウを開く
command + 上矢印キー 先頭に移動 テキスト挿入ポイントを現在のドキュメントの先頭に移動
command + 下矢印キー 末尾に移動 テキスト挿入ポイントを現在のドキュメントの末尾に移動
command + 左矢印キー 行頭に移動 テキスト挿入ポイントを現在のドキュメントの行頭に移動
command + 右矢印キー 行末に移動 テキスト挿入ポイントを現在のドキュメントの行末に移動
option + エリア選択 範囲指定エリア選択 optionを押しながらトラックパッドで範囲指定出来る(行間にまたがって空白を削除したい時に便利)

アプリケーション

常によく使う操作

ショートカットキー 説明 詳細
command + tab アプリ切替え 開いているアプリケーションのリスト (最近使った順番に表示されている) 内を順方向に移動
command + N 新規立上げ 最前面のアプリケーションで新規作成
command + W 閉じる 最前面のウインドウを閉じる
command + Q 終了 最前面のアプリケーションを終了
command + D 保存しないで閉じる 「開く」ダイアログと「保存」ダイアログで「デスクトップ」フォルダを選択・または、Mac OS X v10.6.8 以前で「保存しない」ボタンを含むダイアログから「保存しない」を選択
電源ボタン スリープ 電源が入ったら、タップしてスリープ解除/スリープ状態

Alfred

f:id:Amarron:20140705231226j:plain

Alfredとは、Macで利用できるランチャーアプリです。(App Storeでダウンロードできます)
もっとキーボードだけで操作したい人向けです。
個人的には、標準のショートカットにシャットダウンがないのでAlfredで「S」で検索しシャットダウンしています。(電源ボタン長押しで強制終了は出来るのですが、強制終了は気が引けるので)
その他にもアプリケーションを起動する時に良く利用します。一度使うと手放せなくなると思います。

iPhoneアプリ を iPadアプリ に 対応させる 方法

iPhoeアプリをiPadアプリとしてもAppStoreに配布する方法について書いてあります。 iPad対応するには、Xcodeで「ユニバーサルに変更」するのと「iPad用画像の設定」が必要となります。

環境

Xcode 5.1.1

流れ

  1. ユニバーサル化
  2. プログラム修正(修正が必要な場合のみ)
  3. 申請手続き

1. ユニバーサル化

DeviceをiPhoneからユニバーサルに変更する事でiPadに対応されます。

iPadのTAGETSを作ることで、TAGETS別にプログラムを変更出来るようになります。(「2. プログラム修正」で利用する場合があります)
ユニバーサルにすることによって、既存のiPhone用のxibがそのままiPadでも使えます。
また、iPadでアプリ起動時にiPhoneアプリとして起動するのではなく、iPad用アプリとして起動します。

  1. 「TARGETS->MyApp」を右クリックで「Depulicate」を選択
  2. 表示されたダイアログで「Duplicate and Transition to iPad」を選択(TARGETSにMyApp-iPadが追加されます)
  3. 1で選択した「「TARGETS->MyApp」の「General->Deployment Info->Devices」を「iPhone」から「Universal」に変更(この対応だけで(Depulicateで追加しなくても)iPadに対応します
  4. iPadで確認(実機の場合、右下に倍率を変更するボタンが表示されなければiPad用で開かれています)

f:id:Amarron:20140615124252p:plain

2. プログラム修正(修正が必要な場合のみ)

iPad用のアプリアイコン画像や起動時画像の追加、iPad用に細かいプログラム修正を行います。

  1. アプリアイコンの追加
    • 「TARGETS->MyApp->General->App Icons」でアプリアイコン画像(76x76,152x152)を設定
    • 「TARGETS->MyApp-iPad->General->App Icons」でアプリアイコン画像(72x72,144x144)を設定
  2. 起動画像の追加
    • 「TARGETS->MyApp->General->Launch Images」で起動画像画像(768X1024,1536X2048,1024X768,2048X1536)を設定
  3. プログラム修正(iPadで確認した時にレイアウト部分が崩れている場合は修正しましょう)
    • iOSアプリケーション プログラミングガイドによると「iPhone用とiPad用で、別々にビューコントローラのクラスを定義する」ことをお勧めしています。(別々に作った場合、Target Membershipにチェックを外すことでiPhoneiPadを切り替えることが出来ます)
    • 私は、新たにクラスを定義する程複雑なアプリではなかったので、同じクラスをif文で修正しました。(if([[UIDevice currentDevice].model isEqualToString:@"iPad"]){// iPadの調整})

f:id:Amarron:20140615124310p:plain

3. 申請手続き

  1. iTunes Connetで「Add Version」でバージョンアップ用アプリの追加手続きを行います。(iPhoneアプリと同じ手順)
  2. Xcodeでアーカイブ化しアプリをアップします。(iPhoneアプリと同じ手順)
  3. iTunes Connetでステータスが「Upload Received」に変更されると、iPad用のスクリーンショットを追加出来るようになるのでiPad用の画像(1536x2048 or 1536x2008)を設定しましょう。(iPadアプリで新しい手順)

※.1と2についてはわからない場合、App Store に iOSアプリ を 公開する方法の「3. 配布したいアプリの設定」を参照してください。

参考URL

iOS の アプリ内課金(In-App Purchase) 組込方法

概要

iPhone」や「iPad」でのアプリ内課金(In-App Purchase)の実装方法について書きました。
iOSアプリ内で特定の機能を有料販売するための準備・開発・テストの説明中心です。
全体的な作業時間としては、1日は覚悟したほうが良さそうです。
(課金のタイプによってはサーバー側の開発がないから、APNsよりは少し楽かも??)

開発環境

OS : OS X 10.9.2
Xcode : 5.1.1

前提条件

プロダクト(次の2つ)が作成されていること。

  • iOS Developer Centerでアプリの登録(Identifiersの登録時に「In-App Purchase」にチェックする(デフォでチェック入ってる))
  • iTunes Connectでアプリを作成(ステータスをPrepare for Uploadまで進める)

作成方法はこちら(アプリの登録(1.証明書関連の作成)、アプリを作成(3. 配布したいアプリの設定))

アプリ内課金の種類

全部で5種類。
消費型はゲームの課金でよく使われます。購読型は電子書籍でよく使われます。
今回は、「非消耗型(Non-consumable)プロダクト」で作成します。

名称 説明 備考
消耗型(Consumable)プロダクト アプリケーションの実行に伴って消費されていく項目です。Voice over IPアプリケーションで通信できる残り分数や、音声の転送など一度限りのサービスが例として挙げられます。 消費アイテム
非消耗型(Non-consumable)プロダクト ユーザのすべてのデバイス上で無制限に使用できる項目です。ユーザのすべてのデバイスで使用可能になります。例としては、書籍やゲームレベルなどのコンテンツ、およびアプリケーションの追加機能などがあります。 無制限アイテム
自動更新購読(Auto-renewable subscriptions) エピソードで構成されるコンテンツです。消耗型プロダクトと同様に、自動更新購読は、ユーザのすべてのデバイスで無制限に利用可能になります。非消耗型プロダクトと異なるのは、自動更新購読には期限があるという点です。新しいコンテンツは定期的に信され、ユーザは購読が有効な期間中、発行されたコンテンツに対してアクセスできます。自動更新購読の期限が近づいてくると、ユーザに代わってシステムにより購読が自動的に更新されます。 期間で自動課金
非更新購読(Non-renewable subscriptions) エピソードで構成されるコンテンツを含まない購読です。たとえば、歴史的な写真のデータベースに対するアクセス権や、フライトマップのコレクションなどがあります。ユーザのすべてのデバイスで購読を使用可能にし、ユーザの購入を復元するは、アプリケーション側で対応することになります。このプロダクトタイプは、ユーザのアカウントが既にサーバ上に存在し、このアカウントを使用してコンテンツの復元時にユーザを識別できる場合によく使用されます。購読の期限と期間もアプリケーション(またはサーバ)で実装し、実行することになります。 期間で課金(開発者が期間や期限をサーバーで管理)
無料購読(Free subscriptions) Newsstandに無料購読のコンテンツを置くための手段です。サインアップしたユーザは、Apple IDに関連付けられたどのデバイスからでも購読できます。期限切れになることはありません。また、購読にはNewsstand対応アプリケーションが必要です。 無料、Newsstand対応アプリケーション

流れ

  1. 準備
    1-1. iTunes ConnectでManage In-App Purchasesの追加
    1-2. StoreKitフレームワーク追加
    1-3. In-App PurchaseをON
  2. 開発
    2-1. チェック処理
    2-2. 購入開始処理
    2-3. トランザクション処理・リストア処理
    2-4. 購入終了処理
  3. テスト

1. 準備

iTunes Connectの設定やプロジェクトにフレームワークや設定の変更を行います。

1-1. iTunes ConnectでManage In-App Purchasesの追加

iTunes Connectに接続し対象のアプリを開き、アプリ内課金のアイテム情報(Manage In-App Purchases)を登録します。(登録内容は「2. 開発」で利用します。)
「Type」は、今回は「Non-consumable」を選択します。

f:id:Amarron:20140523212449p:plain

「Reference Name」は、アイテムの表示名を設定します。(Appleから毎月送信される売上レポートで表示名)
「Product ID」は、一意のグローバル識別子でドメイン名のスタイルを逆にして使用することを推奨しています(例: com.companyname.application.productid)。
「Screenshot」は、購入確認画面のスクリーンショットをアップロードします。(3. テスト実施時にスクリーンショットをキャプチャしアップロードしましょう。)

f:id:Amarron:20140523212507p:plain

1-2. StoreKitフレームワーク追加

ライブラリに「StoreKit.framework」を追加します。
(TAGETS > Build Phases > Link Binary With Libraries)

1-3. In-App PurchaseをON

CapabilitiesのIn-App PurchaseをOFFからONに変更します。 (PROJECT > Capabilities > In-App Purchase)

f:id:Amarron:20140523212521p:plain

2. 開発

アプリ内課金のプログラム。
参考URL:失敗しない iOS In-App Purchase プログラミング
サンプルコード:SampleInAppPurchaseswift

2-1. チェック処理

課金イベントにアプリ内課金が使えるかチェックをします。

MyClass.h

 #import <StoreKit/StoreKit.h>

// delegateにSKProductsRequestDelegateとSKPaymentTransactionObserverを追加
@interface MyClass : UIViewController <SKProductsRequestDelegate, 
                                            SKPaymentTransactionObserver> {
}

MyClass.m

// メソッド名は適当に
- (BOOL)_checkInAppPurchase
{
    if (![SKPaymentQueue canMakePayments]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー"
                                                        message:@"アプリ内課金が制限されています。"
                                                       delegate:nil
                                              cancelButtonTitle:nil
                                              otherButtonTitles:@"OK", nil];
        [alert show];
        return NO;
    }
    return YES;
}

2-2. 購入開始処理

アイテム情報の取得と購入開始の処理を行います。

MyClass.m

// メソッド名は適当に(チェック処理の結果がYESだったらこの処理を呼ぶ)
- (void)_startInAppPurchase
{
    // com.companyname.application.productidは、「1-1. iTunes ConnectでManage In-App Purchasesの追加」で作成したProduct IDを設定します。
    NSSet *set = [NSSet setWithObjects:@"com.companyname.application.productid", nil];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    productsRequest.delegate = self;
    [productsRequest start];
}

 #pragma mark SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
  // 無効なアイテムがないかチェック
  if ([response.invalidProductIdentifiers count] > 0) {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー"
                                                        message:@"アイテムIDが不正です。"
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil, nil];
    [alert show];
    return;
  }
  // 購入処理開始(「iTunes Storeにサインイン」ポップアップが表示)
  [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
  for (SKProduct *product in response.products) {
    SKPayment *payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
  }
}

2-3. トランザクション処理・リストア処理

App Storeトランザクションを処理するのを待機し、購入が成功した場合アイテム購入の処理を行います。

MyClass.m

// トランザクション処理
#pragma mark SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                // NSLog(@"購入処理中");
                // TODO: インジケータなど回して頑張ってる感を出す。
                break;
            case SKPaymentTransactionStatePurchased:
                // NSLog(@"購入成功");
                // TODO: アイテム購入した処理(アップグレード版の機能制限解除処理等)
                // TODO: 購入の持続的な記録
                [queue finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                // NSLog(@"購入失敗: %@, %@", transaction.transactionIdentifier, transaction.error);
                // TODO: 失敗のアラート表示等
                break;
            case SKPaymentTransactionStateRestored:
                // リストア処理
                // NSLog(@"以前に購入した機能を復元");
                [queue finishTransaction:transaction];
                // TODO: アイテム購入した処理(アップグレード版の機能制限解除処理等)
                break;
            default:
                [queue finishTransaction:transaction];
                break;
        }
    }
}

// リストア処理結果
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    // NSLog(@"リストア失敗:%@", error);
    // TODO: 失敗のアラート表示等
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    // NSLog(@"全てのリストア完了");
    // TODO: 完了のアラート表示等
}

購入の持続的な記録には次の方法があります。(消費型プロダクトは維持しません。)

  • Appレシートを使用した持続(iOS 7以降の非消耗型プロダクトと自動更新購読で推奨)
  • User DefaultsまたはiCloudを使用した値の持続
// User Defaultsを使用した値の持続の例)
MyClass.m

// 購入の持続的な記録
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];

// 購入アイテム使用時
if (rocketCarEnabled) {
 // ロケットカーを使用
} else {
 // 普通の自動車を使用
}

非消耗型プロダクトはAppレシートが推奨ですが、余力がなかったので記述は割愛します。

2-4. 購入終了処理

トランザクションオブザーバの削除を行います。(トランザクションが終了すると呼び出される)

MyClass.m
#pragma mark SKPaymentQueue
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

3. テスト

テストユーザーの作成と実機で確認します。
参考URL:iPad上でアプリ内課金 (In-App Purchase) の実機テストをする方法メモ

  1. iTunes Connectでテストユーザを作成します。(10 Minute Mailを使うと10分だけ無料でメアドが貰えるので便利かも)
  2. 実機の端末App IDをサインアウトし、アプリをインストールします。
  3. 追加した課金のシステムをテストします。テスト実施(課金イベント)時にサインインを求められるので、テストユーザーでサインインします。
  4. 課金成功のメッセージが表示されれば、無事課金完了です。

テスト実施時の注意事項
参考URL: iOSのアプリ内課金(In App Purchase)での注意点

  • テスト用のiTunesアカウントで事前にサインインしない
  • Appleサーバーに対してレシートの有効性を確認する場合には本番用URL -> サンドボックスURLの順で検証する

以上でアプリ内課金(In-App Purchase)の組込作業は終わりです。
お疲れ様でした!