2015年5月24日 星期日

NSDate

Apple官方文件說明NSDate:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/index.html


// 當地日期時間,沒有秒。ex : "May 25, 2015, 11:30 AM"

let date = NSDate() 

// 格式化
var formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"

let defaultTimeZonStr = formatter.stringFromDate(date) // 同樣的日期時間,但是有秒。ex : "2015-05-25 11:34:21 +0800"

formatter.timeZone = NSTimeZone(abbreviation: "UTC")
let utcTimeZoneStr = formatter.stringFromDate(date) // 同樣的日期時間,但是在UTC。ex : "2015-05-25 03:34:21 +0000"

// 設定日期
var endDate = NSDate().dateByAddingTimeInterval(30 * 60) // 30 minutes from current time

// 日期比較
var dateComparisionResult:NSComparisonResult = NSDate().compare(endDate)
if dateComparisionResult == NSComparisonResult.OrderedAscending
{
    println("當前日期時間比 end date .") // 這個範例中,這個結果會被打印出來
}
else if dateComparisionResult == NSComparisonResult.OrderedDescending
{
    println("當前日期時間比 end date .")
}
else if dateComparisionResult == NSComparisonResult.OrderedSame
{
    println("當前日期時間與 end date 相同.")
}

// iOS 8.0+ NSCalendar 方法
var cal = NSCalendar.currentCalendar()

cal.startOfDayForDate(endDate) // 忽略時間元素,ex : "May 25, 2015, 12:00 AM"
cal.isDateInToday(endDate) // 指定日期是否為今天。其他的還有 
cal.isDateInTomorrow(endDate)
cal.isDate(NSDate(), inSameDayAsDate: endDate) // 比對兩個日期是否相同

// 字串轉為日期
var dataString = "April 1, 2015" as String
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
let dateValue = dateFormatter.dateFromString(dataString) as NSDate!

println(dateValue) // ex : "Apr 1, 2015, 12:00 AM"

格式化:


























類似“/Date(1427909016000-0800)”格式的timestamp,要轉成NSDate:

func timestampToNSDate(jsonDate: String) -> NSDate? {
    let prefix = "/Date("
    let suffix = ")/"
    let scanner = NSScanner(string: jsonDate)
    
    // Check prefix:
    if scanner.scanString(prefix, intoString: nil) {
        
        // Read milliseconds part:
        var milliseconds : Int64 = 0
        if scanner.scanLongLong(&milliseconds) {
            // Milliseconds to seconds:
            var timeStamp = NSTimeInterval(milliseconds)/1000.0
            
            // Read optional timezone part:
            var timeZoneOffset : Int = 0
            if scanner.scanInteger(&timeZoneOffset) {
                let hours = timeZoneOffset / 100
                let minutes = timeZoneOffset % 100
                // Adjust timestamp according to timezone:
                timeStamp += NSTimeInterval(3600 * hours + 60 * minutes)
            }
            
            // Check suffix:
            if scanner.scanString(suffix, intoString: nil) {
                // Success! Create NSDate and return.
                return NSDate(timeIntervalSince1970: timeStamp)
            }
        }
    }
    return nil
}
timestampToNSDate("/Date(1427909016000-0800)/")


2015年1月29日 星期四

閉包 Closures - 高階函式 Higher-order functions

func connect(str1 : String, str2 : String) -> String {
    return str1 + " " + str2
}
connect("hello","world") // "hello world"

func say(name : String, function : (String, String) -> String) -> String{
    let greet = "你好"
    var str = function(name, greet)
    return str
}

// 1. 函數被當成變數傳入另一個函數
var result = say("Ian",connect)
println(result) // "Ian 你好"

// 2. 閉包運算式(Closure Expressions
// connect函數的名稱用函數型態取代,in關鍵字之後為程式碼內容。
result = say("Peter", { (str1:String, str2:String) -> String in
    return str1 + " " + str2
})
println(result) // "Peter 你好"

// 3. 根據上下文推斷型態(Inferring Type From Context
result = say("Alex", { str1, str2 in
    return str1 + " " + str2
})
println(result)// "Alex 你好"

// 4. 閉包單行表示式隱含返回(Implicit Return From Single-Expression Clossures
// 拿掉return關鍵字
result = say("Jay", { str1, str2 in str1 + " " + str2 })
println(result) // "Jay 你好"

// 5. 參數名稱縮寫(Shorthand Argument Names
// 直接通過$0,$1,$2...來順序引用閉包的參數
result = say("Alice", { $0 + " " + $1 })
println(result) // "Alice 你好"

// 6. 運算符函數(Operator Functions
// 簡單地傳遞一個加號,Swift可以自動推斷出我們想使用加號的字串函數操作。
result = say("Bob", + )
println(result) // "Bob 你好"

// 尾隨閉包(Trailing Closures
// 把最後一個參數(閉包函數)搬出參數列括弧,整個大括弧搬出小括弧之外。當閉包內容冗長的時候,適合這樣表達。
result = say("Tim"){
    (str1 : String, str2 : String) -> String in return str1 + str2
}
println(result) // "Tim 你好"
======================================

使用 Javascript 的範例,改寫成 Swift 版
http://ianworkshop.blogspot.tw/2014/03/blog-post.html

高階函示 : 
1. 把其他函式當作引數 (callback function / 回呼函式, 即呼叫時傳入她, 然後她再呼叫回來)
2. 會回傳函式

  • 使用標準程式庫原本的方法


// 將陣列中的數值由小到大重新排列
var nums = [1,3,5,7,9,8,6,4,2]

// 2. 閉包運算式
nums.sort({ (n1:Int, n2:Int) -> Bool in
    n1 < n2
})
println(nums)

// 3. 推斷型態
nums.sort({ n1, n2 in
    return n1 < n2
})

// 4. 隱含返回 (拿掉return關鍵字)
nums.sort({ n1, n2 in n1 < n2 })

// 5. 參數名稱縮寫
nums.sort({ $0 < $1 })

// 6. 運算符函數
nums.sort( < )

// 7. 尾隨閉包
nums.sort{ (n1:Int, n2:Int) -> Bool in
    n1 < n2
}

// 尾隨閉包 + 參數名稱縮寫

nums.sort{ $0 < $1 }

======
// 將陣列中的字串都轉為大寫
var names = ["Ian","Peter","Kevin"]
// 尾隨閉包 + 參數名稱縮寫

names = names.map { $0.uppercaseString }
println(names) // "[IAN, PETER, KEVIN]"



  • 自訂高階函式
// 依照條件建立字串
func builderString(n:Int, callback:(counting:Int) -> String ) -> String {
    var result = "";
    for i in 0...n {
        result = result + callback(counting: i)
    }
    return result
}

// 建立 1~9
var digits_1 = builderString(8, { (counting) -> String in
    return "\(counting+1)"
})

var digits_2 = builderString(8) { "\($0+1)"}

2015年1月21日 星期三

Status Bar

Xcode 6.1.1

這一個是 Status Bar


隱藏:

在專案下的 General > Deployment Info 有一個選項 Hide status bar,打勾後啟動專案發現並沒有消失,所以這個功能沒有作用。

到 Navigator 下找到在 Supporting Files 資料夾裡的 Info.plist,click 右鍵 > Open As > Source Code,在 <dict> 裡面加入:
    <key>UIViewControllerBasedStatusBarAppearance</key>

    <false/>
再次執行專案就看不到 Status Bar 了,對 Info.plist 點擊右鍵 > Open As > Property List,可以看到多了一個 key-value pair:



也可以直接在 Property List 中加入

改變顏色:

在狀態列是開啟的情況時,如果在白色背景下將某個 View 的 Status Bar 設為亮色系,也有隱藏效果
    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return .LightContent
    }
如果將背景改為黑色,則顯示如圖所示:









2015年1月19日 星期一

AJAX 與 [Authorize]


ASP.NET MVC 在 Controller 或 Action 上加上 [Authorize] 就可以驗證是否已經登入,如果沒有登入就會被帶往登入頁面

當使用ajax方式則會得到狀態為200的頁面(指定的頁面),因此需要改寫可以回傳一個Json :

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new JsonResult
            {
                Data = new 
                { 
                    // put whatever data you want which will be sent
                    // to the client
                    message = "sorry, but you were logged out" 
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

$.get('@Url.Action("SomeAction")', function (result) {
    if (result.message) {
        alert(result.message);
    } else {
        // do whatever you were doing before with the results
    }
});
如果是用 AngularJs,IsAjaxRequest 會一直判斷是 false,因為 AngularJs 的 ajax 呼叫沒有包含 X-Requested-With 表頭,而 ASP.NET MVC 是用這個表來判斷是否為一個 ajax 呼叫,所以必須改為:

var productsApp = angular.module('productsApp', []);
productsApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'
}]);





2014年11月5日 星期三

自定AuthenticationFilter實現Basic認證

HTTP基本認證
當沒有訪問需要認證(還沒有認證通過)的網站時,瀏覽器自動跳出登入對話框



自定一個 Filter:
public class MyAutheticationAttribute : FilterAttributeIAuthenticationFilter
    {
        /* basic 驗證的 http header
         *     401 Unauthorizted
         *         WWW-Authenticate: Basic realm="Secure Area"
         *     請求登入
         *         Authorization: Basic {base64編碼過的登錄信息}
         */
        public const string AuthoriztionHeaderName = "Authorization";
        public const string WwwAuthenticationHeaderName = "WWW-Authenticate";
        public const string BasicAuthenticationScheme = "Basic";
        private static Dictionary<stringstring> userAccounters;
 
        static MyAutheticationAttribute()
        {
            // 為了簡單測試起見,將一些帳號密碼存放到一個靜態欄位中
            userAccounters = new Dictionary<stringstring>(StringComparer.OrdinalIgnoreCase);
            userAccounters.Add("Ian""1234");
            userAccounters.Add("Peter""1111");
            userAccounters.Add("Jay""2222");
        }
 
        // 具體的認證實現
        public void OnAuthentication(AuthenticationContext filterContext)
        {
            IPrincipal user;
            if (this.IsAuthenticated(filterContext, out user))
            {
                filterContext.Principal = user;
            }
            else
            {
                this.ProcessUnauthenticatedRequest(filterContext);
            }
        }
 
        //
        protected virtual AuthenticationHeaderValue GetAuthenticationHeaderValue(AuthenticationContext filterContext)
        {
            // Authorization 內容
            string rawValue = filterContext.RequestContext.HttpContext.Request.Headers[AuthoriztionHeaderName];
            if (string.IsNullOrEmpty(rawValue))
            {
                return null;
            }
            string[] split = rawValue.Split(' ');
            if (split.Length != 2)
            {
                return null;
            }
            return new AuthenticationHeaderValue(split[0], split[1]);
        }
 
        // 判斷是否通過驗證
        protected virtual bool IsAuthenticated(AuthenticationContext filterContext, out IPrincipal user)
        {
            user = filterContext.Principal;
            if (null != user && user.Identity.IsAuthenticated)
            {
                return true;
            }
            AuthenticationHeaderValue token = this.GetAuthenticationHeaderValue(filterContext);
            if (null != token && token.Scheme == BasicAuthenticationScheme) // "Basic"
            {
                string credential = Encoding.Default.GetString(Convert.FromBase64String(token.Parameter));
                string[] split = credential.Split(':'); // {UserNamr}:{Password}
                if (split.Length == 2)
                {
                    string userName = split[0];
                    string password;
                    if (userAccounters.TryGetValue(userName, out password))
                    {
                        if (password == split[1])
                        {
                            // 認證成功的情況下得到代表請求用戶的 Principal 對象
                            GenericIdentity identiry = new GenericIdentity(userName);
                            user = new GenericPrincipal(identiry, new string[0]);
                            return true;
                        }
                    }
                }
            }
 
            return false;
        }
 
        // 沒有通過請求
        protected virtual void ProcessUnauthenticatedRequest(AuthenticationContext filterContext)
        {
            string parameter = string.Format("realm=\"{0}\"", filterContext.RequestContext.HttpContext.Request.Url.DnsSafeHost);
            AuthenticationHeaderValue challenge = new AuthenticationHeaderValue(BasicAuthenticationScheme, parameter);
            filterContext.HttpContext.Response.Headers[WwwAuthenticationHeaderName] = challenge.ToString();
            filterContext.Result = new HttpUnauthorizedResult();
        }
 
        public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
        {
            // throw new NotImplementedException();
        }
    }

應用在Controller:
    [MyAuthetication]
public class HomeController : Controller
{
    public void Index()
    {
        Response.Write(string.Format("Controller.User: {0}<br/>"this.User.Identity.Name));
        Response.Write(string.Format("HttpContext.User: {0}<br/>"this.ControllerContext.HttpContext.User.Identity.Name));
        Response.Write(string.Format("Thread.CurrentPrincipal.Identity.Name: {0}<br/>"Thread.CurrentPrincipal.Identity.Name));
    }
}



2014年11月4日 星期二

非同步Action

非同步Action方法有兩種定義方式
1:XxxAsync/XxxCompleted
public class HomeController : AsyncController
{
    // GET : /Home/Article (在繼承自AsyncController裡的Controller才能使用Article當Action名稱)
    // 非同步操作
    public void ArticleAsync()
    {
        // 開始非同步的時候,先使用 AsyncManager.OutstandingOperations 取得一個 OperationCounter 對象,
        // 然後調用 Increment() 方法向系統發一個非同步操作開始的通知,OperationCounter 會 +1
        // 結束的時候調用 Decrement(),OperationCounter 會 -1,在發法結束時,當OperationCounter 為 0 才會自動調用 ArticleCompleted() 方法

        AsyncManager.OutstandingOperations.Increment();
        string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\Project_Readme.html"));
        StreamReader reader = new StreamReader(path);
        reader.ReadToEndAsync().ContinueWith(Task =>
        {
            AsyncManager.Parameters["content"= Task.Result; // 保存要給 ArticleCompleted() 方法的參數,必須與參數同名("content")才能自動匹配
            AsyncManager.OutstandingOperations.Decrement(); // 自動調用 ArticleCompleted() 方法
            reader.Close();
        });
    }
 
    // 非同步最終請求的響應,ArticleAsync 方法的回調
    // content 參數來自 AsyncManager.Parameters["content"]
    public ActionResult ArticleCompleted(string content)
    {
        return Content(content);
    }
}
以 XxxAsync/XxxCompleted 方式定義非同步 Action 方法,表示說必需得為一個 Action 定義兩個方法(實際上可以通過一個方法完成 Action 非同步的定義,那就是讓 Action 方法返回一個代表非同步操作的 Task 對象)。

這方式只能出現在繼承自 AsyncController 的 Controller 中,但是 Task 方式沒有限制(保留 AsyncController 這個抽象類主要是為了實現對 ASP.NET MVC 3 的向後兼容)。

2:Task
public Task<ActionResult> Article()
{
    string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\Project_Readme.html"));
    StreamReader reader = new StreamReader(path);
    return reader.ReadToEndAsync().ContinueWith<ActionResult>(task =>
    {
        reader.Close();
        return Content(task.Result);
    });
}

由於 Action 返回一個 Task<ActionResult> 對象,所以可以利用 async/await 關鍵字 (C# 5.0) 直接將其標示為非同步


public async Task<ActionResult> Article()
{
    string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\Project_Readme.html"));
    using (StreamReader reader = new StreamReader(path))
    {
        return Content(await reader.ReadToEndAsync());
    }
}



2014年11月2日 星期日

IController、ControllerBase 和 Controller

當在 ASP.NET MVC 專案建立一個 Controller 時,Visual Studio 會產生一個以 "Controller" 為結尾並繼承 Controller 抽象類別的新類別,例如:
HomeController : Controller

以下為繼承關係
Controller 抽象類別 --> ControllerBase 抽象類別 --> IController 介面

IController
唯一的目的在 Controller 接收請求時,Execute() 方法會執行一些code
// 摘要: 
//     定義控制器所需的方法。
public interface IController
{
    // 摘要: 
    //     執行指定的要求內容。
    //
    // 參數: 
    //   requestContext:
    //     要求內容。
    void Execute(RequestContext requestContext);
}

Execute() 方法在 MvcHandler 的 ProcessRequest() 方法中被調用;Execute() 方法接收 RequestContext 類別參數,RequestContext 類別封裝了 RouteData(在UrlRoutingModule中解析Http產生的路由數據)與 HttpContext 用來表示當前請求的上下文。


ControllerBase
定義了 Controller 使用到的部分公共屬性,比如:用來保存臨時數據的 TempData,用來返回到 View 中的 Model 數據對象 ViewBag、ViewData;並且初始化了ControllerContext 對象,用來作為後續Controller使用的數據容器和操作上下文


public abstract class ControllerBase : IController
{
    //省略其他成員
 
    protected virtual void Execute(RequestContext requestContext)
    {
        if (requestContext == null)
        {
            throw new ArgumentNullException("requestContext");
        }
        if (requestContext.HttpContext == null)
        {
            throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
        }
 
        VerifyExecuteCalledOnce();
        Initialize(requestContext);
 
        using (ScopeStorage.CreateTransientScope())
        {
            //執行ExecuteCore方法
            ExecuteCore();
        }
    }
    // 該方法在衍生的Controller類中實現
    protected abstract void ExecuteCore();
 
    protected virtual void Initialize(RequestContext requestContext)
    {
        ControllerContext = new ControllerContext(requestContext, this);
    }
    internal void VerifyExecuteCalledOnce()
    {
        if (!_executeWasCalledGate.TryEnter())
        {
            string message = String.Format(CultureInfo.CurrentCulture,
              MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
            throw new InvalidOperationException(message);
        }
    }
    void IController.Execute(RequestContext requestContext)
    {
        Execute(requestContext);
    }
}

ControllerBase 實作了 Execute() 方法,負責建立 ControllerContext(提供MVC當前請求的具體上下文),ControllerBase 以“顯式介面實現”的方式定義了Execute() 方法,該方法在內部直接調用受保護的 Execute() 虛方法,而後者最終會調用抽象方法 ExecuteCore() 方法。

Controller 繼承自 ControllerBase 並實現抽象方法ExecuteCore()