Posted on

Swift初探

條件編譯

這是 Swift 的條件編譯(Conditional Compilation)的一個示例,它允許你根據特定的條件選擇性地編譯代碼。在這段代碼中,它確定代碼是在 iOS 模擬器中運行還是在實際的 iOS 裝置上運行。

    #if targetEnvironment(simulator)
    self.videoCapturer = RTCFileVideoCapturer(delegate: videoSource)
    #else
    self.videoCapturer = RTCCameraVideoCapturer(delegate: videoSource)
    #endif

判斷變數是否存在

Swift的空值會是nil

什麼 是 Protocol 與 Delegate

1. Protocol

在 Swift 中,協議(Protocol)定義了一套規範或合約,但不提供具體的實現。任何型別(如 classstructenum)都可以遵循(implement)這些協議,並為協議中的要求提供具體的實現。

例如,我們可以定義一個表示可序列化對象的 Serializable 協議:

protocol Serializable {
    func serialize() -> String
}

然後,我們可以讓某個 structclass 遵循這個協議:

struct Person: Serializable {
    var name: String
    var age: Int

    func serialize() -> String {
        return "\(name),\(age)"
    }
}

2. Delegate

Delegate 是一種設計模式,允許一個物件將某些決策或功能外包給另一個物件。在 Swift 中,Delegate 通常是通過協議來實現的。這意味著當一個類想要成為另一個類的代理時,它必須遵循一個特定的協議。

例如,假設我們有一個 Downloader 類,當下載完成時,我們希望通知另一個物件。我們可以定義一個 DownloaderDelegate 協議:

protocol DownloaderDelegate: AnyObject {
    func didFinishDownloading(data: Data)
}

然後,Downloader 類可以有一個 delegate 屬性:

class Downloader {
    weak var delegate: DownloaderDelegate?

    func download() {
        // 下載過程...
        // 完成後通知代理:
        delegate?.didFinishDownloading(data: someData)
    }
}

其他類(例如 ViewController)可以遵循 DownloaderDelegate 協議並成為 Downloader 的代理,以響應下載完成事件。

這兩個概念在 iOS 和 Swift 開發中都是核心的,用於很多不同的場景和功能。

強大的extension

在 Swift 中,類別 (class)、結構體 (struct)、列舉 (enum)、或者協定 (protocol) 都可以使用 extension 來增加功能。這是 Swift 中一個非常強大的功能,使得你可以將類別或結構體的定義分散到多個區塊,使其更具組織性和可讀性。

以下是使用 extension 的一些常見原因:

  1. 組織性: 你可以使用 extension 來組織你的程式碼,將相似的功能或者與某個特定主題相關的方法放在一起。
  2. 遵循協定: 如果你的類別需要遵循某個協定 (protocol),你可以使用 extension 來隔離所有與該協定相關的實現。
  3. 可讀性: 將類別或結構體分成小區塊可以提高程式碼的可讀性。
  4. 重用和模組化: 你可以定義多個 extension,並將它們放在不同的檔案中,以便於重用或者模組化。

在你給出的例子中,原始的 WebRTCClient 類別可能已經有了一些基本功能。然後,通過使用 extension,增加了關於視頻控制的功能。這種方式可以讓每個 extension 專注於一個特定的功能或者主題,從而使得整體的程式碼更加清晰和有組織性。

總的來說,Swift 中的 extension 功能允許我們對既有的類別或其他資料結構進行進一步的擴展,這樣可以使得程式碼更具組織性、可讀性,並且更容易維護。

分開寫 extension 和將其寫在一起,從功能的角度來看,並沒有區別。不過,程式碼的組織方式往往是為了達到更好的可讀性和易於維護。將不同的功能或與特定主題相關的方法分開在不同的 extension 中,可以幫助讀者和開發者更快地了解該區塊的主要功能,也更容易定位和修改相關的代碼。

使用guard控制流結構

guard 是 Swift 語言中的一個控制流結構,用於提前退出(early exit)方法或函數的執行,當某些條件不成立時。它是為了減少嵌套(nested)的深度和提高程式碼的可讀性。

以下是 guard 的一些特點:

  1. guard 之後必須跟一個條件,如果這個條件為 false,則執行 else 裡面的代碼。
  2. else 裡面必須有一個轉移控制語句,例如 returnbreakcontinuethrow,以確保退出當前的執行流程。
  3. 如果 guard 的條件為 true,則會繼續執行 guard 之後的代碼。

例如:

guard let dataToSend = alert.textFields?.first?.text?.data(using: .utf8) else {
    return
}

這段代碼試圖從 alert 的第一個文本欄位中獲取文本,然後將其轉換為 UTF-8 編碼的 Data 對象。如果其中任何一步失敗(例如,文本欄位為 nil、文本為空或轉換失敗),則整個條件為 false,並執行 else 裡面的 return 語句,從當前函數或方法中提前退出。

使用 guard 而不是嵌套的 if-let 可以使程式碼更加整潔、易讀,特別是當有多個條件需要檢查時。

在Swift中使用閉包

在Swift中,$0, $1, $2, … 是在閉包中使用的隱式名稱,代表閉包的第一個、第二個、第三個…參數。

peerConnection.transceivers
    .compactMap { return $0.sender.track as? T }
    .forEach { $0.isEnabled = isEnabled }

這裡有兩個閉包:一個是用於 compactMap 的閉包,另一個是用於 forEach 的閉包。

  1. 對於 compactMap 的閉包:$0 代表 peerConnection.transceivers 集合中的每一個元素,即每一個 RTCRtpTransceiver 對象。在這個閉包中,它試圖取出每個 transceiver 的 sender 的 track 並嘗試將其轉型為指定的 T 類型。
  2. 對於 forEach 的閉包:在 compactMap 運行之後,我們獲得一個包含符合指定類型 T 的軌道的集合。在 forEach 的閉包中,$0 代表這些軌道。閉包的作用是設置這些軌道的 isEnabled 屬性。
Posted on

套件管理工具CocoaPods介紹

這是1/9的cocoaHeads裡,SuperBil分享的套件管理工具。
之前小岡也有和我推薦過這個工具,當時沒有去深入研究如何使用。
這次與會完後,便開始試著學習使用這個管理套件。(裝完後心得:天呀!實在太好用了!必裝~)

分享資料

投影片:做自己的可可豆夾
錄影檔:CocoaPods

CocoaPods介紹

CocoaPods是一個管理套件的工具。

過去在開發app時,如果我們想要用一些第三方的Library,通常會要到GitHub下載專案到本地端,然後把它載入專案裡。這樣如果套件有更新時,都要手動更新,若是不同版本的ios要用到不同的library,又要手動去管理,會比較麻煩。

並且如果是直接把原始碼放到專案裡,會很容易和自己寫的code混在一起,管理和瀏覽都會較為困難。

CocoaPods就是用來管理這些第三方套件,使用CocoaPods之後,專案會變這樣:

螢幕快照 2014-01-12 下午4.48.50
要改成點選GourmentGroup.xcworkspace開啟專案

螢幕快照 2014-01-12 下午4.44.44
下方會多一個Pods的套件資料,裡面放的是所有使用到的套件

 安裝方法

這篇文章有非常詳細的方法:CocoaPods

比較要注意的是,我今天在安裝時,因為Podfile檔案所使用的編碼錯誤,會出現如下錯誤
incompatible character encodings: ASCII-8BIT and UTF-8 cocoaPods
後來我雖然把檔案改成utf-8,還是一直跑出同樣的錯誤。
後來才發現,如果有錯誤,要先把終端機關掉再打開,才會再一次執行。

接下來,如果要更新Podfile,到終端機打入
$pod update
就可以了!

投影片另外有講到podSpec,如果有在cocoaPods裡面沒有包含的Library
(現有的Library可到http://cocoapods.org/去輸入函式庫的名稱找有沒有現有的)
如果沒有的話(或者是自己製作的Library),就可以自己去寫spec
這邊有教學:Specs and the Specs Repo

參考資料

Posted on

AutoLayout介紹

投影片分享

過去的作法…

  1. 使⽤用frame和bounds去決定物件的位置和⼤小。
  2. 使用autosizing masks
    1. 設定當畫⾯面⼤大⼩小變動時,要固定 那些值(struts)。
    2. 在view的⼤大⼩小改變時,可以偵測 super view的⼤大⼩小改變去改變物 件的寬和⾼高的值(springs)。

AUTOLAYOUT和AUTORESIZING MASK的區別

Autoresizing Mask是AutoLayout的⼦子集。 AutoLayout更多的功能

    1. 指定任意兩個view的相對位置
    2. 可指定⾮非相等約束(⼤大於或者⼩小於等)
    3. 可以設定約束的優先級

WHAT IS AUTO LAYOUT

一種基於約束的,描述性的佈局系統。 Auto Layout Is a Constraint-Based, Descriptive Layout System.

      • 基於約束 – 以所謂相對位置的約束來定義的
      • 描述性 – 使⽤用接近⾃自然語⾔言的⽅方法來進⾏行描 述
      • 佈局系統 – 設計界⾯面的各個元素的位置。

使⽤用約束條件來描述佈局,view的frame會依據這 些約束來進⾏行計算。
Describe the layout with constraints, and frames are calculated automatically.

影片分享

Posted on

UITableView的小問題

紀錄一下今天開發我的APP時遇到的小問題,
因為要使用UITableView,發現UITableView放在UIView裡時,
若要使用static cells是不能直接使用的。

當我們要用UIView,裡面有放一些自己的東西,再加上一個Static cells的UITableView時,
會發現雖然在storyboard裡能夠正常的顯示表格的樣子,如下圖
螢幕快照 2013-12-11 下午6.28.52
但是當執行出來卻無法顯示已設定好的static cells,而會顯示為一片空白,如下圖
螢幕快照 2013-12-11 下午6.31.55
查問Google大神後,在這邊有看到文章有相關的建議,也就是要我們不要實作下面這三個方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
}

不過,我檢查過後,我並沒有設定這三種方法,但是還是一樣無法正常顯示定義好的static cells,
最後這篇文章解決了我的疑問:How do I put a UITableView into a UIView
原來,我們不能夠直接在UITableView裡直接使用static cells,
如果必需在static cells的畫面裡加上其他元素,則應該要在該位置放入Container,然後Container連接至UITableViewController畫面,
如下圖:
螢幕快照 2013-12-11 下午6.41.42

這樣子便能夠正常的顯示在storyboard裡設定的static cells的樣式了!
螢幕快照 2013-12-11 下午8.15.14
PS:這邊有Container View與母容器互動的範例文章與範例專案

Posted on

iOS6以上控制螢幕旋轉

一般的設定方式

//支援Xcode 4.5
- (NSUInteger) supportedInterfaceOrientations{
//僅正面
// return UIInterfaceOrientationMaskPortrait;

//支援縱向 (利用 | 設定多參數)
// return UIInterfaceOrientationMaskPortrait
// | UIInterfaceOrientationMaskPortraitUpsideDown;

//支援橫向
//UIInterfaceOrientationMaskLandscape: 支援按鈕在左、按鈕在右
// return UIInterfaceOrientationMaskLandscape;

//支援四個方向
return UIInterfaceOrientationMaskAll;

}

- (BOOL) shouldAutorotate {
return YES;
}

TabBarController設定方式

假使今天要控制所有畫面中,某些可支援旋轉,某些不行,
在有使用Navigation Controller和TabBarController的狀況時,
則需要這樣設定:

  1. 勾選支援畫面旋轉
    螢幕快照 2013-12-11 下午3.36.26
  2. 在AppDelegate加上這段程式碼
    - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
    {
        NSUInteger orientations = UIInterfaceOrientationMaskAll;
    
        if (self.window.rootViewController) {
            UIViewController* presented = [[(UINavigationController *)self.window.rootViewController viewControllers] lastObject];
            orientations = [presented supportedInterfaceOrientations];
        }
        return orientations;
    }
  3. 在tabBarController裡加上
    -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
        return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
    }
    
    -(NSUInteger)supportedInterfaceOrientations{
        if (self.selectedViewController)
            return [self.selectedViewController supportedInterfaceOrientations];
    
        return UIInterfaceOrientationMaskPortrait;
    }
    
    -(BOOL)shouldAutorotate{
        return [self.selectedViewController shouldAutorotate];
    }
  4. 支援旋轉的子畫面加上
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }
    
    - (BOOL)shouldAutorotate
    {
        return NO;
    }
    
    - (NSUInteger)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskPortrait;
    }
  5. 要支援旋轉的子畫面加上
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        return YES;
    }
    
    - (BOOL)shouldAutorotate
    {
        return YES;
    }
    
    - (NSInteger)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }

Navigation Controller設定方式

若要控制在Navigation Controller之下的單獨畫面是否支援旋轉,則需要在Controller裡加上這段

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return [self.visibleViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}

- (BOOL)shouldAutorotate {
    return [self.visibleViewController shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.visibleViewController supportedInterfaceOrientations];
}
Posted on

iOS app security - 分析和防範

講者 :Hokila
mail:hokila.jan@splashtop.com
blog:josihokila.blogspot.com
FB:fb.me/hokilaj

這是10/17分享的第一個講者,在分享有關app的安全上攻防的相關議題
因為這是我第一次參加cocoahead聚會,誤信了google map而迷路,遲了半小時入場,因此有部份內容沒有聽完整>”<
幸好後來找到Hokila很好心的預錄了當天的內容,在投影片最後一頁的QR code裡(有列在參考資料裡)。

ps:報名網頁的地址『台北市大安區敦化南路一段205號6樓600室』,打入google map會出現在忠孝復興站@@
說到這,當天我到會場時,有另一個漂亮女生也遲到,我就和她打招呼,結果她問我:妳也是用google map嗎?
嗚哇~所以不只我一個人這樣阿!(握手

今天Hokila講的內容大致如下:

  1. iOS app 資料結構
    在一個APP裡面大概會有幾個資料夾
    2013-10-29_105710
    MyApp.app裡面會存放一些APP會用到的照片、XAB、貼圖(PVR)或素材PNG、JPG等檔案(裡面的值無法修改)。
    Documents是建議存放永久性資料的地方,我們可以在裡面創建、修改、刪除要永久存放的檔案,如DB工具、抓影片放裡面,好處是只要plist裡宣告可以file share的話,他會可以用iTune打開來。PS: 這個資料夾會被iCloud自動備份。
    Library則是較為複雜的資料夾裡面一般會有下面這些資料夾

    1. Application Support:也是放一些設定檔的地方,它不會不見
    2. Cache:不會備份,存放可以再次從網路上下載的資料(每隔一段時間會清掉,何時清也不確定)
    3. Cookie:存放webView的Cookie,要注意的是這個cookie和safari瀏覽器的並不一樣。
    4. Preferences:放使用者相關設定檔的地方(NSUserDefault)

    tmp這個地方,重要的東西千萬不要放在裡面,因為只要每一次NSTemporaryDirectory重新restart時都會清掉,一般講者都是放download到一半的檔案,下次app開啟時再繼續download。

  2. 這邊也有提到可以去觀察對方的plist檔案來看他們的設定(如url schemes、document types )
  3. Console log:可以到此下載(http://support.apple.com/kb/DL1465)這個軟體。
    剛剛隨意的載下來玩了一下,這邊有使用說明(請按此
    感覺蠻好玩的,可以用它來看到app沒有埋好的log、framework⾃自⼰己帶的log、system notification、memory warming
    螢幕快照 2013-10-29 下午9.16.41
    因此,在用framework時或是寫code時,要注意是不是有丟出什麼log訊息。
    或是播影片時,把影片暫停、改全螢幕等等,有些會吐一些log出來,所以有時如果不知道別人怎麼做的,可以去觀察那個APP的log紀錄,把關鍵字丟到google查,或許可以查到一些端倪。
    另外在這邊也可以看到memory leak,不過只能知道有沒有memory leak的狀況,而無法知道是為什麼造成的。
  4. 接下來是資料儲存的部份,很多時候,我們會將資料儲存在Library>Preferences,但若我們打開資料夾,會可以看到plist檔,並且key和value值都是明碼,這是非常危險的。(例如fb會在裡面有使用者的資料)
    螢幕快照 2013-10-30 上午12.50.31
  5. 把資料放到keychain裡面,某app存的資料,另一個app也可以拿出來,並且有經過加密。(不過講者說可用keychain_dump解出裡面的table來)
  6. 另外可以使用charles來觀察網路狀況,這個工具我在開發flash也時常用到,非常好用。個人推薦map local和一個可以控制流量的功能
    螢幕快照 2013-10-30 上午12.55.33 用本機檔案去替代伺服器上的檔案
    螢幕快照 2013-10-30 上午12.55.51 控制下載流量
    那要如何讓CHARLES可以偵測到IOS上APP的HTTP request呢?
    首先我們要設定proxy,然後在iphone上設定代理伺服器
    2013-10-30_1114272013-10-30_111443
    在這邊講者也列出了其他類似的工具:
    ZAP (Mac Windows) Free:一個open source的工具
    Fiddler (Windows) Free
    Wire Shark (Mac Windows) Free:比較老牌的,講者覺得有點難用
  7. 因此我們在分析一個APP的時候,至少要分析下面幾個項目
    ● device screen
    ● console log
    ● plist、db裡面的狀況,一般會放在document裡面
    ● API request/response(送什麼出去、送什麼回來)
  8. 接下來是這次的講者們都很推薦的class-dump(在此下載)
    這個東西可以dump app的class,看看裡面宣告了什麼東西。
    但首先機子要先jb才可以連進去,用遠端登入的方式連進手機的位置
    螢幕快照 2013-10-30 上午1.16.42
    在手機上將專案抓下來
    螢幕快照 2013-10-30 上午1.18.41
    把它解壓縮
    螢幕快照 2013-10-30 上午1.20.07
    拿取版本,把它copy到
    螢幕快照 2013-10-30 上午1.22.40
    接下來就把app的資料拿出來看
    螢幕快照 2013-10-30 上午1.24.15
    便可以看到一些.h的檔案
    螢幕快照 2013-10-30 上午1.27.24
    如上圖,這時後大多的app都會有保護而導致dump失敗,這時候可以去一些ipa破解網站去下載app,通常從那些地方下載的都是已被破解,沒有保護的。否則就得自己去修改iap。
  9. IAP Free和LocalAppStore
    iGameGardin/八門神器=>搜尋記憶體位置,把該資料鎖定住,像是可以鎖值,像是血量什麼都不會變。
    FLEX:最可怕的一項功能,鎖定function回傳值,例如說-(BOOL)isTransactionSucess 一定回傳YES
  10. 在這邊講者提供了十項資安要注意的議題
    螢幕快照 2013-10-30 上午1.33.32
  11. 應對方法:
    • model的值和view的值不要一樣
    • 當model和server互相傳值時,不要太過相信彼此傳來的值,應還是要再做驗證
    • 加密的話,不要用hash

    範例:首先,不要使用get,如GET http://xxx.yyy/getUserData.php改成POST http://xxx.yyy/public
    讓所有的api有一個共通接口,然後用(string)call_file_name來判別要呼叫那個api,用(string)token來認證使用者的身份(此token會在幾分鐘後失效),然後設定數字編號來代表不同的status
    接下來就是使用ssl來傳遞資料,這樣就會有加密了
    或這樣還不放心,可以把傳的值的結構改成struct

  12. 在買東西時做double check。例:買東西時把剩的錢和數量回傳,用的時候可再確認是否有該產品,較好的時機點是在使用者有做任何互動時,再去檢查資料是否正確,而不要每隔幾分鐘就去檢查一次。
    存回db時做hash(不要直接把值原封不動存進去,因為很容易被搜尋修改)
  13. 加密方式選擇:md5、sha1已被破解
    AES-128和AES-256是最潮的(他加解密是同一個key),現在還無法被破解。
  14. public data可以不用加密,像折價券之類。但是private data一定要加密,例如密碼、交易記錄,就非常需要加密
  15. 不要只顧app端而已,要想到更多有關於server的東西,這樣才不會漏洞百出,更重要的是兩者的交互過程也要去思考,這樣整體的設計才會更完美。

參考資料:

  1. IAP攻防戰
  2. IAP攻防戰影片
  3. iOS app security
  4. 當天的影片
Posted on

XSpect簡介(一個AOP觀念實作的框架)

講者資料:小
投影片:XSpect
專案github位置:在此

這是10/17在cocoaHead聚會裡由講者所分享的一個他自己所寫的framework
因為他是在今日議程的最後一個講者,有些部份講的較為快速、簡短
有很多投影片也跳過去未說,在當下聽時只能大略聽到一個概念。

較引起我注意的地方,是他所提到的AOP的觀念與應用
也因為對他所說的AOP的觀念以及相關應對、程式設計方式感到蠻有趣的
這部份在會議結束後也花蠻多時間在研究該講者的code以及相關概念的研究

這是講者對AOP的解釋:

如果我要敘述 AOP 在幹嘛,或是說他的目的的話。我會說 AOP 是在用另一種方式去封裝變化,達到原本 OOP 做不到的事。這個變化就是 crosscutting concerns。
crosscutting concerns 就是為了一個邏輯,而散布在四處的程式碼。
AOP 就是要把他們全部包裝在一起。

下面的是其他網頁對於AOP的解釋

AOP全名Aspect-Oriented Programming,我們先來看看XXX-Oriented的意義,通常翻譯「XXX導向」,也就是以XXX為中心,例如中文中「客戶導向」就是以客 戶為中心,而「物件導向」(OOP:Object-Oriented Programming)就是以物件為中心的程式設計。

自然的,Aspect-Oriented Programming,就是「Aspect導向程式設計」,也就是以Aspect為中心的程式設計,但什麼是Aspect?中文直譯通常是「方面」,但這個名詞容易使人混淆。

牛津字典中的英英解釋對Aspect是:particular part or feature of sth being considerd.

所以Aspect在英文中不只有「方面」的意思,還有部份(part)的意思。中文中稱「就這個方面來說」,通常指的是「就這個角度來說」或 「就這個方向來說」,這個解釋不適用於AOP中的Aspect。如果英文中說from this aspect of sth,除了可以翻譯為上面兩句的意義之外,還可以翻作「就這個部份來說」。

以我們的前一個主題中的記錄(log)動作插入至HelloSpeaker物件的hello()中為例,我們說「就記錄這個部份」是不屬於 HelloSpeaker職責的,它被硬生生切入HelloSpeaker中,英文中我們可以說:The logging aspect of the “hello” method doesn’t belong to the job of HelloSpeaker.

所以以整個方法的執行流程來說,如果執行流程是縱向的,則記錄這個動作硬生生的「橫切」入其中,這個橫切入的部份我們就稱之為Aspect,它 是橫切關注點(crosscutting concern,一個concern可以像是權限檢查、事務等等)的模組化,將那些散落在物件中各處的程式碼聚集起來。

所以Aspect要用中文表達的話,適切一些的名詞該是「橫切面」或「切面」。AOP關注於Aspect,將這些Aspect視作中心進行設 計,使其中從職責被混淆的物件中分離出來,除了使原物件的職責更清楚之外,被分離出來的Aspect也可以設計的通用化,可運用於不同的場合。

然後這個是XSpect框架的架構圖
2013-10-28_135639

簡單來說,使用AOP的框架主要是為了把 crosscutting concerns 封裝在一起,提高程式模組化的程度。
以上圖來看,左邊的部份是原本的程式寫法,假如是一個儲存資料的函數如下,以原本的寫法來看,或許檢查值是否符合格式這部份的程式碼,在許多地方都可以用到,AOP主要便是去關心多個相似的流程的衡切面裡共通的部份,橫著去”跨越”多個程序裡使用的相同的功能。

log是一個標準的crosscutting concern範例,因為log的策略會牽動整個系統的每一個必需記錄的部份。
從橫向去記錄和登記所有classes和methods。

假如今天我們一個送交表單的程式碼,長的像是這樣:

function saveForm(){
...檢查欄位是否為空。
...檢查輸入的值是否符合格式。
[真正的儲存工作]
...後續相關操作(存LOG、紀錄操作等...)
...顯示儲存結果

}

我們可以看到,在這個函數裡面,只有[真正的儲存工作]是函數真正要做的事情,
剩的部份都是要處理商業需求的商業邏輯而寫的功能。這樣的程式碼撰寫方式,會造成寫出的程式只能專門用在單一工作,降低程式碼的重用性以及可維護性。

例如我們今天在程式的多個地方都需要使用LOG來紀錄所有的商務操作,
若今天這一項業務邏輯需要做修改或是移除,
那麼整個專案裡有加入這個LOG功能的部份,都需要同時去更新,這將會增加整個維護的成本。

因此,這個AOP的框架就是為了要將這些和業務相關的程式碼與原本的程式碼分開,
(商務邏輯,稱為Aspect,具體實作這些商務邏輯的程式碼則稱為Advice)
利用程式切入點Joinpoint(在應用程式執行時加入商務流程的點或時機稱之為Joinpoint),
並設定那些Aspect要用在那些Joinpoint裡,這個設定的定義稱為PointCut。

這邊的文章有AOP相關專有名詞的解釋:AOP觀念與術語,下圖則是講者大大的相關說明圖
2013-10-28_143641

這個框架可以讓商業邏輯獨立出物件之外,只需要設定Joinpoint便可以將之插入程式邏輯中。
如果對這個講者個框架有興趣的話,可至他的GITHUB上有更完整的使用手冊及範例: XSpect Tutorial

PS: 在當場講解時,有在場的大大提供了另一個很完整、並且功能與這一套框架相似的框架
叫作ReactiveCocoa,若對AOP有興趣者,也可以到這個網站去看看另一種方式的實作
https://github.com/ReactiveCocoa/ReactiveCocoa

 

相關資料

  1. XSpect, a lightweight library to make your code reusable and maintainable
  2. XSpect Tutorial
  3. AOP入門
  4. AOP觀念與術語
Posted on

iOS 7 轉換指南

參考資料:   iOS 7 UI Transition Guide關於iOS7,設計師需要瞭解的十件事
PDF檔下載:  TransitionGuide

IOS7的設計要點

  1. 依從:UI應要能很幫助使用者了解如何去操作、與內容互動,但不能搶走內容的鋒頭。它著重強調了怎樣使設計更好地支持內容,而不是反過來壓制內容。
  2. 清楚:字體在各種大小下都應清晰易讀,並應巧妙的利用圖示及裝飾,去突顯重點。
  3. 深度:巧妙運用手勢及視覺階層以利使用者了解、快速操作。

可以看看蘋果是怎樣在iOS7內置的日曆應用當中貫徹這一點的。新的日曆應用在界面上極大的簡化了,去除了一切不必要的設計元素,並使用了乾乾淨淨的白色背景,完全以內容為中心。
4196_130812093347_1

根據「依從」原則,apple建議開發者不要使用擬真。介面的設計應該是圍繞著內容而存在,不應奪走內容的光芒。因此,官方建議盡量避免使用3D質感的按鈕、漸變、光暈、陰影等擬物化的設計,因為他們認為這樣的設計會奪去使用者對內容的焦點。但雖然圖示要輕量化,最小點擊區域44px的規則依然不變,因為用戶不能點擊小於44px的界面元素。
關於移除擬物化風格的設計方式,可以參考iOS7內置的Game Center、日曆和Podcast應用。
4196_130812093503_1
另外,關於深度,iOS7最吸引人的設計之一就是系統界面當中由加速計驅動的3D效果。隨著設備在物理空間當中的移動,圖標與背景圖片會展示出視差效應。這是iOS7三大設計主題當中的第三點的典型示例:

深度:視覺外觀的層次以及逼真的動畫效果可以傳達出界面的活力,使界面更容易被理解,並提升用戶的愉悅度。
自然,蘋果也希望應用設計師們採用相同的方式,通過半透明及動畫效果來展示界面和元素之間的層級關係。這是應用設計理念的一次重大飛躍,而且隨著iOS7的發布和普及,這些效果很快就會成為用戶期望當中的一部分。
4196_130812093514_1
在iOS7設計規範當中,蘋果建議,在iOS7框架下重新設計應用的最佳方式,就是剝離你已有的界面,將應用的核心功能呈現出來,重新確認它們之間的相關性。
一旦你確認好功能方面的核心要素,就需要通過iOS7的設計主題將界面和交互模式重新構建起來。舉個簡單的例子,蘋果的指南針應用採用了最小化和功能優先的設計方式,通過重新設計過的、更注重細節的形式來展示關鍵信息。
4196_130812093524_1
iOS7的應用界面通過大量的呼吸空間來確保可讀性和易用性。在官方的設計規範中,蘋果明確指出,他們希望設計師們通過留白傳達出平靜和穩定的感覺,使應用看上去更加專注和高效。

檢視現在的APP

在升級APP至iOS7之前,先檢查幾個項目:

  1. 是否有開啟AutoLayout功能
    因為IOS7裡的文字大小可讓使用者任意調整,所以IOS7是一個好時機去開啟使用AutoLayout,因為這可以幫助開發者自動去調整物件的相對位置。也可以更容易的讓APP同時可支援IOS6及IOS7
  2. 是否也要支援IOS6?
    使用者往往喜歡很快的更新到最新版本。若是因為其他商業因素需要同時支援IOS6及IOS7,則需要做更多的調校
  3. 分辨自己的API是屬於那一種類型
    (1) 完全使用內建的UI介面:確認新的視覺效果是否是自己想要呈現給使用者的,並確定一些手勢等操作可正確運作
    (2) 完全使用自己的UI介面:檢查原有的設計是否可以滿足ios7的使用者要求,如果可以,那便可只改很少的東西。如果不行,則需要整個重新去思考、調整,來適應現有的架構。
    (3) 混合使用:是否需要做大改變取決於過去自定義元素是如何與原有元素結合,應確保功能及外表皆良好,並可與新的內建介面完美整合。

確定項目範圍

  • 必做

    1. 更新ICON:在IOS7裡iphone和ipod需要120*120的圖示、ipad則需要152*152的。在IOS7的ICON在設計上不應使用光圈及陰影。Icon的size需求如下表
      20130916210212734
    2. 讓APP的背景包含狀態列的地方
    3. 支援Retina以及調整APP設計讓他符合iphone5的設計及規範
  • 應做

    1. IOS7建議的畫面要是全螢幕的,因此要確認像狀態欄、導覽列等元素應要是半透明的。
    2. 對於ICON設計的規範有改變,希望是更簡單、並且風格與舊有的有顯著差異,應檢查bar button的圖示是否有符合新的規範
    3. 重新評估所有的按鈕是否真的需要邊框,盡可能的以佈局來讓使用者了解它是按鈕,而不是靠按鈕框
    4. 調整APP的佈局方式,要可以被動態相對位置調整。IOS7建議使用autoLayout,而不要直接用程式去設定物件的位置。某些佈局是應重新衡量及設定。
    5. 因很多物件的預設ui改變,檢查因預設ui改變而影響到的整體外觀。例如switches變窄、Progress Viewe更細、grouped tables分界方式改變。

      2013-10-07_181420
      grouped tables表現方式改變

      2013-10-07_181912
      switches變窄、選日期的元件樣式改變

      這邊有其他相關元件改變的一覽表:http://blogs.innovationm.com/ios6-to-ios7-user-interface-changes/

    6. [重要] IOS7的使用者可以自行設定要顯示的字體大小,應用程式應要可以因應使用者所選的字型大小去調整版型,如下圖:
      2013-10-07_183052
    7. 要預期使用者會開啟IOS7的從底部往上以及從上往下的中央控制功能,因此在APP裡要盡量避免這兩種操作模式(swipe up及top down)
    8. 重新考量一些陰影、漸層的使用:IOS7重視乾淨的介面(smooth)及以深度去強調內容(layered),更勝於一些擬真的設計,因此在設計上應重新考量這部份。
    9. 如果需要,將程式更新至IOS6版本以上,以確保可以使用autoLayout及storyboard。

讓程式支援IOS6

  1. 開啟assistant editor.
  2. 打開assistant editor的上面的選單列
  3. 上面的選單選擇要檢視的XIB或是storyboard
    2013-10-07_184024
  4. 然後選擇要預覽的版本
    2013-10-07_184443
Posted on

IOS6與IOS7的不同處整理

1. View會和狀態列重疊(壓在其之下)

2. 按鈕沒有邊框

3. tab bar變為半透明

螢幕快照 2013-10-06 下午7.18.46

4. 狀態列的顏色不會與導覽列分開

5. tintColor的影響顏色範圍改變,導覽列與tab bar的顏色不會受到tint Color的影響
ps:經測試,在ios7對於Segmented Control的tintColor,如果使用Global tint,一開始時的框線顏色不會變,但點過後顏色會改變。此時必須去各別設定該元件的tint color才會完整變更顏色

6. tab bar的圖示有按下狀態及非按下狀態螢幕快照 2013-10-06 下午7.24.37

7. 文字樣式明顯不同(即使是相同的設定)

8. 預設會是全螢幕畫面

9. 狀態列的高度不再會影響到整體畫面高度(整體畫面會在狀態列之下)

10. NSBundle Class改變Chia-Hsin分享
ios7的pathForResource (NSBundle Class)方法有調整過,
例,Local/iphone/page/01/01.jpg,
ios7之前用:
folder = Local
fileName = iphone/page/01/01.jpg

參數帶入function裡面是OK的~

但現在ios7要改成:
folder = Local/iphone/page/01
fileName = 01.jpg

11. iOS 7 的UITextField會預先給值,iOS 6 不會
if(![username.text isKindOfClass:[NSString Class]]){
NSLog(@”你沒有輸入帳號”);
}
因此這邊在ios7,若使用者沒有輸入任何文字,還是會成立。

12. viewdidload執行順序改變(建議:直接用@property帶值過去,然後在viewWillAppear裡去執行動作)
IOS6的code
ViewController *view1;
[[self navigationController] pushViewController:view1animated:TRUE];
[view1 method];

iOS7以前, 順序為view1被push出來以後會先執行viewdidload候才會執行[view1 method]
而iOS7後變成會先執行[view1 method]才執行viewdidload
目前在push前加viewController.view.hidden = NO;可以解決我現在的問題

Micheal老師建議:本來就是在 View Controller 裡面,去判斷的,不是在你寫的 pushViewController 那段程式碼。你寫的這段的地方,view controller 的 view還沒呈現,所以 view 也沒有值。之前會有值,只是剛好。我們不能決定什麼時候 ViewController 產生 view 的值,我們只知道,在 viewDidLoad 會有 view 的物件,在 viewDidAppear,view 的 frame 會被決定(如果是 storyboard 上的 view 的話)。 再來收到 delegate 回傳值要反應的 view 是 ViewController 的 view 為什不把接收者,設定為 ViewController。

附註:狀態列改變的相關議題

(一)讓上方留狀態列的位置

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] &gt;= 7) {
[application setStatusBarStyle:UIStatusBarStyleLightContent];
self.window.clipsToBounds =YES;
self.window.frame =  CGRectMake(0,20,self.window.frame.size.width,self.window.frame.size.height-20);</code></strong>
<strong><code>
self.window.bounds = CGRectMake(0, 20, self.window.frame.size.width, self.window.frame.size.height);
}
return YES;
}

(二)座標計算不將狀態列的地方算入

if([[[UIDevice currentDevice] systemVersion] floatValue] &gt;= 7.0)
{
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.modalPresentationCapturesStatusBarAppearance = NO;

self.navigationController.navigationBar.translucent = NO;
self.tabBarController.tabBar.translucent = NO;

self.navigationController.navigationBar.barTintColor =[UIColor grayColor];
self.tabBarController.tabBar.barTintColor =[UIColor grayColor];
}

(三)不顯示狀態列

  1. 至project setting=>General=>Deployment Info的地方勾選Hide during application launch
  2. 在ViewController加上
    -(BOOL) prefersStatusBarHidden{
    return YES;
    }

(四)改變狀態列顯示樣子(將字體顯示為白色)
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}

相關資料來源:

Posted on

UIWebView練習作業

作業目標:http://www.youtube.com/watch?v=azplcMbuoQ4&feature=youtu.be
練習檔案:homework0831

先介紹一下UIWebView

UIWebView的資料來源主要有三者:

  1. 本機上的HTML字串
  2. 本機上的資料 + MIME type
  3. 遠端的URL

這邊有中文的介紹:http://www.inside.com.tw/2010/03/14/cs193p-lecture-12

作業練習開始

首先我先拉出storyboard
螢幕快照 2013-09-04 下午11.30.34

然後我練習使用程式去產生UINavigationController,這是在AppDelegate.m裡

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

UINavigationController * navi = [UINavigationController new];
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
UIViewController * controller = [storyboard instantiateViewControllerWithIdentifier:@"pink"];
[navi pushViewController:controller animated:YES];
self.window.rootViewController = navi;
return YES;
}

然後ViewController.h裡在viewDidLoad時初始化一些頁面數值

- (void)viewDidLoad
{
[super viewDidLoad];
[self.inputURL setAdjustsFontSizeToFitWidth:YES];//設定下方網址列的大小會自動resize
self.title = @"填入網址";
self.inputURL.text = @"http://www.apple.com";
}

實作按鈕按下時的動作

- (IBAction)goWeb:(id)sender {
//取出webview的view
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
WebViewController * webView = [storyboard instantiateViewControllerWithIdentifier:@"webView"];
webView.urlString = self.inputURL.text;
webView.delegate = self;
[self.inputURL resignFirstResponder];//收起鍵盤
[self.navigationController pushViewController:webView animated:YES];
}

接下來做WebViewController.m裡的內容,
在這邊我弄了一個下載進度的物件叫作UIActivityIndicatorView
這部份首先要先在- (void)viewDidLoad裡初始化載入效果

//加入載入特效
aciv = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; colorView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];

[colorView setBackgroundColor:[UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.4]];

然後加上下面兩段程式碼

//載入特效
- (void)addLoading{
aciv.activityIndicatorViewStyle=UIActivityIndicatorViewStyleWhite; aciv.center=CGPointMake(self.view.frame.size.width/2,self.view.frame.size.height/2-10);
[self.view addSubview:colorView];
[aciv startAnimating];
[self.view addSubview:aciv];
}

- (void)removeLoading{
[colorView removeFromSuperview];
[aciv stopAnimating];
[aciv removeFromSuperview];
}

然後在實做UIWebView的Delegate去設定載入畫面
[UIApplication sharedApplication].networkActivityIndicatorVisible這個是在上方指示列的圈圈圖示

- (void)webViewDidStartLoad:(UIWebView *)webView
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self addLoading];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self removeLoading];
}

遇到問題(高手幫忙看3Q)

然後其實接下來這一點問題我弄了非常久~ 就是,在從上一頁傳來網址時,
也會執行到
-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
所以會必須去判斷現在是使用者點擊的link,或是是從上頁傳來的指令

為了這個需求,我用了幾個方式,但都有問題,包括現在的其實也有,但因為實在搞太久~
所以就先這樣@@等老師公布他的作法後再來補修正

我總用試了這些方式

  1. 使用一個BOOL去判別,只有第一次loading頁面時不做跳頁呼叫,然後當webViewDidFinishLoad時將BOOL改為NO
    問題點:有某些頁面會有js檔案,當頁面全部載入完成後,執行js後,就又會再第二次讀取網路資料,此時頁面就會莫名的突然回到第一頁然後顯示網址。(例如:我的部落格http://claire-chang.com)
  2. 使用NSConnection(http://stackoverflow.com/questions/9475768/loading-nsdata-into-a-uiwebview)
    這是一個異想天開(?)的想法,我想說,第一次loading時用NSConnection去讀html,然後用loadHTMLString:baseURL:來顯示在畫面上。結果html成功的載下來了,但是不知為何WebView呈現一片空白。
    在網路上查了一下,似乎是因為<scripe>標籤的關係(不確定)
  3. 現在版本的方式:偵測使用者點擊事件,當使用者點擊時,判斷現在是使用者點擊事件,下一次的網頁讀取便會跳頁。
    不過這種方式也有問題,同第一點,不過狀況會變成若頁面尚未載入完,使用者就點擊非link的地方時,js呼叫讀取網頁時,就會跳回第一頁,然後網址顯示為js呼叫的頁面網址= =

也是因為這樣子,我才會去製作下載畫面,想說在未loading完前不讓使用者按下WebView,
結果因為畫面會先loading完成、然後才自動因js而去呼叫另一個網址。
中間還是會有使用者可以點擊畫面的時間點。

我總覺得UIWebView應該有個東西是可以直接判別是否是使用者呼叫的,
觀察過dataDetectorTypes的值,卻似乎不是~orz
有知道的人麻煩幫解惑吧~3Q!
ps:若想試有js會在loading完成後再次loading的狀況,請把網址設為我的部落格http://claire-chang.com

因為我是用第三種方式,所以我對WebView增加了碰觸事件的偵聽
首先在- (void)viewDidLoad設定碰觸事件監聽

//設定碰觸事件
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(handleTapFrom:)];
longPress.minimumPressDuration = .001;
longPress.delegate = self;
[self.web addGestureRecognizer:longPress];
[self.web addGestureRecognizer:test];

然後要實作UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

上面這個一定要加,這樣子handleTapFrom:才會被呼叫到
然後當按到時將bool值改成YES

-(void)handleTapFrom :(UITapGestureRecognizer *)sender{
    NSLog(@"handleTapFrom:%d",sender.state);
    isClick = YES;
}

當讀取網頁且isClick是YES時就跳回第一頁

-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    if(isClick){
        [self.delegate userClickWebLink:request.URL.absoluteString];
        return NO;
    }
    return YES;
}

這時再回去ViewController.m增加接收的方法

-(void) userClickWebLink: (NSString *) url{
    [self.navigationController popViewControllerAnimated:YES];
    self.title = @"點選網址";
    self.webUrl.text = url;
}