高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

HHKB Pro2 鍵盤使用心得

image

我每天的工作是敲打鍵盤之類的文書工作,所以幫自己找一隻順手的鍵盤也是剛剛好的藉口。

對於 HHKB 鍵盤的傳說,很早之前就有所聽聞了。最知名的應該就是 Richard Stallman 用 HHKB 鍵盤搭配百元電腦的那張照片(鍵盤本身比電腦本體還貴!)。我知道它是神器,但我等凡人即使拿了神器還是凡人,不會變成大神。 而且當初看到鍵盤上沒有方向鍵、沒有 CTRL 鍵跟 F1 ~ F12 鍵,在台灣也沒什麼機會試打看看手感,最重要的是,它的價錢不便宜,所以大多只是看看而已。

直到前不久去日本參加活動,有在電腦賣場試打了一下,心一橫的也敗了一隻回來,我挑的是白色無刻的版本。

RubyKaigi 2013

本篇文章已刊載於 OpenFoundry 電子報自由專欄,刊登之內容由專業的 OpenFoundry 團隊潤稿,此篇為原文。

今年日本的 RubyKaigi 2013 在 5/29(四)、5/30(五)、6/1(六)舉辦,議程是雙軌同時進行,共計有超過五十場演講及十場 Lightning Talk,並包括三場主題演講 (Keynote),跟上一屆一樣也是三天。這次有超過 500 位 Ruby 開發者參加,其中有超過 100 位是從國外來的,這樣的規模短期內在台灣可能還是沒辦法達到(全台灣真的有在用 Ruby 工作的開發者加起來總數說不定都還不到 500 人)。 image photo by @eddiekao

Code Reading @ RubyKaigi 2013

很高興這次能有機會到日本參加 RubyKaigi 2013,我分享的題目是:"Code Reading, Learning More about Ruby by Reading Ruby Source Code“,算是個滿冷門的題目,主要是分享我自己從閱讀 Ruby 原始程式碼中學習到的一些經驗。不過當天還有不少朋友來捧場,讓場子看起來不會太冷清,真是太意外了(其實我也搞不清楚到底有多少人,我當時已經緊張到沒辦法算數了)。
Really glad to go to Japan and join the RubyKaigi 2013, the topic of my talk is ”Code Reading, Learning More about Ruby by Reading Ruby Source Code“, which I don’t think it’s a popular topic, and it’s about my experience while reading Ruby source code. Thanks to those friends who are willing to come to my talk.(Actually I was too nervous to count how many people already)

在進入主題之前,我問大家有多少人曾經來過台灣,結果在場有十來個人舉手,就以現場人數的比例來說不算少。
Before I jump into my topic, I asked everyone how many of you have been to Taiwan before, to my surprise, there’re more than 15 people raised their hand, which is about 20% of the attendees in this hall(I think).

image View on Speaker Deck | Download PDF

雖然之前有一些些上台演講的經驗,但這是第一次出國演講,而且還是全程用英文演講,我知道有很多的大神甚至 Ruby Committer 就坐在台下,所以我相當緊張。我那個「上台的前一天不會睡」的魔咒果然還是依舊存在,所以演講當天凌晨四點,我拿著筆電到飯店的大廳練習對著空氣講,練到早上七點,但還是覺得不夠穩。
Although I have a some experience about having public speech, but it’s my first time to have a presentation abroad and do my speech in English, and I also know there might be lots of awesome rubyists sitting there, including some Ruby core committer, so I really feel nervous. And I have a curse that “I won’t sleep if I have a public presentation on the next day”, so I practiced in the lobby of the hotel from 4 A.M. to 7 A.M., but still not stable enough.

我的演講廳是小間的,所以壓力有稍微小一點點。
My presentation hall is the smaller one, so my pressure is also slightly smaller, too.

為了怕現場出包,所以在 code review 展示的部份我是用預錄的,不過事後想想好像直接現場操作比較容易掌控時間
I think I might make some mistakes while presenting, so I recorded the code review demo in advance, but I think the real live demo might be easier for me to control time.

上午的演講結束後,下午發生的小插曲讓我抖了好大一下。我在演講內容裡有提到一些在 Ruby 的原始碼裡面有一些小地方的命名不太優,當下大家笑得很開心,雖然娛樂效果達到了,但我其實也有點擔心會不會有什麼不好的副作用。結果上午的講場結束,下午就看到這則
After finishing my talk, there’s something surprised me. In my speech, I mentioned some interesting naming in the Ruby source code just for fun. I know it might be entertaining but still a little worried about if there would be any bad consequence. Then in the afternoon, I saw this tweet:

image

哇!! 我有嚇到的感覺了,不過因為我個人覺得這樣好像有些失禮,所以當晚的 Official Party,我就親自去跟 @nobu 說聲不好意思,希望沒有造成他的困擾。其實我也是想趁這個機會認識一下只有在網路上才能看得到的傳說大神,而且他好像一點也不在意。
Wow! That’s really really surprised me!! and I don’t know if this would cause any trouble for committer, so I went to @nobu and said sorry to him on the official party in the night, and hope didn’t cause extra trouble for him. In fact, I went to him also want to know him by this chance, and actually he didn’t mind at all.

第三天的議程,我本來就打算要來聽 Jim Gay 的主題(他是 Clean Ruby 一書的作者 ),沒想到坐下來沒多久竟然看到自己的名字跟前一天講的東西出現在投影片上:
In the last day, I was planning to attend Jim Gay’s talk, whihc is the author of the book “Clean Ruby”. After sitting in the hall, I suddenly found my name was quoted in the slide:

image

再度有被嚇到的感覺。
Yes, Supprised me again.

活動結束後,RubyKaigi 2013 的頭目角谷也推了一篇
the Organizing Director of RubyKaigi 2013 Shintaro Kakutani also tweeted this:

image

再次看到自己的名字被提到,有些驚訝也有些不好意思,不過這個"Conference-Driven Development(CDD)“ 聽起來好像不錯,而且我喜歡 "RubyKaigi made Ruby Better",如果有朝一日我有能力,我也希望可以多貢獻一些心力。
I feel surprised again and also little embarrassing, but the "Conference-Driven Development(CDD)” sounds workable, and I love the “RubyKaigi made Ruby Better”, I hope I can do more contributions to Ruby and this community someday if possible.

感謝高井さん(@takai)幫我拍的照片,讓我在 Rubyist 時計上也可以有一張漂亮的照片。
Thanks to Naoto Takai(@takai) to take a photo for me so that I can have a nice picture on the Rubyist Tokei.

image photoed by @takai

這趟日本之行有學習到不少東西,收獲很多,會在接下來的幾篇文章介紹。最後,有講的不好的地方,還請多多指教。
Anyway, I learned a lot on this trip to Japan, and I’ll write them down in next blog posts. At last, if there’s any bad or something wrong about my speech, please feel free to comment.

Thank you all, RubyKaigi team, you’re all AWESOME!

HHKB Pro 2 鍵盤入手

image

其實沒什麼,只是敗家紀錄一下,就前幾天去日本參加 RubyKaigi 時候順便帶回來的。而且還特別挑了個無刻的回來:

image

鍵盤很小一個,只有 60 顆鍵帽;沒有上下左右鍵,沒有 Page UpPage Down 鍵;Ctrl 鍵的位置不一樣(跟日本鍵盤一樣,在一般鍵盤的 Caps Lock 的位置),ESC 鍵在數字 1 的左手邊;沒有最上面一排的 F1 ~ F12 等種種跟一般鍵盤的差異,我也不太懂為什麼我要練這種折磨人的鍵盤,因為練這個可能得把之前的習慣整個砍掉重練,跟練 Vim 有點像,一但只要習慣它,就沒辦法習慣一般鍵盤了。

不過我很喜歡它打起來的手感,所以.. 喜歡就不需要太多理由了,還在習慣中 :)

一個月後的使用心得

Delegation in Objective-C

在開發 iOS app 的過程中,Delegation(委任) 幾乎是避不掉的東西,例如在 ViewController 裡處理 UITableView 的時候,大家一定都寫過像這樣的程式碼:

1
self.tableView.delegate = self

坊間的書本大多會教要這樣寫,但不一定有說明為什麼要這麼寫。其實 delegation 的概念並不困難,只是要用程式碼來表達的時候,對新手來說可能就需要多一點的想像力了。

Delegation,中文翻譯成「委任」,委任兩字講的好聽是拜託別人做事,講白一點就是自己不想做或不會做,所以外包出去叫別人做。

但是,就算是要叫別人做也不能隨便找一個路人就可以,舉個例子,我想要把「撰寫 Ruby 程式」這件事委任給別人,要有能力處理這份工作的人至少得知道 Ruby 程式怎麼寫。

那要判斷對方是否有「能力」來接受我的委任,就是問這個被委任的人是否有符合(Conform)我訂的條件(Protocol),然後這個條件就跟面試新人一樣,某些技能是必須的(Required),但其它條件是非必須的(Optional)。

一樣以 UITableView 來舉個例子,如果我希望某個 ViewController 可以有能力接受 TableView 的委任,假設這個 UITableView 是設定成一個叫做 tableView 的 IBOutlet 的話,我們可以直接在 ViewDidLoad 的地方這樣寫:

1
2
3
4
5
- (void)viewDidLoad
{
    [super viewDidLoad];
    _tableView.delegate = self;
}

或是直接在 Storyboard 用拉的也行:

image

這樣就完成了 delegation 的設定啦!

但是,剛剛才講到,我怎麼知道這個 ViewController 有能力可以完成我想要委任的工作? 其實就是讓這個 ViewController 實作 UITableViewDelegate 這個 Protocol 就行了。

1
2
3
@interface ViewController()<UITableViewDelegate>

@end

關於 Protocol,大家可以參考之前寫的這篇

但是,UITableViewDelegate 這個 protocol 到底定義了哪些東西? 讓我們按著鍵盤的 Cmd 鍵加上滑鼠點擊 UITableViewDelegate 就可以連過去看一下它的定義(部份程式碼省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional

// ..省略

// Variable height support

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

// Section header & footer information. Views are preferred over title should you decide to provide both

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;

// ..省略

@end

UITableViewDelegate 這個 protocol 定義在 UIKit 的 UITableView.h 裡面,而且全部都是 optional 的,也就是說就算不實作任何這個 protocol 裡的方法也不會怎麼樣,它還是有自己預設的行為。

但如果你想要做一些 UI 客製化,例如想要動態的調整每個 cell 的高度,你就可以在這個 ViewController,也就是這個 tableview 所「委任」的對象,覆寫這個 method:

1
2
3
4
5
#pragma mark - UITableView delegate
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 100.0f;
}

上面的實作這個方法,你可以想像成以下情境:

tableview 問 view controller 說:「喂,我每個 cell 要設定多高啊?」

view controller 回答:「就 100 個點(point)吧。」

再舉個例子,如果你想要客製化這個 tableview 的 section header 或 footer,就是覆寫這兩個方法:

1
2
3
4
5
6
7
8
9
- (UIView *) tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    // 內容省略
}

- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // 內容省略
}

以此類推。

UITableView 所需要的一些行為或是外觀描述,都是透過 delegate 的方式,請被委任的傢伙(通常是那個 tableview 所在的 view controller)告知。

Delegate 怎麼寫?

上面大概說明了怎麼用別人寫好的 delegate,如果也想自己仿著做一個,該怎麼做呢?

我這邊來做個簡單的範例,大概就是希望某個 UIView 在進行動畫完成之後透過 delegate 發送一個「喂,我已經搞定囉」的通知,並同時也回傳狀態。

我開了一個新的 Xcode 專案,選了 Single View Application,裡面應該只有預設的 ViewController.hViewController.m,接著我新增了一個繼承自 UIView 的類別,叫做 AnimationSquareView

image

並且在上面加了一個叫做 run 的 public method:

1
2
3
4
5
6
//== 檔案:AnimationSquareView.h ==
#import <UIKit/UIKit.h>

@interface AnimationSquareView : UIView
- (void) run;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// == 檔案:AnimationSquareView.m ==
#import "AnimationSquareView.h"

@implementation AnimationSquareView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor blackColor];
    }
    return self;
}

- (void) run
{
    [UIView animateWithDuration:3.0 animations:^{
        CGRect newFrame = CGRectMake(self.frame.origin.x + 150,
                                     self.frame.origin.y,
                                     self.frame.size.width,
                                     self.frame.size.height);
        self.frame = newFrame;
        self.alpha = 0.5;
    } completion:^(BOOL finished) {
        // do something later..
    }];
}

@end

這個 run 做的事情就是在 3 秒鐘完成一個簡單的動畫(其實就是讓它自己往右邊移動 150 個點,並且透明度變 50%)。

ViewController 的部份程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// == 檔案:ViewController.m ==

#import "ViewController.h"
#import "AnimationSquareView.h"

@interface ViewController()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    AnimationSquareView* squareView = [[AnimationSquareView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    [self.view addSubview:squareView];
    [squareView run];
}

@end

執行一下,應該會看到一個黑色的方塊緩緩的往右程動,過三秒鐘之後就停下來了:

image

加上 delegate

如果,我想要讓這個 ViewController 知道這個方塊什麼時候做完它的動畫效果,該怎麼做?

一種方法是可以在那個動畫 block 的 completion 區塊利用 NSNotification Center 並且在 ViewController 搭配 KVO(Key-Value Observing) 來實作,不過我們這邊選擇用 delegation。首先,我先定義一個叫做 AnimationSquareViewDelegate 的 protocol,並且在原來的 AnimationSquareview 類別裡加了一個名為 delegate 的 property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// == 檔案:AnimationSquare.m ==
#import <UIKit/UIKit.h>

@class AnimationSquareView;
@protocol AnimationSquareViewDelegate <NSObject>

@optional
- (void) animationSquareView:(AnimationSquareView *) squareView didFinishAnimationWithStatus:(NSDictionary *)status;

@end

@interface AnimationSquareView : UIView

@property (nonatomic, weak) id <AnimationSquareViewDelegate> delegate;
- (void) run;

@end

這裡可能有些需要解釋的:

  1. delegate 這個 property 並不一定要命名為 delegate,只是慣例上通常會使用 delegate 這個名字,你硬是要把它叫 abc 也行,只是在待會我們要設定 delegate 的時候就要用 abc 了。另外,protocol 的定義也不一定要放在同一個 header 檔裡,也可以另外獨立的檔案,不過因為這個 delegate 的行為跟這個類別有關,我通常也會把它寫在同一個檔案裡面。

  2. 上面第 4 行,為什麼需要那個 @class AnimationSquareView;? 因為在定義 protocol 的當下,這個類別還沒被定義出來,所以 @class 是告訴編譯器說:「我這個 AnimationSquareView 是一個類別,反正我待會就會寫,你先別管這麼多」。

  3. Protocol 裡的方法有分兩種,一種是規定必須實作的(required),另一種則是不一定要實作的(optional),在這個範例裡,我認為完成動畫的這個方法並不是一個一定要實作的方法,所以我把 animationSquareView:didFinishAnimationWithStatus: 它設定成 optional (如果沒有特別標記,就是 required 的)。

  4. Protocol 的名字 AnimationSquareViewDelegate 也不一定要叫這個名字,只是在後面加上個 Delegate 似乎也是慣例。

再來,我們改寫一下剛剛那段動畫的 completion 區塊的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// == 檔案:AnimationSquare.m ==
- (void) run
{
    [UIView animateWithDuration:3.0 animations:^{
        CGRect newFrame = CGRectMake(self.frame.origin.x + 150,
                                     self.frame.origin.y,
                                     self.frame.size.width,
                                     self.frame.size.height);
        self.frame = newFrame;
        self.alpha = 0.5;
    } completion:^(BOOL finished) {
        if ([_delegate respondsToSelector:@selector(animationSquareView:didFinishAnimationWithStatus:)])
        {
            NSDictionary* currentStatus = @{@"status": @"finished"};
            [_delegate animationSquareView:self didFinishAnimationWithStatus:currentStatus];
        }
    }];
}

這裡需要解釋的就應該就只有那段 respondsToSelector 了。這段的意思是問自己本身的這個 delegate 是不是有實作 animationSquareView:didFinishAnimationWithStatus: 這個方法,如果有的話,就呼叫它,並且把自己(self)以及狀態(一個NSDictionary)傳入。

設定 delegate

再來,回到 ViewController 裡,準備設定我們剛剛寫好的 protocol 跟 delegate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// == 檔案:ViewController.m ==

#import "ViewController.h"
#import "AnimationSquareView.h"

@interface ViewController()<AnimationSquareViewDelegate>

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    AnimationSquareView* squareView = [[AnimationSquareView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    [self.view addSubview:squareView];
    squareView.delegate = self;
    [squareView run];
}

#pragma mark - AnimationSquareView Delegate
- (void)animationSquareView:(AnimationSquareView *)squareView didFinishAnimationWithStatus:(NSDictionary *)status
{
    NSLog(@"current status = %@", status);
}

@end

這邊多了幾行程式碼:

  1. 第 6 行,在類別後面加上了 <AnimationSquareViewDelegate>,就是告訴編譯器說:「我有遵守(conform) AnimationSquareView 規定的 protocol」,表示可以接受 AnimationSquareView 的委任。

  2. 第 17 行,設定 squareView.delegate = self,把 squareView 的 delegate 設定到自己身上,也就是目前這個 ViewController。

  3. 第 22 行到第 25 行,就是實作 protocol 裡定義的方法。

執行一下,應該可以在動畫執行結束的時候,看到輸出結果。

那,如果第 22 行到第 25 行的程式碼沒實作出來會怎樣? 在我們這個例子,不會怎樣,因為我們並沒有在 protocol 裡設定一定要實作的方法(required)。

透過 delegation,可以降低類別與類別之間的耦合,但仍然可方便的讓類別之間有"溝通"的效果,而且也很容易的可以把要溝通的"訊息"一併傳回。

範例檔下載

以上,希望這篇文章能讓大家對 delegation 有更進一步的了解。如果有哪邊寫錯,還請前輩先進指點。