2016年2月21日 星期日

iOS筆記:Swift練習(2)

由下面程式碼得知, 我們可以透過設定preferredStyle的參數來指定UIAlertController的樣式

.ActionSheet 跟 .Alert.

UIAlertAction的Style是一個有三個可能值的列舉

Default, Cancel, Destructive
style可以使用 UIAlertActionStyle.Default 或是 .Default皆可

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        //建立一個類似動作清單的選單
        let optionMenu = UIAlertController(title: nil, message: "What do you want to do", preferredStyle: .ActionSheet)
        //加入動作至選單中
        let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
        optionMenu.addAction(cancelAction)
        //呈現選單
        self.presentViewController(optionMenu, animated: true, completion: nil)
    }

為提示控制器加上動作

現在即是所謂的Closure, 類似於Obj-C中的Block. 可以在程式中做傳遞的程式碼區塊.
(action:UIAlertAction!) -> Void:參數以及回傳的型態.
in表示閉包的參數與回傳值型態的定義已經完成, 可以開始閉包的本體程式.

let callActionHandler = { (action:UIAlertAction!) -> Void in
            ...
        }

完整版:

let callActionHandler = { (action:UIAlertAction!) -> Void in
            let alertMessage = UIAlertController(title: "Service Unavailable", message: "Sorry, the call feature is not available yet. Please retry later.", preferredStyle: .Alert)
            alertMessage.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
            self.presentViewController(alertMessage, animated: true, completion: nil)
        }

        let callAction = UIAlertAction(title: "Call " + "123-456-\(indexPath.row)", style: UIAlertActionStyle.Default, handler: callActionHandler)
let callActionHandler = { (action:UIAlertAction!) -> Void in
            ...
        }

下面則為另外一種實現Closure的方式

let isVisitedAction = UIAlertAction(title: "I've been here", style: .Default, handler: {
            (action:UIAlertAction!) -> Void in
            let cell = tableView.cellForRowAtIndexPath(indexPath)
            cell?.accessoryType = .Checkmark
        })

程式中的問號

上述程式中的Cell是一個Optional, 而Optional只是表示是否有值, 所以上述程式的意思就是Swift會先幫你確認Cell是否存在, 如果存在的話可以允許設定accessoryType的值.

Cell的右邊空間主要是預留給Accessory view:
1. Disclosure indicator
2. Detail disclosure
3. Checkmark
4. Detail

增加刪除列

只要新增以下程式碼就可以看到刪除的選項出現, 至於刪除按鍵點下去後會發生什麼事情就是在大括號內實作:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        <#code#>
    }

或是透過tableView的deleteRowsAtIndexPaths方法來刪除特定列, 還可以選擇刪除的動畫如:.Right, .Left, .Top .Fade

UITableViewRowAction

這個類別可以對表格視圖自訂動作, 想要加入動作到Row上的話, 只要實作以下:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
        <#code#>
    }

當你利用這個方式來新增功能的時候在上面所提到的刪除列的方式就不適用. 需要改透過RowActionh才行.

用法:可以指定標題, 樣式以及當下使用者按下按鈕時會執行的程式碼.
以下我們設定了一個Share的動作, 當使用者按下後, 會帶出一個Activity Controller來使用社群分享功能.

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {

        let shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Share", handler: {
            (action,indexPath) -> Void in
            let defaultText = "Just checking in at "+self.restaurantNames[indexPath.row]
            let activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            self.presentViewController(activityController, animated: true, completion: nil)
        })

        let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler: {
            (action, indexPath) -> Void in
            self.restaurantNames.removeAtIndex(indexPath.row)
            ...
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        })
        return [shareAction, deleteAction]
    }

UIActivityViewController

一個提供數種服務的View Controller, 像是複製項目到剪貼簿, 分享內容到社群網站…等, 只需要建立UIActivityViewController的實體以及訊息, 然後將控制器呈現在畫面上即可.

按鈕的顏色預設是紅色的, 客製化方式如下.

shareAction.backgroundColor = UIColor.blueColor()
        deleteAction.backgroundColor = UIColor(red: 202.0/225.0, green: 202.0/225.0, blue: 203.0/225.0, alpha: 1.0)

Segue

在iOS9中有以下幾種型態
1. Show:一般常見的移動到下一個視窗後有返回按鈕的都是這個.
2. Show Detail:和Show類似但是不會有返回按鈕.
3. Present Modally:以Modal型態呈現. 視窗將會以動畫的方式從底部出現.
4. Present as Popover:將內容以固定點彈跳框來呈現.

Note:CMD + /可以將框起來的部分code註解/取消註解.

最重要的則是要覆寫以下:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "ShowRestaurantDetail" {
            if let indexPath = tableView.indexPathForSelectedRow {
                let destinationController = segue.destinationViewController as! RestaurantDetailViewController
                destinationController.restaurantImage = restaurantImages[indexPath.row]
            }
        }
    }

2016年2月18日 星期四

iOS筆記:Swift練習(1)

第一個實作永遠都是要來個TableView.

利用Stack View來客製化Cell

按著Command選取紅色匡中的三個Label先按下底下Stack的按鈕, 並透過調整檢視器中的”Spacing”來調整各個Label之間的間距.

再將這三個Label跟ImageView合成一個Stack.

enter image description here

建立連結

IBOutlet:用來指出可以和Storyboard中的視圖物件連結的屬性
IBAction:可能經由某個事件所觸發的動作方法

    @IBOutlet var thumbnailImageView: UIImageView!
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var locationLabel: UILabel!
    @IBOutlet var typeLabel: UILabel!

將Cell的ImageView改成圓形

每一個UIKit的視圖(ex. UIView, UIImageView…)的背後都是CALayer類別的實體, 這些物件主要設計作為視圖背後的管理以及處理視圖相關動畫.

     cell.thumbnailImageView.image = UIImage(named: restaurantImages[indexPath.row])

     cell.thumbnailImageView.layer.cornerRadius = 30.0
     cell.thumbnailImageView.clipsToBounds = true

enter image description here

Note

  1. ViewController的箭頭不見得話, 可以勾選“Is initial View….”.
    enter image description here

  2. as! & as?
    向下轉型可以讓你轉換一個類別的值至其衍生的類別.
    ex. tableView.dequeueReusableCellWithIdentifier理論上會回傳標準的TableViewCell但如果你使用自訂的Cell. 就需要經過轉型.

如果你清楚轉換後的內容為何則可以使用 as!. 如果不清楚某個型態的值是否可以轉換至另一個則使用as?.

  1. 假如設定都正常且code都打完了, 但是就是沒有值出現在cell中. 請確認Storyboard是否有建立連結.
    enter image description here

  2. UITableView Protocol
    UITableViewDataSource: 定義了處理表格資料的方法.
    UITableViewDelegate: 專門處理UITableView section的heading 和 footer還有表格列的選擇與Cell的排序.

  3. 閱讀文件
    將滑鼠移到一個類別或是協定上, 然後按下CMD+Ctrl+? 就會彈出一個視窗.

2016年2月17日 星期三

iOS筆記:iOS9 Auto Layout & Stack

為什麼是點(Points)而不是像素(Pixels)

主要是因為顯示器解析度的不同, 而點系統讓我們可以不論螢幕解析度的變化, 這些煩人的問題讓iOS自己去處理就好, 我們依舊是面對點與最基本的解析度(4/4s的320*480 and 5/5s的320*568)

Size Class

此為擁有自適應性佈局的一個重要觀念. 尺寸類別的概念, 即一個裝置如何依照他的螢幕尺寸與方向來做分類. 可以同時使用Size Class and Ato Layout來設計自適應介面.

尺寸類別指出了水平跟垂直的顯示空間的相對量.
尺寸類別型態:
1. Regular, 表示較大的畫面空間
2. Compact, 表示較小的畫面空間

enter image description here

介紹:http://cg2010studio.com/2014/12/13/ios-%E5%A4%A7%E5%B0%8F%E9%A1%9E%E5%88%A5-size-class/

Auto Layout

如果把已經約束好的介面做了更動, 可以點選黃色按鈕, 介面建構器會很聰明地幫我們解決佈局的問題. 點選問題旁邊的指示圖標, 如下下圖選擇“Update Frame” 按鈕就會回到視圖中間.
enter image description here

enter image description here

Storyboard預覽

在Xcode7中提供開發者預覽的功能, 可以在Xcode中取得UI的預覽圖.

記得按著”Option”鍵然後點選Main.storyboard(Preview)
enter image description here

可以點選“+”按鈕來新增不同尺寸的視窗.
enter image description here

Stack View

提供一個簡化的介面, 以行或列來佈局視圖的集合. 原理就是加多個UI物件群組起來,讓他們可以當作一個單一物件來移動或調整大小.

對於嵌入堆疊視圖內的視圖, 一般稱作”Arrangeed View”

Xcode7提供兩種方式實現Stack View
1. 從元件庫中拖曳一個StackView(horizontal/vertical), 並置於storyboard中. 然後就可以拖曳或放置標籤 按鈕等…
2. 也可以在Auto Layout選單中選取Stack選項.

如果想選取Stack中的某個視圖, 可以按住Shift鍵並在堆疊視圖按右鍵. 就會出現一個快捷選單.

限制堆疊視圖不管螢幕大小如何, 都保持固定的寬高比(Aspect Ratio)

在文件大綱視圖中, 在stackView上按住Ctrl鍵拖曳, 然後選取Aspect Ratio.

2016年2月14日 星期日

iOS筆記:ARC & KVO

Automatic Reference Counting(ARC): 提供自動管理對Objective-C對象的分配記憶體,而不是讓開發者總是在思索什麼時候retain和什麼時候release操作. ARC會預估對象的生命週期並且在編譯時自動插入合適的內存管理方法,也會生成合適的dealloc方法。

strong: 和原來的retain比較相似,strong的property將對應__strong的指標,它將持有所指向的對象
weak:不持有所指向的對象,而且當所指對象銷毀時能將自己置為nil,基本所有的outlet都應該用weak

ARC 是如何判斷何時要回收記憶體的?

compiler會分析你的程式碼中呼叫的message是否有含alloc, new, copy, mutableCopy這些開頭
如果有的話就知道回傳的object的ownership是你的
接著根據你所在的message是否有alloc, new,… 來決定你回傳的物件是哪種類型。

Retain cycle (互為strong)

既然父對象持有子對象,而子對象會隨父對象釋放而釋放,那麼,如果兩個對象相互為父對象怎麼辦?

比如A和B兩個對象,A持有B,B同時也持有A,按照上面的規則,A只有B釋放之後才有可能釋放,同樣B只有A釋放後才可能釋放,當雙方都在等待對方釋放的時候, retain cycle就形成了,結果是,兩個對象都永遠不會被釋放,最終內存泄露。

Key-Value Observing(KVO)

它提供一種機制,當指定的對象的屬性被修改後,則對象就會接受到通知。簡單的說就是每次指定的被觀察的對象的屬性被修改後,KVO就會自動通知相應的觀察者了。

參考:
1. http://onevcat.com/2012/06/arc-hand-by-hand/
2. http://seanysh.blogspot.tw/2014/04/ios-arc.html

iOS筆記:Protocols and Delegation

Protocol 跟 Delegate

你需要做一件事情,你自己不想動手,委託給 delegate 處理,為了避免所託非人,delegate 需要遵守一個你和 delegate 都能接受的 protocol。一個類只要聲明它遵守了某個你想要的 protocol,你就能把它信認為是你的 delegate。

如果使用Apple的delegate是什麼情況?

  1. 在h檔引用適當的protocol。
  2. 依照引用的protocol,於m檔實作必須的方法。
  3. 於m檔加入類似:self.delegate=self的程式碼。

若是要一個delegate method完成值的傳遞必須具備以下內容:

  1. 一個protocol定義檔。
  2. 一個delegate method的執行或運算邏輯。
  3. 一個引用上述protocol,並實作完成的程式檔。

Protocol 範例:
如果沒特別標明的,預設是@required

@protocol Drawable

@required
-(void) draw;

@optional
-(void) whateverMethod;

@end

Delegate 自己動手樂趣多

以下範例為我們想從B傳資料到C, 但是在C視窗中使用back button我們想回到A.

所以在這邊就是要使用Delegate 讓B傳訊息給A, 再讓A去投射到C.
enter image description here

delegate 使用的前三步(委託者):
1. 宣告 delegate 原型
2. 宣告 delegate 變量
3. 調用 delegate 方法

先在B視窗內宣告Protocol:

//B視窗.h file
@protocol PFMatchViewControllerDelegate
-(void)presentMatchesViewController;
@end

@interface PFMatchViewController : UIViewController
@property (weak) id <PFMatchViewControllerDelegate> delegate;
...
@end
//B視窗.m file
- (IBAction)viewChatsButtonPressed:(UIButton *)sender
{
    [self.delegate presentMatchesViewController];
}

delegate 使用的後三步(被委託者):
4. 宣告實現 delegate
5. 設置 delegate 的值
6. 實現 delegate 方法

A視窗.m file

#import "B視窗.h"

@interface PFHomeViewController () <PFMatchViewControllerDelegate>
...
@end

@implementation A視窗
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    BViewController *matchVC = segue.destinationViewController;
    matchVC.delegate = self;
}

- (void)presentMatchesViewController
{
    [self dismissViewControllerAnimated:NO completion:^{
        [self performSegueWithIdentifier:@"homeToMatchesSegue" sender:nil];
    }];
}
@end

matchVC.delegate = self這句話的涵義:
將實作B delegate的A View(self), 指派給B的Delegate屬性中, 好將B視窗取得資料的工作, 委派給A View(self)物件。

Note

  1. 因為Obj-C是不支持多繼承的,所以很多時候都是用Protocol來代替.
  2. delegate 這個 property 並不一定要命名為 delegate,只是慣例上通常會使用 delegate 這個名字
  3. protocol 的定義也不一定要放在同一個 header 檔裡,也可以另外獨立的檔案, 但通常都會寫在同一個檔案裡
  4. protocol 本身不會實作,只會宣告 method,實作的部分由使用這個 protocol 的 class 來實作.

參考:
1. http://www.jianshu.com/p/2310f29d45e0s
2. http://lokanghung.blogspot.tw/2013/06/ios-delegate-protocol.html
3. http://blog.eddie.com.tw/2013/05/24/delegation-in-objective-c/
4. http://www.cnblogs.com/GarveyCalvin/p/4210828.html

2016年2月13日 星期六

iOS筆記:Core Data

Core Data

SQLite資料庫是預設作為Core data的儲存區.

一種以物件導向方式, 讓使用者跟資料庫(或者是其他永續性儲存器)互動的框架. 因此使用Core Data 只要將你App中的物件與資料庫中的表格相對應即可, 甚至不需要瞭解SQL.

Core Data Stack

enter image description here

Xcode:
第一個實體(Entity,即table)。點選下方的 “Add Entity”,在左邊欄的會出現新的 “Entity” 項目.

enter image description here

新增這個 Product entity 的屬性(Attirbutes),點選 Attributes 欄位下方的 “+” (或下方的 “Add Attribute” 也可以)即可新增。

enter image description here

再來要讓程式碼中可以使用這個 Entity,我們要建立一個 Product 類別,並且繼承 NSManagedObject。XCode 可以幫我們自動產生,在選單列中選擇 Editor > Create NSManagedObject Subclass…:

enter image description here

接下來就一連串的Next及Create, 最後就會產生下列的檔案. 而這四個檔案就是用來存取Core Data資料的關鍵檔案.

enter image description here

單箭頭跟雙箭頭的差異在於”Relationship”, 一對多 or 多對多的概念.
enter image description here

存取Core Data資料

第一步先import剛剛建立的.h跟.m 檔案.
存資料可以透過

[self.managedObjectContext save:nil];

要抓取資料時, 必須先建立fetch request, 再指定這個fetch request要從哪個entity取出資料.

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Album"];
    fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]];

    NSError *error = nil;
    NSArray *fetchedAlbums = [[PACoreDataHelper managedObjectContext] executeFetchRequest:fetchRequest error:&error];

刪除資料

[[self.photo managedObjectContext] deleteObject:self.photo];

Core Data error

當遇到底下的Error時, 解決辦法就是delete the app in iOS simulator and run it again.

returned error Error Domain=NSCocoaErrorDomain Code=134100 “The operation couldn’t be completed. (Cocoa error 134100.)” UserInfo=0x7fdc09663080 {metadata={ NSPersistenceFrameworkVersion = 519; NSStoreModelVersionHashes = { TODOITEM = <222b8d5c 4aad84a8 08eb331e 50cd9b02 ed5eed35 ee67488b 069ff634 799badb7>; }; NSStoreModelVersionHashesVersion = 3; NSStoreModelVersionIdentifiers = ( “” ); NSStoreType = SQLite; NSStoreUUID = “26066907-B092-491B-BB89-BE8CC6B2B845”; “_NSAutoVacuumLevel” = 2; }, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {

NSFetchRequest

enter image description here

參考網址:
http://ikevin.tw/?p=541
http://useyourloaf.com/blog/uialertcontroller-changes-in-ios-8.html

2016年2月11日 星期四

Git指令:Rebase觀念 & 雜項

Atom

最近改試用Atom, 下面是個人認為可以安裝的套件.
1. Autocomplete-python 自動幫你把 python 語言完成,變相可以放更多心思在開發而不是重覆代碼上面
2. linter-flake8 可以幫你將某些重點的代碼 highlight,有助減少錯誤
3. atom-runner 按下 Alt + R,就可以在右方看到你的代碼在執行了!

Git branch and merge/rebase

雖然Merge 跟 Rebase都是合併分支但是使用上還是有些不一樣. 合併的方式與步驟不同
* MERGE 如何合併 – 假設你有一個 new_feature 分支想合併回 master – 你會這樣執行
* git checkout master
* git merge new_feature (會建立起一條分支線圖)

  • REBASE 如何合併 – 假設你有一個 new_feature 分支想合併回 master ( 事實上是將自己的所有變更 commit 在 master 最新版後面)
    • git checkout new_feature
    • git rebase master (會讓 new_feature 併進 master 的線圖上)
    • git checkout master
    • git merge new_feature

Rebase優點:
用來確保整體分支線圖保持乾淨, 還有在整理分支線圖時會用到.
enter image description here

Run below command will print out the history of your commits, showing where your branch pointers are and how your history has diverged.

$ git log --oneline --decorate --graph --all

發生 confict 時的處理步驟

  1. 若合併失敗 (發生confict),會停留在 Unmerged 狀態,需人工介入處理,處理完衝突,需繼續完成合併動作 (需做一次commit動作)
  2. 將發生 confict 的檔案打開,處理內容( 別忘了刪除<<<、===、>>>)。
  3. 使用 git add 將處理好的檔案加入 stage。
  4. 反覆步驟 1~2 直到所有 confict 處理完畢。
  5. git commit 提交合併訊息。
  6. 完成

如果放棄合併可用:

$ git reset --hard

取消已暫存的檔案

有時候手殘不小心將還沒修改完的檔案使用 git add 加入了 stage ,這時候可以使用 git reset HEAD <file> 來將這支檔案取消 stage:

$ git reset HEAD <file name>

取消修改過的檔案

想完全放棄這次修改 (將檔案狀態回復到最新的一次 commit 時的狀態):

$ git checkout -- <file name> 

git reset中 hard 與 soft 的差異

使用 git reset 的時候,都會把目前狀態回復到你想回復的版本,但若是不加參數的情況,會把你做過的修改仍然保留,但是,若是加上 —soft 參數,則會把做過的修改加入 stage ,若是加上 hard 參數的話則是把做過的修改完全刪除,回到那個版本原本的樣子。

協作

  1. 利用clone下載檔案
$ git clone git@github.com:ihower/sandbox.git
  1. 遠端更新
$ git pull or git pull origin master

實際作用是先 git fetch 遠端的 branch,然後與本地端的 branch 做 merge,產生一個 merge commit 節點
3. 將 Commit 送出去

$ git push or git push origin master

實際的作用是將本地端的 master branch 與遠端的 master branch 作 fast-forward 合併。如果出現 [rejected] 錯誤的話,表示你必須先作 pull。

參考:
1. http://www.slideshare.net/WillHuangTW/git-merge-rebase
2. http://gogojimmy.net/2012/01/21/how-to-use-git-2-basic-usage-and-worflow/
3. http://eltonnoteshk.blogspot.tw/2015/10/ide-atom-20102015.html
4. https://git-scm.com/

iOS筆記:Swift(5)

Initialization

Initialization is the process of preparing an instance of a class, structure, or enumeration for use.
(為了使用某個類別、結構或列舉型別的實例而進行的準備過程。這個過程包含了為實例中的每個屬性設置初始值和為其執行必要的準備和初始化任務。)

Initializers

建構器在創建某特定型別的新實例時呼叫。它的最簡形式類似於一個不帶任何參數的實例方法,以關鍵字init命名。

下面範例中定義了一個用來保存華氏溫度的結構Fahrenheit,它擁有一個Double型別的儲存型屬性temperature:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit")
// 輸出 "The default temperature is 32.0° Fahrenheit」

更簡單的方式在定義結構Fahrenheit時為屬性temperature設置預設值:

struct Fahrenheit {
    var temperature = 32.0
}

Automatic Reference Counting

Swift 使用自動引用計數(ARC)這一機制來跟蹤和管理你的應用程式的內存。通常情況下,Swift 的內存管理機制會一直起著作用,你無須自己來考慮內存的管理。

How ARC Works

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the type of the instance, together with the values of any stored properties associated with that instance.

The reference is called a “strong“ reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.

Strong Reference Cycles Between Class Instances

下面的範例展示了自動引用計數的工作機制。範例以一個簡單的Person類別開始,並定義了一個叫name的常數屬性:

class Person {
    let name: String
    init(name: String) {
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }
}

在上面的範例中,ARC 會跟蹤你所新創建的Person實例的參考數量,並且會在Person實例不再被需要時銷毀它。

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

將這兩個實例關聯在一起之後,一個迴圈強參考被創建了。Person實例現在有了一個指向Apartment實例的強參考,而Apartment實例也有了一個指向Person實例的強參考。因此,當你斷開john和number73變數所持有的強參考時,參考計數並不會降為 0,實例也不會被 ARC 銷毀:

當你把這兩個變數設為nil時,沒有任何一個析構函式被呼叫。強參考迴圈阻止了Person和Apartment類別實例的銷毀,並在你的應用程式中造成了內存泄漏。

在你將john和number73賦值為nil後,強參考關系如下圖:

enter image description here

Resolving Strong Reference Cycles Between Class Instances

Swift 提供了兩種辦法用來解決你在使用類別的屬性時所遇到的迴圈強參考問題:弱參考(weak reference)和無主參考(unowned reference)。

Weak References

弱參考不會牢牢保持住參考的實例,並且不會阻止 ARC 銷毀被參考的實例。這種行為阻止了參考變為迴圈強參考。宣告屬性或者變數時,在前面加上weak關鍵字表明這是一個弱參考。

在實例的生命周期中,如果某些時候參考沒有值,那麼弱參考可以阻止迴圈強參考。

Weak references must be declared as variables, to indicate that their value can change at runtime. A weak reference cannot be declared as a constant.

Checking Type

用型別檢查運算子(is)來檢查一個實例是否屬於特定子型別。若實例屬於那個子型別,型別檢查運算子回傳 true ,否則回傳 false 。

for item in library {
    if item is Movie {
        ++movieCount
    } else if item is Song {
        ++songCount
    }
}

Any和AnyObject的型別檢查

Swift為不確定型別提供了兩種特殊型別別名:

  • AnyObject可以代表任何class型別的實例。
  • Any可以表示任何型別,除了方法型別(function types)

Extension Syntax

宣告一個擴展使用關鍵字extension:

extension SomeType {
    // 加到SomeType的新功能寫到這裡
}

or 一個擴展可以擴展一個已有型別,使其能夠適配一個或多個協定(protocol)。當這種情況發生時,協定的名字應該完全按照類別或結構的名字的方式進行書寫:

extension SomeType: SomeProtocol, AnotherProctocol {
    // 協定實作寫到這裡
}

計算型屬性(Computed Properties)

擴展可以向已有型別添加計算型實例屬性和計算型型別屬性。

extension Double {
    var km: Double { return self * 1_000.0 }
    var m : Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
println("One inch is \(oneInch) meters")
// 列印輸出:"One inch is 0.0254 meters"
let threeFeet = 3.ft
println("Three feet is \(threeFeet) meters")
// 列印輸出:"Three feet is 0.914399970739201 meters"

Extension Methods

extension Int {
    func repetitions(task: () -> ()) {
        for i in 0..self {
            task()
        }
    }
}

repetitions方法使用了一個() -> ()型別的單參數(single argument),表明函式沒有參數而且沒有回傳值。

定義該擴展之後,你就可以對任意整數呼叫repetitions方法,實作的功能則是多次執行某任務:

3.repetitions({
    print("Hello!")
    })
// Hello!
// Hello!
// Hello!

Protocol Syntax

協定的定義與類別,結構,列舉的定義非常相似,如下所示:

protocol SomeProtocol {
    // 協定內容
}

在類別,結構,列舉的名稱後加上協定名稱,中間以冒號:分隔即可實作協定;實作多個協定時,各協定之間用逗號,分隔,如下所示:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 結構內容
}

當某個類別含有父類別的同時並實作了協定,應當把父類別放在所有的協定之前,如下所示:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 類別的內容
}

Protocol Property & Method Requirements

協定能夠要求其遵循者必須含有一些特定名稱和型別的實例屬性(instance property)或類別屬性 (type property),也能夠要求屬性具有(設置權限)settable 和(存取權限)gettable,但它不要求屬性是儲存型屬性(stored property)還是計算型屬性(calculate property)。

前綴var關鍵字將屬性宣告為變數。在屬性宣告後寫上{ get set }表示屬性為可讀寫的。{ get }用來表示屬性為可讀的。即使你為可讀的屬性實作了setter方法.

protocol SomeProtocol {
    var musBeSettable : Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

用類別來實作協定時,使用class關鍵字來表示該屬性為類別成員;用結構或列舉實作協定時,則使用static關鍵字來表示:

protocol AnotherProtocol {
    class var someTypeProperty: Int { get set }
}

protocol FullyNamed {
    var fullName: String { get }
}

FullyNamed協定含有fullName屬性。因此其遵循者必須含有一個名為fullName,型別為String的可讀屬性。

struct Person: FullyNamed{
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
//john.fullName 為 "John Appleseed"

方法跟屬性類似, 其遵循者必備某些特定的實例方法和類別方法。協定方法的宣告與普通方法宣告相似,但它不需要方法內容。

protocol SomeProtocol {
    class func someTypeMethod()
}

protocol RandomNumberGenerator {
    func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
println("Here's a random number: \(generator.random())")
// 輸出 : "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// 輸出 : "And another one: 0.729023776863283"

Mutating Method Requirements

能在方法或函式內部改變實例型別的方法稱為突變方法。Swift預設不允許修改型別,因此需要前綴mutating關鍵字用來表示該函式中能夠修改型別)

如下所示,Togglable協定含有toggle函式。根據函式名稱推測,toggle可能用於切換或恢復某個屬性的狀態。mutating關鍵字表示它為突變方法:

protocol Togglable {
    mutating func toggle()
}

當使用列舉或結構來實作Togglabl協定時,必須在toggle方法前加上mutating關鍵字。

enum OnOffSwitch: Togglable {
    case Off, On
    mutating func toggle() {
        switch self {
        case Off:
            self = On
        case On:
            self = Off
        }
    }
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
//lightSwitch 現在的值為 .On

泛型所解決的問題(The Problem That Generics Solve)

泛型程式碼可以讓你寫出根據自我需求定義、適用於任何型別的,靈活且可重用的函式和型別。它的可以讓你避免重複的程式碼,用一種清晰和抽像的方式來表達程式碼的意圖。

泛型是 Swift 強大特征中的其中一個,許多 Swift 標準函式庫是通過泛型程式碼構建出來的。
泛型函式

泛型函式可以工作於任何型別,這裡是一個swapTwoInts(交換int的值)函式的泛型版本,用於交換兩個值:

func swapTwoValues<T>(inout a: T, inout b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoValues函式主體和swapTwoInts函式是一樣的,它只在第一行稍微有那麼一點點不同於swapTwoInts,如下所示:

func swapTwoInts(inout a: Int, inout b: Int)
func swapTwoValues<T>(inout a: T, inout b: T)

這個函式的泛型版本使用了占位型別名字(通常此情況下用字母T來表示)來代替實際型別名(如In、String或Doubl)。占位型別名沒有提示T必須是什麼型別,但是它提示了a和b必須是同一型別T,而不管T表示什麼型別。只有swapTwoValues函式在每次呼叫時所傳入的實際型別才能決定T所代表的型別。
swapTwoValues函式除了要求傳入的兩個任何型別值是同一型別外,也可以作為swapTwoInts函式被呼叫。每次swapTwoValues被呼叫,T所代表的型別值都會傳給函式。

在下面的兩個範例中,T分別代表Int和String:

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

2016年2月9日 星期二

iOS筆記:Swift(4)

Methods

Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods, which encapsulate specific tasks and functionality for working with an instance of a given type. Classes, structures, and enumerations can also define type methods, which are associated with the type itself. Type methods are similar to class methods in Objective-C.

Structures and enumerations can define methods in Swift is a major difference from C and Objective-C. In Objective-C, classes are the only types that can define methods. In Swift, you can choose whether to define a class, structure, or enumeration, and still have the flexibility to define methods on the type you create.

Instance Methods

實例方法是屬於某個特定類別、結構或者列舉型別實例的方法。實例方法提供存取和修改實例屬性的方法或提供與實例目的相關的功能,並以此來支撐實例的功能。實例方法的語法與函式完全一致

    class Counter {
        var count = 0
        func increment() {
            ++count
        }
        func incrementBy(amount: Int) {
            count += amount
        }
        func reset() {
            count = 0
        }
    }

    let counter = Counter()
    // the initial counter value is 0
    counter.increment()
    // the counter's value is now 1
    counter.incrementBy(5)
    // the counter's value is now 6
    counter.reset()
    // the counter's value is now 0

The Counter class defines three instance methods:
* increment increments the counter by 1.
* incrementBy(amount: Int) increments the counter by a specified integer amount.
* reset resets the counter to zero.

Local and External Parameter Names for Methods

Function parameters can have both a local name (for use within the function’s body) and an external name (for use when calling the function), as described in Specifying External Parameter Names.

    class Counter {
        var count: Int = 0
        func incrementBy(amount: Int, numberOfTimes: Int) {
            count += amount * numberOfTimes
        }
    }

incrementBy(_:number Of Times:)方法有兩個參數: amount和numberOfTimes。默認情況下,Swift 只把amount當作一個局部名稱,但是把numberOfTimes即看作局部名稱又看作外部名稱。下面調用這個方法:

    let counter = Counter()
    counter.incrementBy(5, numberOfTimes: 3)
    // counter value is now 15

The self Property

型別的每一個實例都有一個隱含屬性叫做 self , self 完全等同於該實例本身。你可以在一個實例的實例方法中使用這個隱
含的 self 屬性來參考當前實例。

只要在一個方法中使用一個已知的屬性或者方法名稱,如果沒有明確的寫 self ,Swift 假定是指當前實例的屬性或者方法。

func increment() {
    self.count++
}

Modifying Value Types from Within Instance Methods

Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.
但是,如果你確實需要在某個具體的方法中修改結構或者列舉的屬性,你可以選擇變異(mutating)這個方法,然後方法就可以從方法內部改變它的屬性;並且它做的任何改變在方法結束時還會保留在原始結構中。

要使用變異方法, 將關鍵字mutating 放到方法的func關鍵字之前就可以了:

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveByX(deltaX: Double, y deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
println("The point is now at (\(somePoint.x), \(somePoint.y))")
// 輸出 "The point is now at (3.0, 4.0)"

Assigning to self Within a Mutating Method

變異方法能夠賦給隱含屬性self一個全新的實例。

enum TriStateSwitch {
  case Off, Low, High
  mutating func next() {
    switch self {
    case Off:
      self = Low
    case Low:
      self = High
    case High:
      self = Off
    }
  }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight 現在等於 .High
ovenLight.next()
// ovenLight 現在等於 .Off

Inheritance

在 Swift 中,類別可以呼叫和存取超類別的方法,屬性和下標腳本(subscripts),並且可以重寫(override)這些方法,屬性和下標腳本來優化或修改它們的行為。

下面的範例定義了一個叫Vehicle的基類別。這個基類別宣告了兩個對所有車輛都通用的屬性(numberOfWheels和maxPassengers)。這些屬性在description方法中使用,這個方法回傳一個String型別的,對車輛特征的描述:

class Vehicle {
    var numberOfWheels: Int
    var maxPassengers: Int
    func description() -> String {
        return "\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
    }
    init() {
        numberOfWheels = 0
        maxPassengers = 1
    }
}

如果要創建一個Vehicle類別的新實例,使用建構器語法呼叫上面的初始化器,即類別名後面跟一個空的小括號:

let someVehicle = Vehicle()

Subclassing

以下定義一個新的類別叫Bicycle,它繼承了Vehicle的特性:

class Bicycle: Vehicle {
    init() {
        super.init()
        numberOfWheels = 2
    }
}

Bicycle類別定義了一個建構器來設置它定制的特性(自行車只有2個輪子)。Bicycle的建構器呼叫了它父類別Vehicle的建構器 super.init(),以此確保在Bicycle類別試圖修改那些繼承來的屬性前Vehicle類別已經初始化過它們了。

不像 Objective-C,在 Swift 中,初始化器預設是不繼承的

Overriding

A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding.

如果要重寫某個特性,你需要在重寫定義的前面加上override關鍵字。
override關鍵字會提醒 Swift 編譯器去檢查該類別的超類別(或其中一個父類別)是否有匹配重寫版本的宣告。這個檢查可以確保你的重寫定義是正確的。任何缺少override關鍵字的重寫都會在編譯時被診斷為錯誤。

在合適的地方,你可以通過使用super前綴來存取超類別版本的方法,屬性或下標腳本:

  1. 在方法someMethod的重寫實作中,可以通過super.someMethod()來呼叫超類別版本的someMethod方法。
  2. 在屬性someProperty的 getter 或 setter 的重寫實作中,可以通過super.someProperty來存取超類別版本的someProperty屬性。
  3. 在下標腳本的重寫實作中,可以通過super[someIndex]來存取超類別版本中的相同下標腳本

Overriding Methods

You can override an inherited instance or type method to provide a tailored or alternative implementation of the method within your subclass.

下面的範例定義了Vehicle的一個新的子類別,叫Car,它重寫了從Vehicle類別繼承來的description方法:

class Car: Vehicle {
    var speed: Double = 0.0
    init() {
        super.init()
        maxPassengers = 5
        numberOfWheels = 4
    }
    override func description() -> String {
        return super.description() + "; "
            + "traveling at \(speed) mph"
    }
}

Car中的description方法並非完全自定義,而是通過super.description使用了超類別Vehicle中的description方法,然後再追加一些額外的資訊,比如汽車的當前速度。

Preventing Overrides

你可以通過把方法,屬性或下標腳本標記為final來防止它們被重寫,只需要在宣告關鍵字前加上@final特性即可。(例如:@final var, @final func, @final class func, 以及 @final subscript

2016年2月8日 星期一

Python筆記:Web crawler practice

最近練習了在Python3中如何使用爬蟲來抓取網頁的資料.
聽別人說爬蟲爬了半天終於找到時間可以做個簡單的練習. 並做了POST/GET的練習.

Urllib

一般網頁的原理如下圖所示, 當有使用者發出請求時, Server會響應請求並作出回應。

我們就是利用Python來模擬Client端發送請求給Server,然後將得到的html文件進行解析就能得到你想要的內容了.

enter image description here

第一步透過Chrome來觀察目標網頁是走POST 或是 GET

enter image description here

第二步

確認From data

enter image description here

第三步

透過Urllib這個library來模擬request.

urlopen方法是用來打開一個網頁的(其實是發送一個請求), 他也支援多個參數.
urllib.request.Request功能為封裝一個請求。你可以給該請求添加提交的數據、頭(header)和指定提交方法等。這在登錄某些網站時是必須的。

urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None) 

header則是請求信息的頭。有點像文章的標題,服務器一看頭就知道你的身份和目的。瀏覽器有時會以header中的User-Agent一項來判斷是不是爬​​蟲.

urllib.parse.urlencode主要將data轉換成url所接受的格式

ex. 下圖為抓取高鐵網站的範例

import urllib.request

url = "http://www.thsrc.com.tw/tw/TimeTable/SearchResult"
request = urllib.request.Request(url)
request.add_header("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36")

form_data = {
    "StartStation": "977abb69-413a-4ccf-a109-0272c24fd490",
    "EndStation": "f2519629-5973-4d08-913b-479cce78a356",
    "SearchDate": "2016/02/05",
    "SearchTime": "17:00",
    "SearchWay":"DepartureInMandarin",
    "RestTime":"",
    "EarlyOrLater":""
}
form_data = urllib.parse.urlencode(form_data)
form_data = form_data.encode('utf-8')
response = urllib.request.urlopen(request,data=form_data)

在urllib.request.urlopen中, 如果沒有傳送data的話, 就會被默認是使用GET的方式傳送資料.

這裡的重點就是: url = url + "?" + form_data

url = "http://www.showtimes.com.tw/timetable.asp"
form_data = {
    'date':'2016-02-04',
    'sid':'8'
}
form_data = urllib.parse.urlencode(form_data)

url = url + "?" + form_data
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)

分析HTML

得到網頁資訊之後接下來就是分析了, 以下我會利用beautifulSoup來做介紹.

BeautifulSoup

功能強大的HTML Parser. Beautiful Soup將復雜HTML文檔轉換成一個複雜的樹形結構,每個節點都是Python對象,所有對象可以歸納為4種:
1. Tag
簡單來說title a 等等 HTML 標籤加上里麵包括的內容就是 Tag.

<title>The Dormouse's story</title>

Tag,它有兩個重要的屬性,是 name 和 attrs

print soup.p.attrs
#{'class': ['title'], 'name': 'dromouse'}
  1. NavigableString
    用 .string 即可獲得標籤內的資料.

  2. BeautifulSoup

  3. Comment

安裝

$ pip install beautifulsoup4

使用

from bs4 import BeautifulSoup

soup = BeautifulSoup(response, 'html.parser')

for time in soup.find_all('table', attrs={'class':"touch_table"}):
    print(time.text)

參考:
1. http://www.showtimes.com.tw/timetable.asp
2. http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#find-all
3. https://docs.python.org/3/library/urllib.html
4. http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#find-all
5. http://cuiqingcai.com/1319.html