高見龍

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

Taipei.rb 起步走

image

台北的 Ruby 社群跟相關活動其實不算少,除了一些個人或學校社團的活動外,人數較多的活動有例如每一、二個月舉辦一次的 Ruby Tuesday,會邀請特定的來賓來分享他們在工作上或個人研究上的專業主題;有年度大型活動 RubyConf Taiwan,會邀請國內外對 Ruby/Rails 有研究的高手來分享他們的心得,甚至連 Ruby 的老爸 Matz 也會來。除此之外,還有女性朋友專屬的 Rails Girls 以及每週進行的 Rails Girls 活動 (每週約二十人左右參加)。

又一個 Ruby 聚會?

不過,上述這些活動不是主題不適合新手,或是主題不容易引起共鳴,不然就是有性別限制,像我這種好像會寫一點點程式的高級新手想要找到同好一起學習反而變得不太容易。

目的/方向

所以,為了能讓更多對 Ruby/Rails 有興趣的朋友可以有個可以固定而且定期的交流管道,幾位 Ruby 圈的朋友(慕凡Manic 以及 大兜)就打算來籌備這樣的一個活動。我們想了好幾個名字,最後大家鼓掌通過的名字是「Taipei.rb」

活動的方向很簡單,就是「讀書」 + 「閒聊」。

不像 Ruby Tuesday 之類的主題式分享,Taipei.rb 初期設定的方向是「讀書會」,會先挑幾本書來跟大家一起讀、一起討論,每次活動開始會有一到二位與會者上台進行導讀,並從參與者中挑選下回活動上台的人。

新手/老鳥不拘,願意讀書就行,來找人閒聊或是來求職、求才也都歡迎。當然,性別不拘 :)

目前暫定書目:

Ruby 相關

Rails 相關:

週期

預計每二星期舉辦一次。

費用

每人僅需負擔場地費用(約 NT 200 元)即可,不另外收取費用。

第 0 次兼說明會

如果各位看完還是不知道這個活動要幹嘛,我們在星期二(3/4)晚上七點,在 DeRoot 有個說明會兼第 0 次的導讀示範,有興趣的朋友請按這裡報名。

再見 2013,哈囉 2014!

很快的,2013 年結束了,照例看了一下 2013 年的許願清單,完成度差不多只有六成,還有很多進步的空間。

2013 年幹了哪些事

  • 今年有 22 場公開的活動,雖然有點累,但很有趣!
  • 第一屆的 WebConf Taiwan 真的非常感謝大家的支持、捧場,活動算是圓滿結束。
  • 今年第一次厚臉皮的辦了 Ruby/Rails 有心人活動,感謝大家的支持。
  • 今年在日本舉辦的 RubyKaigi 剛好有榮幸投稿上了,所以同時解除了第一次在國外發表演說以及第一次用全英文演講 25 分鐘的成就,也在日本見識到了什麼才叫認真的技術魔人。
  • 在台北舉辦了兩次的 Rails Girls 活動,出乎意外的受歡迎。
  • 十月份帶了一家四口前往日本遊山玩水一個多星期,勉強算是有個交待了。

希望可以更熟悉的技能

資訊這一行,要學習的東西、想要做的事情太多,但人生有限,所以 2014 年只會把時間集中放在這些地方了:

  • iOS app / Cocoa Framework:這是目前幾乎 80% 的工作時間都在這上面了,不再精進一些怎麼行。
  • Ruby / Rails:很有趣的而且具生產力的程式語言/網頁框架,但常看到其它高手的寫法就會覺得自己還差得遠。
  • Golang:身邊的朋友不少都投入這個新語言,在效能上有很優異的表現,是時候該學習它了 :)

許願

  • 希望小朋友們可以平安長大,2014 年也可以再帶他們一起來去旅行。
  • 希望 WebConf Taiwan 2014 可以順利進行。
  • 希望 Rails Girls 活動可以繼續辦下去。
  • 希望「有心人」課程可以繼續辦下去。
  • 希望繼續推廣 Ruby 給更多的朋友知道。
  • 希望手上正在做的產品可以順利完成。
  • 希望公司生意… 順利就好。

許了一堆願望,2014 年也要繼續努力!

id 與 instancetype

這星期我們再來看個有點冷門但我覺得還滿有趣的小東西:instancetype。如果我們去翻一下 NSObjectallocinit 的定義:

1
2
3
4
5
6
7
8
// 檔案:NSObject.h
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

- (id)init;
+ (id)new;
+ (id)alloc;

會發現 allocinit 的回傳型態都是 id。而在上一篇提到,在 Objective-C 裡 id 是一個可以指向任何物件的指針,所以如果這樣寫的話:

1
NSArray* myArray = [[NSArray alloc] init];

看起好像沒什麼問題,執行起來也正常,但這裡就有個小小的疑惑了.. 即然 allocinit 都是回傳 id 型別,Objective-C 是個動態語言,很多資訊是在執行階段(runtime)才會取得,那編譯器(compiler)又是怎麼知道它應該要是個 NSArray?

Object, Class and Meta Class in Objective-C

寫了一陣子的 Objective-C/iOS app,這次讓我們回頭來看點基礎的東西 :)

什麼是 id?

在 Objective-C 裡,變數宣告通常得告知編譯器這個變數的型別,例如這樣:

1
NSArray* myArray = @[@"eddie", @"kao"];

而在 Objective-C 裡有個特別的型別叫做 id,它可以指向任何物件,像這樣:

1
id myObject = @[@"eddie", @"kao"];

那這個 id 是什麼? 我們直接來從原始程式碼來找這個 id 的定義。大家可以打開 XCode,然後選擇「File」→「Open Quickly..」,應該可以找到 objc.h 這個檔案,往下捲一點,應該可以找到像下面的這幾行程式碼:

1
2
3
/// 檔案名稱:objc.h
/// A pointer to an instance of a class.
typedef struct objc_object *id;

就照上面這段註解說的,所謂的 id 就是一個「指向一個實例 (instance) 的指針」,而且 id 這個 Struct 的定義本身就有帶一個 *,這也是為什麼你在宣告其它一般物件型別的變數需要加註一個 * 來表示這是一個指標變數,而使用 id 卻不需要的原因。

什麼是一個物件 (Object)?

讓我們再從剛剛那個檔案,順蔓摸瓜的繼續看下去..

1
2
3
4
5
/// 檔案名稱:objc.h
/// Represents an instance of a class.
struct objc_object {
    Class isa;
};

從這段程式碼可以知道,其實所謂的「物件」,說穿了就只是一個 C 的結構 (C Struct),而在這個 Struct 裡面只有一個 isa 的指針,指向自己所屬的類別。

再來我們來看看在 Objective-C 裡,到底什麼是一個類別(Class)

什麼是類別 (Class)?

繼續來看剛剛的那個檔案:

1
2
3
/// 檔案:objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

看得出來其實 Class 本身也是一個 C Struct,再往下看一下這個 objc_class 的定義 (runtime.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 檔案:runtime.h
struct objc_class {
    Class isa;

#if !__OBJC2__
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
#endif
};

從上面這段程式碼可以看到,所謂的類別也是一個 C Struct,裡面第一個指針 isa 指向它的所屬類別,第二個指針 super_class 則是指向它的父類別。除此之外還有一些其它的指針,指向例如 instance variables、method list、cache、protocol 等 Struct。

訊息傳遞 (Message Passing)

大家在其它 Objective-C 的書上應該常看到,在 Objective-C 的世界裡,方法不像其它程式語言一樣的被直接呼叫,而是透過「訊息傳遞(Message Passing)」方式,像是這樣:

1
[someObject saySomething:@"hello"]

這裡的 someObject 稱作 recevier,就是接收「訊息」的傢伙,而 saySomething: 就是「訊息」,@"hello" 則是參數。

事實上,在訊息傳遞的過程,當 someObject 這傢伙收到訊息之後,會順著這個物件的 isa 指針找到自己的類別,然後再依照收到的「訊息」去類別的 method list 找出對應的方法。如果在這個類別裡面找到了,就會直接執行它,如果找不到,會再往上一層父類別(super class)找。以上這個流程都是在程式執行過程中(Rumtime)才動態決定的,而不是在編譯(Compile)時期就決定好的。

而這個訊息傳遞的過程畢竟是多做了幾件事,相對感覺會比較耗時,但實際上在程式的執行過程中,一但執行過的方法就會被暫存(cache)下來,下次再收到一樣的訊息的時候就會快得多了。

其實,在 Objective-C 的世界裡,類別本身也是物件,所以你也可以對它「發送訊息」,像是這樣:

1
[NSArray arrayWithObjects:@"eddie", @"kao", nil];

看起來就跟在其它程式語言的「類別方法」差不多,但事實上它就是對 NSArray 這個類別發送了 arrayWithObjects: 這個訊息。

接下來我們直接寫一小段程式會比較容易想像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Animal Class
@interface Animal : NSObject
@end

@implementation Animal
@end

// Fox Class
@interface Fox : Animal
- (void) say;
@end

@implementation Fox
- (void) say
{
    NSLog(@"What does the fox say!?");
}
@end

上面這段程式碼建立了兩個類別,分別是 AnimalFox ,而且 Fox 繼承自 Animal,而 Animal 類別則是繼承自 NSObject 類別。

不管是「物件」或是「類別」,因為一樣都是物件,所以他們在收到訊息時的反應流程也是一樣的:

  • 當對「物件」發送訊息的時候,它會順著這個物件的 isa 指針找到它的所屬類別,然後翻看看這個類別的 method list 有沒有符合的方法可以執行;
  • 當對「類別」發送訊息的時候,它會順著這個類別的 isa 指針找到它的所屬類別(也就是我們待會要說明的 Meta Class),然後翻看看這個類別的 method list 有沒有符合的方法可以執行。

什麼是 Meta Class?

Meta 這個字我不太懂中文該怎麼翻譯比較好,有人翻譯成「元」,有人翻譯成「後設」,但我個人還是喜歡直接使用 Meta 這個詞就好。

剛剛在看類別的 objc_class 的定義的時候有提到類別的 Struct 裡也有一個 isa 指針,指向它所屬的類別,你可以想像成是它是「類別的類別」,也就是所謂的 Meta Class

而 Meta Class 其實也是一種類別,所以也跟一般的類別一樣有 isasuper_class 指針… 所以就可以把這整個的關係用一張圖表來解釋:

image

看圖說故事:

  1. 每個 Class (Fox, Animal, NSObject) 的 isa 指針都指向一個唯一的 Class,這個 Class 稱之為 Meta Class
  2. 每個 Meta Class 的 isa 指針都是指向最上層的 Meta Class (在我們上面那段範例裡,就是 NSObject 的 Meta Class),而最上層的 Meta Class 的 isa 則是指向自己,形成一個迴路。
  3. 每個 Meta Class 的 super_class 指針都是指向它原本類別的 Super Class 的 Meta Class,但最上層的 Meta Class 的 super_class 則是指向 NSObject 類別本身。
  4. 最上層的 Class (NSObject),它的 Super Class 指向 nil

PS: 在 Objective-C 裡有兩種 Root Class(NSObject 跟 NSProxy),但因為在 Objective-C 裡「大部份」的類別都是 NSObject 的子類別,所以舉例常會說最上層的類別就是 NSObject

一堆 Class、Super Class、Meta Class 的,有種快打結的感覺了嗎? 其實只要理解上面那張圖片,基本上就不用記這些繞舌的規則了。

但口說無憑,讓我們來寫幾行程式碼驗證一下:

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
30
31
32
Fox* lucky = [[Fox alloc] init];
[lucky say];

// about fox instance
NSLog(@"the class of lucky is %@, address = %p", [lucky class], [lucky class]);

// about Fox class
NSLog(@"Fox = %@, address = %p", [Fox class], [Fox class]);
NSLog(@"Fox's Super Class = %@, address = %p", [Fox superclass], [Fox superclass]);
Class metaClassOfFox = object_getClass([Fox class]);
NSLog(@"Fox's Meta Class = %p", metaClassOfFox);
NSLog(@"Fox's Meta Class's Super Class = %p", [metaClassOfFox superclass]);
Class metaMetaClassOfFox = object_getClass(metaClassOfFox);
NSLog(@"Fox's Meta Class's Meta Class = %p", metaMetaClassOfFox);

// about Animal class
NSLog(@"Animal = %@, address = %p", [Animal class], [Animal class]);
NSLog(@"Animal's Super Class = %@, address = %p", [Animal superclass], [Animal superclass]);
Class metaClassOfAnimal = object_getClass([Animal class]);
NSLog(@"Animal's Meta Class = %p", metaClassOfAnimal);
NSLog(@"Animal's Meta Class's Super Class = %p", [metaClassOfAnimal superclass]);
Class metaMetaClassOfAnimal = object_getClass(metaClassOfAnimal);
NSLog(@"Animal's Meta Class's Meta Class = %p", metaMetaClassOfAnimal);

// about NSObject class
NSLog(@"NSObject = %@, address = %p", [NSObject class], [NSObject class]);
NSLog(@"NSObject's Super Class = %@, address = %p", [NSObject superclass], [NSObject superclass]);
Class metaClassOfNSObject = object_getClass([NSObject class]);
NSLog(@"NSObject's Meta Class = %p", metaClassOfNSObject);
NSLog(@"NSObject's Meta Class's Super Class = %p", [metaClassOfNSObject superclass]);
Class metaMetaClassOfNSObject = object_getClass(metaClassOfNSObject);
NSLog(@"NSObject's Meta Class's Meta Class = %p", metaMetaClassOfNSObject);

輸出結果如下:

What does the fox say!?

the class of lucky is Fox, address = 0x100002260

Fox = Fox, address = 0x100002260
Fox's Super Class = Animal, address = 0x100002210
Fox's Meta Class = 0x100002238
Fox's Meta Class's Super Class = 0x1000021e8
Fox's Meta Class's Meta Class = 0x7fff77cce838

Animal = Animal, address = 0x100002210
Animal's Super Class = NSObject, address = 0x7fff77cce810
Animal's Meta Class = 0x1000021e8
Animal's Meta Class's Super Class = 0x7fff77cce838
Animal's Meta Class's Meta Class = 0x7fff77cce838

NSObject = NSObject, address = 0x7fff77cce810
NSObject's Super Class = (null), address = 0x0
NSObject's Meta Class = 0x7fff77cce838
NSObject's Meta Class's Super Class = 0x7fff77cce810
NSObject's Meta Class's Meta Class = 0x7fff77cce838

輸出結果可能在每個人的電腦上都不一樣,但從輸出的結果就能證明上面那張圖片的每個類別之間的關係。

要特別注意的是,你可能會覺得直接對類別發送 class 訊息就可以得到該類別的 Meta Class,事實上直接對類別發送 class 訊息只會得到該類別自己。要取得類別的 Meta Class,可以透過 object_getClass 來取得。

以上,希望這篇文章能讓大家對 Objective-C 基本的物件跟類別有更進一步的了解。

個人認為,雖然了解這些知識不見得對 iOS app 的開發有直接的幫助,但至少當在寫一個類別或使用一個物件的時候,會更清楚到底是怎麼一回事。如果有哪邊寫錯,還請前輩先進不吝指點。

分心 FanXin app

image

今年二月份剛好有機會到朋友的公司幫忙開發 iOS app:「分心 FanXin」。顧名思義,就是一個可以讓你一邊看電視,一邊分心地跟朋友聊劇情的 app。

下載網址:https://itunes.apple.com/tw/app/fanxin-fen-xin/id545965153?mt=8

但近期因為覺得我家的那兩隻小朋友在長大的速度變好快,覺得好像再這樣忙下去就會錯過小朋友的成長過程了(像今天才看到我家三歲的小男生拿媽媽的化妝品在畫自己的臉)。所以把專案進度做個段落之後,就要回家來養小孩了(沒錯,我就是要從小開始訓練小朋友寫 code 了!)。

做玩具與做產品的差別

這回幾乎把一些常見的 iOS app 常用的東西都再磨過一輪。UI customization 當然是免不了的,另外,從 Facebook SDK 到 remote & local notification,從 HTTP API 的串接到 streaming messaging protocol 的訊息處理,從 Core Data 到 NoSQL 等等,甚至也很幸運的遇到了 iOS 7 的 migration,完全體會到做玩具跟做產品的差別。

這個 app 雖然不是全新打造的,但由於舊版的 app 架構有些不適合新改版的功能需求,所以最後幾乎是整個砍掉重練,從無到有手刻了超過三萬五千多行的程式碼(不包含3rd party library): image 雖然跟一些大型專案比起來還差很遠,但對我個人來說已是個里程碑,除了提昇了不少寫 code 的手感外,也更了解 Objective-C 跟 Cocoa framework 是怎麼回事。

分心團隊雖然是個新創團隊,但裡面的成員都不會太新手,有眼光犀利的UI、UX designer 讓整個 app 看起來很精緻,使用起來也更直覺;有優秀的工程師負責處理 API,也有專門負責行銷及想點子的同事,讓開發人員可以 專心的開發 app;除此之外,還有 Web 平台的開發人員,讓 Web 跟 Mobile 都有一樣的功能。在這短短的八個多月的時間跟大家學到不少。

徵人啟事

分心團隊目前仍持續有在徵人,有興趣的朋友歡迎來試試看囉!