CocoaPods - iOS 的Package Manager

Node.js有NPM(Node Package Management),Android有Apache Maven,當然iOS也要有! 那就是CocoaPods

安裝CocoaPods

$ [sudo] gem install cocoapods
$ pod setup

專案加入Podfile

在你的iOS project下新增一個Podfile (p.s. 不需附檔名)
檔案內容結構如下:

platform: ios , '6,0'
pod 'JSONKit'
pod 'AFNetworking'

安裝Package

開啟終端機回到專案目錄下

$ pod install

安裝成功後你會看到多一個.xcworkspace檔案

要用這個.xcworkspace打開才看的到所安裝的package

常用指令

  • 安裝 - $ pod install
  • 更新 - $ pod update
  • 列出所有package - $ pod list
  • 搜尋package - $ pod seatch {%package name%}

Reference

[iOS x Design Pattern] 觀察者模式 Observer Pattern ( II )

既上回介紹Notification與KVO後又發現了一個KVO的用法KVO Compliance

前言

事情是這樣的,在與Johnny分享KVO的pattern後,建議他可以用KVO觀察某個陣列的數量來被動更新畫面,這麼一來就可以不用每次都加一個物件就再多加一段updateView,當天晚上我就回家再實作了一次,發現竟然沒有接收到通知!於是我立馬展開調查(撞牆)

錯誤示範

@interface LYObserverViewController ()
@property (strong,nonatomic) NSMutableArray *dataSource;
@end

@implementation LYObserverViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.dataSource = [[NSMutableArray alloc] init];
    
    //觀察陣列dataSource的數量
    [self.dataSource addObserver:self forKeyPath:@"count"
                         options:NSKeyValueObservingOptionNew
                         context:@"dataSourceSizeChanged"];
    //陣列加入物件(應該會驅動observe)
    [self.dataSource addObject:@"first"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == @"dataSourceSizeChanged") {
        //印出結果
        NSLog(@"%@", change);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

顯然這其中一定有什麼誤會,不然沒道理不會動。

Key-Value Observing

後來查了蠻久都找不到原因observe NSMutableArray count,最後參考官方文件Key-Value Observing Programming Guide,文中指出只要用Key-value coding compliant方式實作的物件都會自動發出通知,並且提供兩種手動機制來觸發通知:

1. Automatic Change Notification (自動通知)

從下面這段程式碼可以大約猜出,如果要Observe NSMutableArray的話要透過物件,並且在透過mutableArrayValueForKey取得的實體才會支援自動通知。

// Call the accessor method.
[account setName:@"Savings"];
 
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
 
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
 
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];

2. Manual Change Notification (手動通知)

willChangeValueForKey:didChangeValueForKey給埋在Setter裡面,就可以當做發出通知的訊號,當然,你也可以故意做成setA然後Notify B,這邊的key你是可以自已定的。或者你自己用一個覆合式的通知,完全自定:)

- (void)setOpeningBalance:(double)theBalance {
    if (theBalance != _openingBalance) {
        [self willChangeValueForKey:@"openingBalance"];
        _openingBalance = theBalance;
        [self didChangeValueForKey:@"openingBalance"];
    }
}

解決方法

只要從mutableArrayValueForKey取的實體,在對它觀察就可以囉。

@implementation LYObserverViewController
- (void)viewDidLoad
{
    [super viewDidLoad];    
    self.dataSource = [[NSMutableArray alloc] init];

    //觀察陣列dataSource的數量
    [self addObserver:self forKeyPath:@"dataSource"
                         options:NSKeyValueObservingOptionNew
                         context:@"dataSourceSizeChanged"];
                         
    //從mutableArrayValueForKey取的實體
    //陣列加入物件(應該會驅動observe)
    NSMutableArray *array = [self mutableArrayValueForKey:@"dataSource"];
    [array addObject:@"first"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == @"dataSourceSizeChanged") {
        //印出結果
        NSLog(@"%@", change);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

後記

其實一開始只是想學好iOS的MVC架構,並且將Observer Pattern運用在上面(iOS好像本來就該這麼做),卻意外學到了怎麼用手動觸發通知,也算是不錯的經驗:)

[筆記] Waht's New in Objective-C and Foundation in iOS 7

Modules

在舊的架構下,iOS會幫你每個UI檔案(ex. UIViewController ...etc.) import UIkit.h這個檔案,很不幸的這個檔案有超過11,000行程式碼,這意味著你每個檔案都會至少大於11,000行,那不是肥慘了!!

Original solution: Pre-compiled headers

原有的解法是依靠pre-compiled把需要用的東西cache起來,如下面這段:

#import <Availability.h>
 
#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif
 
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#endif

New solution: Modules

每一次你用新的framework系統就自動幫你執行下面的動作:

  1. Add #import line to the file using the framework
  2. Write code that uses the framework.
  3. Compile.
  4. Watch as errors are spat out during linking.
  5. Remember that you forgot to link the framework.
  6. Add the linking of the framework to the project build phase.
  7. Compile again.

如何使用


除此之外,Module還自動幫你把#import轉成@import

BUT! Modules 並不支援任何第三方frameworks,所以沒事暫時不要啟用這個功能吧。

New Foundations

NSArray

NSArray *array = @[@"A", @"B", @"C"];
NSString *firstString = [array firstObject];
NSString *lastString = [array lastObject];

NSTimer

大意是,藉由設定tolerance改善task群組的方式,改進iOS對NSTimer的效率。

- (NSTimeInterval)tolerance;
- (void)setTolerance:(NSTimeInterval)tolerance;

NSProgress

假設你有10個任務,要每完成一個就回報給Progress更新,也許你會用Key value Observing (KVO) 去觀察 NSProgress 的實體,然後通知progress bar更新,更新prgress bar的進度與文字。

Reporting progress

+ (NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;
NSArray *array = /* ... */;
 
NSProgress *progress = 
    [NSProgress progressWithTotalUnitCount:array.count];
 
[array enumerateObjectsUsingBlock:
    ^(id obj, NSUInteger idx, BOOL *stop) {
        // Perform an expensive operation on obj
        progress.completedUnitCount = idx;
    }];

Receiving progress updates

@property (readonly) double fractionCompleted;

當fractionCompleted從0變成1的時候,表示所有任務都已經完成,這非常適合用KVO去觀察這個property:

[_progress addObserver:self 
            forKeyPath:@"fractionCompleted" 
               options:NSKeyValueObservingOptionNew 
               context:NULL];

接著, override通知的method

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context
{
    if (object == _progress) {
        // Handle new fractionCompleted value
        return;
    }
 
    // Always call super, incase it uses KVO also
    [super observeValueForKeyPath:keyPath 
                         ofObject:object 
                           change:change 
                          context:context];
}

別忘了移除KVO免得造成crash

[_progress removeObserver:self 
               forKeyPath:@"fractionCompleted" 
                  context:NULL];

後記

有些東西還是看不太懂阿(汗),不過還是硬著頭皮硬翻,如有錯誤請大大門指正,感激不盡。

Refernce

設計開發工具(不定期更新)

這裡會放些關於App開發的其他工具

Prototype


Moqups - https://moqups.com

設計


LOGO

IICNS - Icon大集合
Photoshop templates to preview iPad icon designs - 預覽Icon 在iPad上面的樣子
appicontemplate

Icon

Ico Moon - Icon font

顏色 Color

Color Tools
字型與背景色演算法
HSL picker - HSL顏色選擇器,可以看兩顏色重疊效果。
Adobe KULER - Adobe 配色選擇器
Colour Lovers - 配色參考網站

切圖

Cut&Slice me - 切圖神器
icon slayer - 把做好的Logo一次切出不同 size 給 iOS+Android

[iOS x Design Pattern] 觀察者模式 Observer Pattern ( I )

無論在任何程式語言中,觀察者模式都是使用率最高的一種設計模式,在此模式中會有一個主題(subject)物件讓其他物件可以訂閱,而其他的訂閱者就稱為觀察者(Observer),當主題有更新資料時,會主動通知所有的訂閱者,因此訂閱者不需要在費心處理資料有沒有更新這回事。

定義一對多的物件依存關係,讓物件狀態一有變動,就自動通知其他相依物件做該做的更新動作。--Design patterns : Elements of Reusable Object-Orientied Software

Wiki - Observer pattern

問題

在一般生活中也有顯而易見的觀察者模式,

你習慣看某個Blog,但這個作者很有個性,不是固定時間更新Blog,
有的時候一天發三篇文章,有的時候又一個月才發一篇。

你總不可能三天兩頭一直去check這個Blog,這樣太浪費時間了。

這時候你就會想,要是有個東西可以在這個Blog更新的時候通知我,那該有多好。

於是RSS就是這個問題的最佳解決方案,RSS協定讓User可以藉由RSS reader自動檢查這個Blog,並且一有更新就通知你。

iOS的觀察者模式

在iOS當中也有這樣的問題存在,於是官方實作了兩項有解決方案分別是NotificationKVO

[Notification]

iOS提供一個NotifivationCenter,你可以在任何一個地方去訂閱發送通知

  1. 訂閱:告訴NotificationCenter,我要訂閱

    [[NSNotificationCenter defaultCenter] addObserver: self
    selector: @selector(blogUpdated)
    name: @"部落格更新"
    object: nil];
    
  2. 定義Callbak:收到更新後我要做什麼事情

    - (void)blogUpdated{
        NSLog(@"部落格更新了,現在就去看");
    }
    
  3. 更新: 在任何一處都可以更新並通知User

    - (void)didWhiteNewBlog
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"部落格更新" object:self];
    }
    

Notification的優點是,訂閱者不用知道主題(subject)的實體物件在哪裡,只管聆聽某一個name,有點像網路協定中,我們講好我們用某某port來做溝通,subject只管往那個port送資料,而訂閱者只管去那個port收資料。
而這個方法的缺點是,主題(subject)與觀察者(Observer)必須事先協議好溝通的key,如果兩端都是同一個人實作那問題還小,因為你自己定key,假如你沒有主題(subject)的主控權,又要如何訂閱呢? 這就要請到下一位KVO大大了。

[Key-Value-Observer (KVO)]

KVO的實作比較困難一點點,但帶來的好處就是被訂閱的主題(subject)完全無感,就好像你在監聽他一樣(不過完全合法),只要該物件的property修改,就會馬上通知你。

參考Apple官方的KVO Programming Guide,其中指出KVO常用於model and controller layer尤其是從controller去綁定(觀察)model

controller訂閱model

model <- controller

iOS把KVO實作在基礎類別NSObject,所以你可以任意訂閱任一物件,你就是老大不用擔心被告違法(監聽),接著告訴大家該怎麼做:

  1. 了解要訂閱的物件
//Subject.h
@interface Subject : NSObject
@property (strong, nonatomic) NSString *lastBlogPostDate;
@end

我們要訂閱subject物件中lastBlogsPostDate這個property,在他更新部落格文章時,接收第一手的資訊。

  1. 訂閱與接收通知
//Observer.m
#import "Subject.h"

//訂閱
- (void)subscribeSubject:(Subject *)subject
{
    /* 向subject物件增加一個觀察者(self),
  並且我要觀察'lastBlogPostDate'這個屬性,
  options: 當該值更新時把新舊值都傳給我(放在dictionary中)
  */
    [subject addObserver:self forKeyPath:@"lastBlogPostDate" 
  options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
    context:nil];
}

//接收通知
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:@"lastBlogPostDate"])
    {
        NSLog(@"%@",change);
    }
}

其中option的部份有下面幾項:

  • NSKeyValueObservingOptionNew : 通知時要告訴我新的值
  • NSKeyValueObservingOptionOld : 通知時要告訴我舊的值
  • NSKeyValueObservingOptionInitial : 訂閱時會先通知一次
  • NSKeyValueObservingOptionPrior : 變更數值前會先通知一次,變更數值後再通知一次(通知兩次)

KVO的優點是可以更改subject的情況下訂閱(也意味著被訂閱的subject完全不用理會通知observer這件事情),缺點是observer必須要知道subject的實體位置(在訂閱時需要該本體)。

KVO與notification各有好處,就看大家怎麼去用囉:)

後記

為了寫文章反而學到更多,真是不錯,缺點是每天都在熬夜阿XD

[iOS x Design Pattern] ViewController之間傳遞參數 Delegate Pattern

在iOS中頁面的參數傳遞有幾種作法,最常碰到的是相鄰的兩個頁面傳遞,其中又以把參數往下一頁傳遞的狀況較多,比較少的情況是需要把參數傳遞給前一頁

題外話,一個月前我還什麼都不會的時候,跑去參加iOS Bootcamp 2013那時候真的搞不懂iOS的delegate,本來還以為跟匿名函式一樣:P 但是完全不是,許多用字也與過去的不同,像是interface在iOS中是用Protocol,總之,天下武功大逕相同,殊途同歸,只要了解語言特性去習慣它,勿故步自封便能無招勝有招。

言歸正傳

情境

ViewControllerA -> ViewControllerB

[傳遞給下一頁]

Passing Data Forward

ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
viewControllerB.isSomethingEnabled = YES;
[self pushViewController:viewControllerB animated:YES];

Passing Data Forward using Segue's

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.isSomethingEnabled = YES;
    }
}

tips:你不能直接把值設定給UI物件,因為在prepare segue階段該物件尚未產生實體,會導致你傳資料給一個不存在的物件。

[傳遞給前一頁]

透過Protocol限制delegate的物件

  1. 建立Protocol

ViewControllerB.h

@protocol ViewControllerBDelegate <NSObject>
- (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
@end
@interface ViewControllerB : UIViewController
@property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
@end

ViewControllerB.m

#import "ViewControllerA.h"
//When you need viewControllerA
- (void) setDataBack{
  [self.delegate didFinishEnteringItem:YES];
}

set delegate
加上介面ViewControllerBDelegate
ViewControllerA.h

@interface ViewControllerA : UIViewController <ViewControllerBDelegate>

ViewControllerA.m

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.delegate = self;
    }
}

更簡單的方式,把ViewControllerA當成物件傳進來

ViewControllerB.h

@property (nonatomic, strong) id previousViewController;

ViewControllerB.m

#import "ViewControllerA.h"
//When you need viewControllerA
- (void) setDataBack{
  ViewControllerA *controllerA = (ViewControllerA)self.previousViewController;
  [controllerA setText:@"set somthing data"];
}

別忘了在前一頁的時候要把自己傳過去

ViewControllerA.m

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.previousViewController = self;
    }
}

這樣也就不用寫什麼Protocol,就懶人來說是比較容易的方式XD

結論

比較後面這兩種方式,一種是正規的Delegate Pattern,一種是偷吃步懶人寫法,前者有較嚴謹的規範,你可以定義好前一頁該做什麼事情並且不用管前一頁是誰(即便是ViewControllerC),只要是符合Protocol的人都能當他的前一頁; 而後面偷吃步的作法,好處是可以偷懶不用寫Protocol,若你今天要回傳的資料很多,寫起來也是挺麻煩的,但如果你是跟別人合作,或是你的ViewControllerB是好幾頁的子頁面,就比較建議用正規寫法囉。

另外值得一提的是,如果你要傳遞的頁面距離該頁面有些距離,比如你要傳遞給你的曾祖父資訊,那就要動用到觀察者模式Observer pattern了,會在下一篇介紹給大家。

refernce:Passing Data between View Controllers

有限狀態機與狀態模式

前言

回想當初碩班學習狀態機是一段蠻不錯的回憶,常常為了解一個難解的狀態機而在書桌前熬夜,只為了畫出最精簡的狀態機圖,然後隔天可以跟同學開讀書會討論並分享自己的解法。
最近在趕一個專案,發現自己在開發沒有什麼方法可言,想到了就寫,走了非常多冤枉路,其實如果我能夠有先預留一些時間畫出狀態圖,就可以避免目前物件失控的現象並設計出簡潔的物件。

有限狀態機(Finite state machine)


一個完整的狀態機有幾個元素:

  1. 起始(start->): 並指向一個起始狀態
  2. 狀態: 每個圈圈都是一個狀態
  3. 轉換: 箭頭表示並標示轉換的動作
  4. 結束: 雙圈圈代表可以結束的狀態

繪製狀態機圖可以幫助你精鍊你的流程,把流程最佳化。

狀態模式(State pattern)

我們常常會用列舉Enum放在物件中當做狀態,並且用if else去判斷,好一點則用Switch case

Enum USER_STATE{
  GUEST,
  MEMBER
}

void someMethod(){
  if(User.State == USER_STATE.GEST){
    //Do somthing when user is gest.
  }else if(User.State == USER_STATE.MEMBER){
    //Do somthing when user is member.
  }
}

但是總不能在每個method中都加入狀態的判斷(ex. 一堆if else, swtich case),這樣的作法顯然不太聰明,在這個設計模式中用狀態抽象成為物件的方式來簡化並分隔出每個狀態,而每個狀態相對應該做的事情就在各class中自己解決。
懶得舉例了,大家參考下面連結吧:)

[Design Pattern] 狀態模式(State Pattern)

XCode SVN 版本控管設定

UserInterfaceState.xcuserstate 檔案會一直更新,*.a靜態檔案也可以不用加到SVN中,因此可以修改該Repostory中的config, 並忽略上述檔案而不commit。

  1. 開啟config檔案: 開啟terminal輸入
    vi ~/.subversion/config
    
  2. 搜尋[miscellany]
    /miscellany
    
  3. global-ignores下一行新增

    global-ignores = *~ #*# .#* .*.swp .DS_Store .xcuserstate
    
  4. 存檔, 關閉

    :wq
    

這次專案是用SVN來做版本控管,下次如果用git就把它加入.gitignore就搞定囉。

Reference

使用UIAlert, UIActionSheet 與Modal View的時機

UIAlert

給使用者非常重要資訊會影響使用者的後續操作,通常用於顯示非預期狀況,告訴使用者目前遇到的問題或改變目前狀態,需要使用者做決定。

ActionSheet

給使用者在目前當下正進行的動作予以額外的選擇,使用者學習去預期它的出現(ex.刪除所有的來電記錄),或是某個動作可以用不同的方式進行(ex .讓使用者在多個目的地中選擇其中一個)。

Modal View

可以視其圍子任務,必須完成當下這個子任務才能回到你原本的狀態下繼續進行(ex. 登入: 可以在需要權限時在開支線任務給User)

Reference
iphone - When to use a UIAlertView vs. UIActionSheet - Stack Overflow