2017年10月24日 星期二

十年來,程式設計領域有什麼重要進展?

程式設計語言層出不窮,然而內核是萬變不離其宗。我個人看法覺得是以下幾個方面的變化比較明顯。

語言本身:

1. 工業標準 網頁標準有 W3C 控制,尤其是流覽器的開發,所有主流的流覽器都會自覺符合這個組織的標準,當然這些開發商本身就是這個組織的成員。所以新的 HTML5,CSS3,ES6 JavaScript 的新特性的得到順利推動,讓大部分主流流覽器都支持它,W3C 功不可沒。 PHP 有 PHPFIG 組織,雖然不是強制性的,但是很多新的框架和庫都自覺遵守這個組織的程式設計標準。 Java, C 語言都有各自的工業標準準則,來維護各自工業標準。 這個標準其實不是強制性的,雖然很多程式師在自己工作上,不遵守這些工業標準,但是要推出新的模組的話,不遵守這些工業標準的模組,是沒有人會去使用的。如今是不是面向標準程式設計,是體現一個程式師是否專業,一個模組是不是專業模組的一個重要指標。 2.協力廠商模組走紅 各種語言的框架和庫,可能比自己的語言還出名,比如 CSS 的 Bootstrap,JavaScript 的 jQuery;一個好的框架和庫甚至可以推動這個這個語言的發展,比如說 PHP 的 Laravel 框架,JavaScript 的 jQuery. 模組化的發展,大大加快了開發的速度。很多人也願意開發各種框架和模組,不但可以鍛煉自己的開發技能,也是一種展示自己的能力。 過去,程式師要成名,要開發出有用的軟體,比如說求伯君開發出了 WPS,牛;張曉龍開發出了 Foxmail,牛。 現在,程式師要成名,開發出一個大家都會用的框架和模組也行。比如 Evan You 開發的 Vue.js,玉伯開發的 SeaJs。 3.模組化程式設計和依賴管理 在 2010 前,依賴管理工具只是個很時髦的概念,大家習慣手動到庫的官方網站上下載後手動導入到項目中。升級也是個麻煩事。所以一般大家也就下載一兩個必要的庫,其他都自己手寫完成。 如今,依賴管理工具已經是必備的了,大家不再手動導入庫了;而且是能找到協力廠商模組的功能,就不再自己編寫了,統統用工具導入項目;自己編寫的程式碼,能模組化的代碼統統模組化,甚至是獨立出來,網上開源,然後使用依賴管理工具進行管理導入到自己的專案中。 這樣好處也明顯:
  1. 代碼量減少
  2. 加快開發速度
  3. 高度解耦
  4. 定位 bug 容易,改動影響小
  5. 寫單元測試容易
如今大家更加願意寫小模組,而不是重複造輪子了。 4. 框架使用 更願意先選一個合適的框架,再開始程式設計,而不是所有功能自己從頭開始寫了.
  1. JavaScript 的框架多了,Vue,React, Backbone,AngularJs 等;
  2. CSS 有 Bootstrap,Fundation 等;
  3. PHP 有 Laravel,CakePHP 等
  4. C#有 MVC
  5. Java 有 Spring+Hibernate+struts
框架要先選好,模組的話,等需要慢慢加就行了。 5. 測試代碼 2006 年,單元測試在開發過程中,重要性不是很大,可有可無,程式完成,功能能用就行。 如今的代碼,沒有單元測試部分,這個工程就不能算完結。甚至是,測試驅動開發已經成為主流,先寫測試代碼,然後開發。 測試代碼的發展有不單單是單元測試部分。單元測試,集成測試,功能測試,性能測試,壓力測試等等,都在開發過程中占了極大的位置。以前測試都是由專門的測試員進行人工測試,或者他們負責測試;如今單元測試和集成測試都是要開發者自己寫。 6.跨設備,跨平臺 Java 提出的跨平臺,一次編譯到處運行的夢想,其實至今未很好的實現。但是如今這個跨設備,跨平臺程式設計趨勢卻越來越明顯了。 跨設備,主要是指桌面和手機,尤其是針對顯示器的最佳實踐是層出不窮,如今是回應式成為了主流。 跨平臺,出自於 Java 的一個概念,如今已經算普及了,尤其是 JavaScript,桌面,手機,伺服器,流覽器,嵌入式都能看到 JavaScript 的身影,這大大歸功於 JavaScript 標準化的推廣。跨平臺過去是說一次編譯到處運行;如今是只要這個平臺支援這個語言或標準,就能用。如今的跨平臺程式設計,更講究特性檢查這個功能,如果你這個 平臺沒有這個特性,那麼就關閉這個有這個特性的功能,但其他功能還可以繼續使用。 今後,各種設備層出不窮,VR 頭盔,AR 眼鏡,巨型螢幕,物聯網等等,跨平臺會有進一步的發展。 工程方面的: 1.工具化 我覺得工具化非常突出了,凡是能工具完成的事情,絕對不手工完成。以下幾個方面都是可以找到相應工具,幫助開發者管理代碼品質
  1. 代碼風格檢查
  2. 工業標準檢查
  3. 代碼整理
  4. 代碼複雜度檢查
  5. 單元測試覆蓋率檢查
  6. 依賴管理
  7. 壓縮代碼
  8. 重複代碼檢查
  9. 無用代碼檢查
等等, 2. 工程化 工程化也是近年來最最突出的一個發展趨勢,過去只是選擇性的,現在是必須的。 工程化是以工具化為基礎的,沒有工具,那麼工程化也無從談起。 工程的核心就是流程自動化,又稱之為構建,這些包括了:代碼品質檢測,代碼壓縮,代碼合併,代碼優化,代碼編譯,單元測試等等部分。構建就是把這些以工作流程的方式組合起來,然後用一個命令列運行這整個流程。它有點像批次處理,但是是程式開發中使用的特殊批次處理 在網頁程式設計的過程中,現在又流行“即時程式設計”,就是當你在保存代碼的時候,以上的構建流程就開始工作完成後自動刷新流覽器,保證新代碼效果立刻反應在流覽器上。 現在,你去 GitHub 的專案庫中找軟體,首先翻看,是否有工程檔,看看它的構建流程是什麼,就知道這個項目的專業程度和項目的品質了 而自己,沒有一個配置一個工程化的流程系統,都不好意思說自己在做軟體工程。 3. 自動化 自動化是以工程化為基礎的,工程化本身就是一種流程自動化。而自動化有在工程化的過程中更進一步的自動化。 持續集成就是全自動化的一個終極體現。他的主要流程是:版本控制庫 ->構建 ->測試 ->報告. 持續集成有點像 Windows 的定時任務,但是它是程式開發專用的定時任務。 持續集成的特點就是全自動,一個專案一次配置好了後,要求不變的話,就不用管了;然後開發者不斷把代碼加入到版本控制庫裡就行了,每當庫有新代碼時 候,持續集成就會下載代碼進行構建;當它完成構建和測試後,如果測試沒有通過,就會報告給你,然後你根據報告結果進行修改代碼。所以你每次往版本庫加的新 代碼時候,持續集成就會全自動的幫你構建和測試代碼,儘快的通知你代碼的問題。這樣程式師就可以更加集中精力編寫功能代碼和測試代碼,而不用擔心新代碼是 否會影響到過去的代碼了。 持續集成在多人一起開發的時候,更是有用,誰上傳的代碼沒通過測試,能馬上知道。這樣保證多人專案在代碼順利合併,體現“持續集成”的功效。 另外還有個持續部署,其實就是持續集成在測試成功後部署上產品伺服器上的流程。如今有些網站一天就要部署幾十次,有了持續部署後,部署多少次都毫無壓力。 工具化,工程化,自動化的關係挺有意思,前者是後者的基礎,而後者卻極大推動了前者的發展。它們是相互積極作用,相互推動了對方的發展,形成了一個很好的良性迴圈

其他方面:

1. 版本控制,Git,GitHub 版本控制在程式設計界中的地位是越來越重要了。在程式設計界中有個說法:沒有版本控制的項目,就等於沒有這個項目。 版本控制的工具很多過去有 SVN,如今 Git 的強大,用的人也是越來越多,而它和 GitHub 的相同作用下,對程式設計界的積極影響和積極推動,是令人無法忽視的。比如幾乎所有的依賴管理工具的庫下載源,都是和 GitHub 綁定的, 就這一點來說,GitHub 的重要性在 IT 就不可估量。 而 GitHub 上和 Git 的方便管理,上傳,查看,統計,bug 報告等功能更是極大地推動了程式師之間的合作;GitHub 上的開源更是改變了開源軟體對世界的影響力。 GitHub 不是 Git 的全部,Git 也不是版本控制的全部,本質上來說,GitHub 只是一個網站而已;然後 GitHub 確實又是這個程式設計世界不可缺少的一個重要的模組,已經成為了一個不可或缺的組成部分了。甚至 GitHub 已經跳出了程式設計界,成為了一個世界級的不可或缺的服務平臺了。然而 GitHub 是 2008 年建立的,真正開始流行是在 2012 年的。在 2015 年 Google 宣佈關閉自己的 Google Code。可見 GitHub 的影響力,以及在業界的重要程度了。 2.生態圈意識 生態圈意識在業界是越來越強了,它應該和程式設計工具化和工程化有極大的關係。一個語言,框架或者庫的出現,人們用它們,不但是因為它們本身的強大,更是因為它們背後的生態圈。 比如說人們選一個 JavaScript 的框架,選 React 還是選 Ember.js,更多是看支持他們的生態圈如何,React 是有 Facebook 支持的,更有很多程式師為它開發相關工具和庫以及有很多文檔教程。這樣 React 的生態圈就很大,會讓更多人願意選擇 React 作為第一開發框架。而 Ember.js 相對來說生態圈小,選擇它的人可能就不會很多。 選語言也一樣,選 JavaScript 編寫爬蟲還是選 PHP 編寫爬蟲還是用 Python?更多的是看他們的生態系統了,Python 的爬蟲庫強大且豐富,所以更多人選用 Python 編寫爬蟲。 一個新的語言出現,成熟與否,看的就是它的生態圈了,比如是否有測試框架,是否有 MVC 框架,成熟的時間庫,資料庫 SDK 等等,這些都是其必要的生態圈組成部分。

總結:

以上的這些現象和趨勢,其實都是相輔相成的,最終成了一種良性迴圈。這些現象和趨勢都會繼續發展下去,並成為以後新趨勢的基礎。所以這些特點都是非常重要的,而且應該成為每個程式師都應該知道的知識。

一些建議:

我在讀程式設計專業的時候,這些東西大學都沒有教過,甚至在工作中,公司都沒有這些要求。大學主要教的是代碼編寫,能編譯通過,能出正確結果就可以了。在工作中,代碼能用,沒有明顯 bug 就行。 然而,在我個人工作實踐中,逐漸的體會到這些趨勢的重要性了,可維護性的高品質代碼可以大大減少自己在維護中的難度和壓力。作為準備成為一個合格的開發人員,應該熟練掌握這些知識和技能。如果大學沒有教過,一定想辦法自己學習和提高。 又想到幾個發展,這裡更新一下 1. WEB 技術的桌面化和 JavaScript 的全棧化 JavaScript 近些年發展火熱,逐漸印證了一個 Atwood 法則:凡是可以用 JavaScript 實現的,最終都會用 JavaScript 實現
  1. Nodejs 的出現,奠定了 JavaScript 走出流覽器,走向了伺服器端
  2. NW 的出現和 electron 正式版發佈,JavaScript 走向了桌面
  3. MongoDB 的出現,JavaScript 走向了資料庫
  4. Tessel 的出現,走向了硬體和物聯網
如今一個全棧系統,從前端到資料庫,可以完全使用 JavaScript 一種語言。還有很多人正在致力於把 JavaScript 推向更多的領域中。 而 Web 技術(html+css+JavaScript)由於 NW 和 Electron 的出現,已經可以編寫桌面程式了。正是由於 JS 的優秀模組很多,以及 HTML+CSS 的介面容易編寫和掌控,糾錯工具豐富,很多人願意用 WEB 技術進行開發。現在比較火的桌面工具有 VS-Code 編輯器和 Atom 編輯器。 總結一下:由於 WEB 技術的便利性,WEB 技術涉及的領域也就越來越多,再也不是流覽器的專利了。 2. Web API 的全面發展 Web API 雖然歷史悠久,但是真正使其推廣流行的應該是 Twitter,而後移動設備的普及使其得到更大發展和普及。移動設備如果沒有 Web API 基本就不能工作了。Web API 的普及,也使得網路服務之間相互連通,形成一個更大的服務網路。總之,如今的 Web API 已經是不可或缺的存在了。 Web API 更多的是一種服務,或是一種資料交換模式。只要語言帶有 HTTP 的網路訪問功能,就都能使用。提供 Web API 的公司,發佈 Web API 後,一般也會同時發佈一些常用語言的 SDK,方便相應語言開發人員快速上手;但是如果語言比較小眾,沒有提供相應的 SDK 也沒有關係,編寫一段 HTTP 的請求,也是可以交換資料。 從程式設計的角度來歸納一下 Web API 特點就是:
  1. 容易編寫,就是個函數,無需介面
  2. 語言無關性,無論 Web API 是個語言編寫,幾乎任何語言都能調用
  3. 訪問性好,無論在哪,只要網路能訪問,Web API 就可以用。
3. 語言之間的相互借鑒 語言之間的相互借鑒也越來越明顯了,比如:
  1. PHP5.0 后支持了类,5.4 后支持了 Trait,5.5 后支持了生成器(Generator)
  2. JavaScript ES6 支持了箭头匿名函数,生成器(Generator),类(不是 Prototype 的类)
  3. C# 和 Java 相互借鉴
  4. Coffee Script 借鉴 Python 和 Ruby
與其說是相互借鑒,不如說隨著語言的發展,一些語言概念逐漸成為了標配,如果沒有,就算是一個不完整的語言了。比如說類,匿名函數,常用資料結構等都成為了標配。 4. 語言解析器的工具化 語言解析器(Parser)在過去自是作為編譯器的一部分存在的。如今,它已經獨立出來作為一個模組或者工具來使用了,這個對於一個語言的生態有著很大的意義,促進了語言生態圈的良好發展。 獨立出來的解析器,可以用來編寫以下和語言有關的工具,這些工具都是用來優化代碼品質的,提高編碼體驗的。
  1. 語法檢查,JavaScript 的 JSHint 用的就是 JavaScript 的一個解譯器,被 JavaScript 重新解釋一遍,把可能有問題的地方標記出來通知程式師,程式師可修改避免潛在錯誤。
  2. 代碼最小化,代碼重寫的一種形式,JavaScript 的最小化項目(比如 Urglify),是把語法正確讀取後,進行最小化壓縮。把單詞變數轉換成單字母變數。甚至是 if else 轉換成?: 形式。
  3. 語法擾亂器,就是代碼重寫的一種形式,讓代碼無法閱讀,保護代碼。
  4. 語法整理器,代碼重新的一個形式,把無法閱讀的代碼,轉換成可閱讀的代碼,比如 beautifier。
  5. 語法高亮,一般用於代碼編輯器和代碼顯示元件的。
  6. 代碼分析器, 把可用的代碼部分進行掃描,列出代碼相關資料,比如用了多少類,多少物件,多少變數,多少全域變數等等
  7. 代碼清理器,分析器的加強,清理不用的變數,不用的物件和,不用的函數等。
  8. 自動完成,一些 IDE 可以分析已經存在的變化和函數,以後在不斷的打字中可以智慧的自動完成。
  9. 代碼追蹤,比如說某段代碼被執行了幾次,程式報錯時候,函數被執行的順序,測試程式時候的代碼覆蓋率等等
  10. 虛擬執行,JavaScript 代碼在一個保護區域內或環境執行,代碼可以返回值,但不能影響非虛擬環境內的代碼執行。比如說,代碼裡面有全域變數,但是虛擬執行後這個全域變數只在虛擬環境內,非虛擬環境的沒有這個全域變數。
關於這點,我回答過下面的問題。 用 JavaScript 寫成的 JavaScript 解譯器,意義是什麼? – 知乎用戶的回答 5. 資料交換語言的發展 資料交換語言發展總體來說就是從 XML 主流逐漸發展到 JSON 主流的過程. 雖然 XML 現在應用還是非常廣泛,但是由於其複雜和標籤佔用空間大,逐漸被羽量級的 JSON 給代替了。尤其 JSON 與 JavaScript 天然相容,無需解析,直接使用。所以在很多網路技術中 JSON 是優先使用的。 而如今很多設定檔也是用 JSON 實現的,比如 Composer 和 node 的設定檔。 JSON 的閱讀方式更符合程式師的閱讀習慣,格式化後的結構一目了然,容易理解。 JSON 好處:
  1. 結構符合程式師閱讀習慣
  2. 文件大小相對更小
  3. JavaScript 可以直接使用
  4. 在非 JavaScript 的腳步語言中,轉化成資料結構更容易
  5. 學習曲線很短
正是以上這些原因,使用 JSON 作為資料交換語言可以說在程式設計界裡,是大勢所趨了。

前端工程師

之前上 Laravel Dojo 的 網站製作工作坊 看到一張前端工程師技能樹的圖片,由於是簡報的關係,圖片被砍成幾版,今天在 [前端工程] 前端工程師技能樹 看到了原圖,所以下載作參考。

我們聽過很多工程師,什麼Java工程師,PHP工程師,數據庫工程師,等等,而對於Web前端開發工程師,可能是最近幾年才火起來的一個很新的職業,在國內乃至國際上真正開始受到重視的時間不超過10年。Web前端開發是從網頁製作演變而來的,名稱上有很明顯的時代特徵。

前端工程師是互聯網時代軟體產品研發中不可缺少的一種專業研發角色。從狹義上講,前端工程師使用 HTML、CSS、JavaScript 等專業技能和工具將產品UI設計稿實現成網站產品,涵蓋使用者PC端、移動端網頁,處理視覺和交互問題。從廣義上來講,所有使用者終端產品與視覺和交互有關的部分,都是前端工程師的專業領域。 2005年的時候大多數網頁長這樣: 現在的網頁一般是這樣的:

前端工程師的發展之路和前景是怎麼樣的?

前端是一個相對比較新的行業,互聯網發展早期(1995年~2005年)是沒有專業的前端工程師的。隨著互聯網的發展,大約從2005年開始,正式的前端工程師角色被行業認可,到了2010年,互聯網開始全面進入移動時代,前端工程師的地位越來越重要,前端領域的技術發展也越來越快,各種新的思想、設計模式、工具和平臺都快速發展,對前端工程師的技能要求也越來越高。 有一些資料可以說明前端行業的發展迅速。
  • 在2010年之後最流行的新程式設計語言中有相當部分和前端有關,比如 Dart、Clojure、CoffeeScript 和 TypeScript。
  • 作為前端最重要的程式設計語言 JavaScript,在最近幾年裡不論是代碼量還是關注數都穩居 Github 平臺熱門程式設計語言榜。
  • 行業對前端需求量持續增加,前端程式師薪水在行業裡面處於較領先的位置。
近年來最流行的程式設計語言很多都是JavaScript替代語言 近年來最流行的程式設計語言很多都是JavaScript替代語言 JavaScript在最熱程式設計語言 TOP10 JavaScript在最熱程式設計語言 TOP10 近幾年互聯網公司前端團隊每年擴張一倍 近幾年互聯網公司前端團隊每年擴張一倍 JavaScript工程師平均薪水排名在程式語言工程師收入前10 JavaScript工程師平均薪水排名在程式語言工程師收入前10

前端工程師需要什麼樣的知識和技能?

有人說前端工程師的技術棧是這樣的: 還有人說是這樣的: 實際上前端工程師最核心的技能還是: 在一個典型的互聯網公司的產品研發流程中,前端工程師和其他角色的關係大致上是這樣的: 前端是最接近產品和設計的工程師,起到銜接產品和技術的作用,前端為使用者可以看到的部分負責,所以也是最接近用戶的工程師。 在多終端的時代,如果一個產品同時支援PC、移動端,前端工程師還需要和更多的角色打交道: JavaScript 對於前端是最重要的技能,所以優秀的前端工程師要有扎實的JavaScript基本功。而JavaScript這門程式設計語言也是目前程式設計領域炙手可熱的寵兒,如今的它不僅僅只是用來開發Web,還可以用在各個方面。 JavaScript 可以用在“樹莓派”這類智慧硬體晶片開發 JavaScript 可以用在“樹莓派”這類智慧硬體晶片開發 前端工程師也是軟體工程師,所以軟體工程師的基礎知識也是非常重要的,這些基礎知識包括:
  • 數學
  • 電腦體系
  • 作業系統
  • 資料結構和演算法
  • 編譯原理
HTML和CSS也是前端工程師非常重要的基本功,很多同學,尤其是喜歡寫代碼的同學容易忽視 Markup Language,實際上 ML 也是 UI 相關的領域裡面很重要的內容,不應該被忽視。
  • HTML: The Living Standard
  • HTML & CSS
有同學問說:“前端工作需求很多,老是改來改去,實際的技術點並沒有多少,產品決定業務邏輯,從事底層基礎服務會不會更有挑戰和職業未來?” 的確,越貼近業務和產品層面上的工作,需求差異性越大,可能改動越頻繁。不僅僅是前端改來改去,PHP服務端做業務的同學也面臨這樣的問題,業務邏輯改來改去。越底層通用性越強,改動相對較少。 不過事情都是有兩面性的,首先可以這麼想想,是底層基礎服務的市場大還是互聯網業務和產品的市場大。其次,基礎服務的通用性很容易達成,而產品層面上如何通用化,如何在業務驅動的產品研發中利用工程化和工具化提升開發效率,這其實是一個很難的問題。豐富的互聯網產品已改變和正在改變著我們的生活,然而作為產品的創造者,工程師們怎樣讓自己過得更好,這個領域值得研究。 另外,不要覺得實際的技術點沒有多少,舉幾個例子:實現曲線和曲面動畫,計算地圖的最短路徑,讓png靜態圖片類似於gif圖一樣做局部的運動,抽獎遊戲,物理效果的HTML5遊戲,3D圖表,增強現實的WebGL視頻流處理等等,這些都是在前端領域中遇到的實際問題。 就 JavaScript 來說,在實際專案中設計最合適的模型高效率解決現實問題本身就很有挑戰。作為一種典型的新生代程式設計語言,JavaScript 特性豐富,使用靈活,性能優良。物件導向、函數式程式設計、各種設計模式、MVC 和 MVVM,這些本身就有足夠的吸引力。 前端要解決介面和交互問題,實際上UI層面上的問題一直是軟體工程方面的一個難題,因為UI不停地在變化。流覽器各個版本的相容性、Web 標準、移動設備、多終端適配,給了前端工程師很大的挑戰,對前端工程師的能力也有很高的要求。許多UI問題有不只一種解決方法,許多問題有非常巧妙的思路和精彩的解決辦法,前端在工程師群體裡是屬於非常有創造力的一個群體,因為這個行業需要豐富的創造力和想像力。 前端工程師還是Web標準的制定者、實踐者和推動者,而現在的W3C標準不僅僅局限於流覽器,還包括各種手持智慧設備,車載設備、智慧家居等等。在未來萬物互聯的時代,前端將不僅僅是網頁上的工程師,而是所有人機交互領域的工程師。

前端工程師的學習和成長

前端領域發展很快,各種新技術新思想不斷湧現,這是一個好現象。但是前端發展太快也帶來一些問題,比如有同學就問到我究竟應該學些什麼,Angular.js、React、Node.js、ES6、ES7、CoffeeScript、TypeScript……似乎永遠有太多東西需要學習,有些東西好像還沒學明白就被另一些新的技術取代而“過時了”。 其實還是那句話,前端工程師首先是軟體工程師,基礎是最重要的,如果基礎不扎實,一切應用技能就都是“浮雲”。前端的基礎是什麼?HTML、CSS、JavaScript基本功,數學、演算法、資料結構、作業系統、編譯原理基本功。 一個優秀的前端工程師必須要有自己擅長的領域,並且鑽研得足夠深入,同時要有眼界,能“跨界”。可以以前端作為職業,但千萬不要把自己的技能限制在前端領域,因為有很多東西,只有站在前端之外,才能看得更清晰,更透徹。 學東西千萬別盲目更風,大家都在談AngularJS就立即跑去學習,過幾天大家都談React了,就又放下AngularJS去學習React。前端領域知識點很多,值得學的東西也很多,聰明的同學懂得花時間學習成體系的知識並且研究得足夠深入,因為只有這樣才能從中總結出規律,形成方法論,這樣才能最大化學習的價值。 知識的正確用法 —— 一個領域裡面的大師永遠不會是另一個類似領域的菜鳥 知識的正確用法 —— 一個領域裡面的大師永遠不會是另一個類似領域的菜鳥 這次前端星計畫佈置的一個實現帶有農曆和節氣的萬年曆,有些同學卡在農曆計算上,大約70%的同學懂得去網上找代碼,但只有不到1%的同學真正弄明白農曆計算的原理。 在面試的時候,面試官問到如何做前端性能優化,有的同學能夠拿雅虎的性能優化軍規回答得頭頭是道,反復強調使用工具壓縮靜態資源,但是自己搭建的博客的nginx服務卻沒有開啟gzip。都知道說要合併靜態資源,要減少HTTP請求,然而為什麼要減少HTTP請求,減少請求之後預計能改善多少性能,獲得多少收益呢?需要弄明白這些問題,也需要深入瞭解HTTP協定本身。 還有一個更有趣的問題,大家都說寫HTML的關鍵是語義化,那麼到底什麼是語義化呢?這個問題難住了不少同學。標籤要符合語義,這個答案看似簡單標準,但什麼樣的標籤才是符合語義?強調用 strong 不用 b?那如果有個外星文明,它們的語言裡 strong 相當於地球的 bold,bold 相當於地球的 strong,那麼它們究竟該用 strong 還是用 b?我們說 i 標籤是斜體的意思,那為什麼 fontawesome.io 拿它做 icon font 的標籤,這是不是“反語義”的? 過去很多地方農村有一種民間的染坊,製作染布的染料。這種染房裡面有一口很大的鐵缸,通常都要有一個身體非常強壯的工人拿一根很長的鐵棒在染缸裡面用力地敲擊,敲得越響,製作出來的染料顏色越鮮豔。 為什麼越用力敲打鐵缸染料就越好?染坊的人說這是祖祖輩輩傳下來的經驗,而事實上也是如此,真的染料的顏色和敲打用力有很大關係。直到有一天,一位從村裡走出去學化學的大學生,弄明白了原來只需要在染料中加適當比例的鐵屑,就能讓染料和含鐵元素氧化物產生化學反應而變得更鮮豔。原來祖祖輩輩傳下來的“儀式”實際上在真實原理面前只是一種信仰和宗教。同樣,如果我們不去瞭解技術的本質而止步于應用,那麼我們就只是技術宗教的信徒。所以在周愛民老師的《JavaScript 語言精髓與程式設計實踐》中說,電腦語言如同祭司手中的神杖,神杖換了,祭司還是祭司,世人還是會把頭叩得山響。祭司掌握了與神交流的方法,而世人只看見了神杖

由興趣選擇前端

在我學程式設計的最初,我學習的是C語言,然而整整一本書除了教我如何在黑洞洞的控制台上輸出 Hello World 和各種其他字元或者用鍵盤輸入一些什麼然後依然是字元輸出外,就沒有什麼其他的內容了。學習了一段時間之後,我的內心一度是崩潰的,因為我覺得這和我想得不一樣,學了那麼多知識,我都不知道自己究竟算不算是“學會”了C語言,因為在我看來,那些豐富多彩的作業系統和各種應用軟體和黑洞洞的控制台之間明顯還有著非常巨大的鴻溝。 事後回想起來,當時的想法當然是幼稚可笑的,那時候的我並不知道程式語言和運行環境之間的區別,對作業系統、使用者API、硬體介面、網路服務等等都完全不瞭解。然而這並不能怪我,因為C語言的教程並沒有任何一言半語來告訴我這一點,我也不知道學習了C語言的語法之後接下來還應該學習些什麼。 相對來說,Web開發更吸引我,因為不需要安裝任何環境,只需要在文字編輯器裡面輸入一些字元,保存後打開流覽器,馬上就能看到豐富的視覺效果,這就是前端的優勢,你所做的努力立即就能看得見。 相對於死板的輸入輸出,Web開發在介面可見的一層要豐富多彩得多,這一點吸引了我,如果這一點也能吸引你,讓你著迷,那麼你就適合學習前端。 在選擇前端作為職業之前,要明確判斷自己對前端開發的確感興趣,選擇做前端,應該是確認自己喜歡和適合做前端,而不是為了一份看起來體面而且薪水不菲的工作。如果你對構建豐富多彩的介面、處理各種交互邏輯不感興趣,甚至厭煩,那麼最明智的選擇是放棄成為前端工程師的想法 —— 因為選擇一個自己不喜歡的職業,為之忍受數十年直到退休,實在是一件很悲催的事情。

對在校學生,我們看重哪方面能力?

有同學問,360前端是否一定要求實際經驗的學生,在這裡我可以回答:否。 對於學生,我們比較關心的是: 基礎:包括數學、演算法、資料結構、電腦相關基礎的掌握。 學習能力和學習方法:如何學的前端,學了多久,學到什麼程度,遇到過什麼問題,是如何嘗試解決這些問題。 興趣:對前端的興趣如何,這一點可以體現在很多細節上。有一個反面的例子比較常見,一般來說我會問學生最近在關注什麼前端新知識,有的學生會說我關注某某某,但當我再問他究竟關注到什麼程度,會發現他實際上根本沒有在這項新知識上花費多少時間。如果你對感興趣的問題都不花費時間,如何證明你自己對前端的“興趣”呢。 解決問題的能力:遇到難題如何解決的,遇到沒接觸過的問題是如何思考和最終解決的。從這裡可以判斷出同學有沒有前端思維,這些問題沒有標準答案,我們不追求某些“官方思路”,看重過程而不是結果。 關於簡歷,有同學提到說現在似乎很多公司都希望學生會點 Node.js,會點 React,我自己不會該怎麼辦。 我想說的是,我們並不要求學生必須會這些。相反,我個人更鼓勵學生利用時間打好基礎。簡歷上寫自己真正擅長的內容即可,我們不會因為在你的簡歷上看不到 Node.js 或者 React 就忽略你。只要你真心熱愛前端並用心學了,你應該明白如何用前端基礎來打動我。有的學生喜歡在簡歷上堆砌詞彙,實際上這一點不見得好,因為如果你寫了一個你自己一知半解的東西,最後在面試中被面到了,一定會得負分的。 技術本身是有深度的,A 同學說“我知道React但沒用它做過東西”, B 同學說“我用 AngularJS 寫過一些個人的小項目”, C 同學說“我上個月使用彈性佈局的思路來寫我的博客,結果在 Android 系統 4.1 版本的 Webkit 流覽器下出現了一個顯示 bug,最後我是這樣這樣解決的”。你們說 A、B、C 三個同學我們會選擇哪個同學? 面試是一個彼此交流的過程,我們希望看到大家在前端領域的能力和潛力,“知道”一件事,並不是一種有價值的能力,尤其是在知識廉價的互聯網時代。我們的同學千萬不要像背書一樣去死記硬背一樣東西,而應該真正用心去學。我們的高等學校不僅僅教授大家知識,還有如何真正學習和做研究,不是嗎? 如果你對前端真的感興趣並有潛力,花點小心思,你該知道如何學習它。 最後,祝願大家都能成為優秀的前端工程師。

消息通信機制NSNotificationCenter

最近寫程序需要用到這類,研究了下,現把成果和大家分享。
NSNotificationCenter是專門供程序中不同類間的消息通信而設置的,使用起來極為方便,

設置通知,就是說要在什麼地方(哪個類)接受通知,一般在初始化中做。
我僅對以上參數做以說明:addObserver 這個是觀察者,就是說 在什麼地方接收通知;
 selector 這個是收到通知後,調用何種方法;
 name: 這個是通知的名字,也是通知的唯一標示,編譯器就通過這個找到通知的。
發送通知,就是說此時要調用觀察者處的方法。

我僅對以上參數做以說明:
postNotificationName:通知的名字,也是通知的唯一標示,編譯器就通過這個找到通知的。
object:傳遞的參數
發送通知時,默認調用test方法。

2017年1月5日 星期四

Ch09 - 介紹 Objective-C 繼承

Objective-C 程式設計學習筆記 Ch09

  • Ch09-介紹 Objective-C 的繼承。

目錄:

  • (1) 什麼是繼承 (inheriance)
  • (2) override 覆蓋
  • (3) super 關鍵字
  • (4) 抽象類別 (Abstract classes)

(1) 什麼是繼承 (inheriance)

什麼是繼承 (inheriance), 我大概分以下幾個方向去描述:
  • 什麼是繼承?
  • 繼承可以得到什麼?
恩還記得前幾個章節我們所寫的幾乎每一個類別,都是繼承自 NSObject 嗎? 複習一下 interface 界面檔的宣告方式:
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass
    //方法(Method)的宣告
@end
假如有個 Demo 類別的 superclass 是 NSObject,那我們就可以說 Demo 的父類別是 NSObject,而 NSObject 也有一個子類別是 Demo:
@interface Demo : NSObject{
    //....
}
@end
你可能為覺得好奇的是,為什麼總是繼承 NSObject? 這是因為在 Objective-C 中的根類別 (root class) 就是 NSObject 類別,除了它以外,其他所有的類別都有父類別 (可以說 NSObject 沒有父類別)。
因此 Demo 類別已經繼承 NSObject 了,再回到之前我們是如何建立個實體的,例如 :
Demo *test = [[Demo alloc] init];
假設我們並沒有在 Demo 類別宣告 alloc 以及 init 的方法,為何 Demo 可以使用這兩個方法?這原因就是因為 Demo 類別繼承 NSObject,繼承有個特性,子類別可以繼承父類別的所有實體方法跟變數

以下這張圖用來解釋繼承的關係:

當 Demo 繼承自 NSObject,也表示繼承了 NSObject 的實體方法跟實體變數,因此當我們要使用 Demo 建立一個物件時,直接使用的 alloc 或 init 都是繼承自 NSObject。(此圖只是舉例,實際上 NSObject 當然不可能只有這些實體變數跟方法)。
而若有新的類別繼承字 Demo 類別,那麼他一樣能夠使用 Demo 類別的實體變數跟方法 (ex: getResult)。 ch09-001
繼承就是建立一個新類別來 (1)擴充 或 (2)修訂原類別(父類別) 或 (3)直接使用父類的實體變數與方法 的功能,修訂其實就是我們接下來會講的複寫 override。
objective-C 沒有多重繼承,一個類別只能有一個父類別。

一個練習:

現在我們來寫兩個 class,其中第二個 class 必須繼承第一個 Class,第一個又必須繼承 NSObject,並且試著呼叫父類的方法以及實體變數。

//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
@end

@implementation Demo2
-(void) printDemo2{
    NSLog(@"Hi i am Demo2");
}
@end
程式部分:
Demo1 *first = [[Demo1 alloc] init];
Demo2 *second = [[Demo2 alloc] init];

[first printDemo1];
[second printDemo1]; //second 可以呼叫父類別 Demo1 的實體方法
[second printDemo2]; 
執行結果:
2014-01-18 11:53:53.470 ch07[7366:303] Hi i am Demo1
2014-01-18 11:53:53.472 ch07[7366:303] Hi i am Demo1
2014-01-18 11:53:53.472 ch07[7366:303] Hi i am Demo2
  • 甚至你在輸入 second 的方法字頭 prin 時,Xcode 的提示字元也會出現父類是 prin 開頭的方法: 

(2) override 覆蓋

覆蓋其實就是寫一個跟父類同名方法,子類別的方法會取代父類別的方法,這就是 override。被覆蓋的方法就不會再繼承父類的方法了,但其他沒有被覆蓋的方法一樣是具有繼承的特性。
我們無法透過繼承刪除/修改父類的實體方法,因此想要改變繼承而來的方法其內部實作如何,就使用 override 吧。
覆蓋沒有什麼特別的關鍵字,只要該類別的:
  1. 實體方法命名與想要覆蓋的父類方法同名即可。
  2. 該方法的回傳形態跟參數形態要一樣。

我們來改一下上一段 Demo1 與 Demo2 類別的範例,讓 Demo2 新增一個方法來 override Demo1 類別的 printDemo1 的方法:

//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
-(void) printDemo1;
@end

@implementation Demo2
-(void) printDemo2{
    NSLog(@"Hi i am Demo2");
}
//覆寫 override Demo1 類別的 printDemo1 方法
-(void) printDemo1{
    NSLog(@"Haha! i override you ;)");
}
@end
程式部分:
Demo1 *first = [[Demo1 alloc] init];
Demo2 *second = [[Demo2 alloc] init];

[first printDemo1];
[second printDemo1];
[second printDemo2];
執行結果:
2014-01-19 11:48:01.631 ch07[9839:303] Hi i am Demo1
2014-01-19 11:48:01.632 ch07[9839:303] Haha! i override you ;)
2014-01-19 11:48:01.633 ch07[9839:303] Hi i am Demo2

(3) super 關鍵字

還記得我們上一章 ch08 有學過一個 self 關鍵字,self 用在一個方法裡面要呼叫另外一個方法。現在要介紹的這個 super 關鍵字,用在一個方法裡面要呼叫父類別的方法
沒錯,我們現在又要來改 Demo1 以及 Demo2 類別 :P,改成當使用 printDemo2 的方法時也會呼叫 Demo2 父類別(也就是 Demo1) 的 printDemo1 方法 [super printDemo1]
//這是第一個 class Demo1,  繼承 NSObject
@interface Demo1 : NSObject
-(void) printDemo1;
@end

@implementation Demo1
-(void) printDemo1{
    NSLog(@"Hi i am Demo1");
}
@end

//這是第二的 class Demo2, 繼承 Demo1
@interface Demo2 : Demo1
-(void) printDemo2;
@end

@implementation Demo2
-(void) printDemo2{
    //在 printDemo2 內部呼叫父類別(Demo1) 的方法 printDemo1
    [super printDemo1];
    NSLog(@"Hi i am Demo2");
}
@end
程式部分:
Demo2 *second = [[Demo2 alloc] init];
[second printDemo2];
執行結果:
2014-01-19 12:21:32.551 ch07[10030:303] Hi i am Demo1
2014-01-19 12:21:32.553 ch07[10030:303] Hi i am Demo2

(4) 抽象類別 (Abstract classes)

抽象類別 (Abstract classes) 又稱為抽象超類別 (Abstract superclasses),至於為什麼叫做 Abstract superclasses,是因為 abstract classes 只是用來更方便地產生一個子類別而存在的類別,實際的實作功能是必須要由 subclass(子類別) 來完成的,因此 abstract classes 也被稱為 abstract superclasses,NSObject 就是一個例子。

Ch08 - 介紹 Objective-C 類別(2)

Objective-C 程式設計學習筆記 Ch08

  • Ch08-介紹 Objective-C 的類別相關的應用。
這章節只是更深入介紹一些關於類別的功能,像是...上一章節,我們已經學會將介面檔 (interface)、實作檔(implementation) 分開,並且學會實作 getter 以及 setter 方法,這章節我們會學到使用 @property 和 @synthesize 來實做 setter 和 getter 兩個方法。
另外我們會深入地使用一些方法,像是實作傳遞多個參數到一個方法,以及介紹兩個關鍵字的使用方式 static 以及 self 關鍵字。

目錄:

  • (1) @property 以及 @synthesize
  • (2) static 關鍵字
  • (3) self 關鍵字

(1) @property 以及 @synthesize

上一章節 Mobile 的類別,我們是自己寫 getCharge 以及 setCharge 方法這一組 getter 與 setter,分別是取得 chargeValue 以及設定 chargeValue。
現在只要使用 Objective-C 的 @property 以及 @synthesize 就可以幫我們達成這件事情,@property 寫在 interface 介面檔(ex: mobile.h),定義需要產生 getter 與 setter 的方法(注意,@property並不是寫在 @interface 宣告區塊裡,而是獨立的一個區塊),而 @synthesize 是寫在 implementation實作檔裡(ex: mobile.m),為指定的實體變數產生一組 gettet 與 setter 方法。

@property 的宣告方式:

使用 @property 時,就不需要在 interface 區段宣告一樣的實體變數了。比方說 interface 已經有宣告 int charge,而 @property int charge; 也已經宣告過一次了,因此可以不用重複宣告 charge (雖然有宣告也不會有什麼編譯上的錯誤)。
@property (型別) 實體變數或屬性
我們來改寫一下 mobile.h,你就會知道 @property 在 interface 介面檔如何使用: 這是目前的 Mobile.h:
因為註解有點多,我們把註解拿掉,以下是目前比較完整以及乾淨的 Mobile.h:
#import <Foundation/Foundation.h>

@interface Mobile : NSObject

@property int charge;
@property bool mobileStatus;

-(void) setShutdown;
-(void) print;

@end
  • 使用 @property 指令識別屬性 charge 以及 mobileStatus。
  • 而實體方法有 setShutdown 以及 Print。

@synthesize 的宣告方式:

@synthesize 其實也沒什麼特別的宣告方式,跟 @property 很像,只是 @synthesize 是寫在實作檔。
還有不要忘記為什麼要用 @synthesize,是因為 @synthesize 可以自動幫我們生成 getter 與 setter 方法,而且需要搭配 interface 介面檔中的 @property。但是在 Xcode 4.5 之後已經可以不用 @synthesize 了,只要使用 @property 就可以幫你產生 getter 與 setter,即便如此,我們的範例還是會使用 @property 搭配 @synthesize 來示範。
@synthesize 後面只需要接實體變數或是屬性就可以了:
@synthesize  屬性或實體變數名稱;
當你有多個實體變數或是屬性需要 @synthesize 時,也可以寫在同一行,用『,』隔開:
@synthesize 屬性或實體變數名稱, 屬性或實體變數名稱;
回到我們的 mobile.m 檔,接著我們要讓 setCharge, getCharge 方法以及讓 mobileStatus 使用 @synthesize 的方法產生 getter 與 setter,我們更動了一下 mobile.m 檔,已經被註解的地方表示已經不需要自己宣告的部分:
現在畫面有點亂,下面是已經整理過後的 mobile.m 檔:
#import "Mobile.h"

@implementation Mobile

@synthesize charge;
@synthesize mobileStatus;

-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", charge);

    if(mobileStatus==1){
        NSLog(@"Now your mobile shutdown");
    }else{
        NSLog(@"Now your mobile is on standby");
    }
}

@end

大功告成

方便的事情是,現在我們已經利用 @property 以及 @synthesize 幫我們完成自動產生 getter 以及 setter。可是,怎麼用呢? 當我們用 @synthesize 製作 charge 的 setter 與 getter 時:
  • getter 方法使用方式就是直接輸入方法名稱,如 [win_mobile charge]。
  • setter 方法使用方式就是 set+ charge,也就是 setCharge,如[win_mobile setCharge:85]。
getCharge 跟 setCharge 就是上一章我們自己寫的 getter/setter,透過 @synthesize,我們可以省去自己寫這段 code。
執行你的 main.m
你會發現當我們改寫 mobile.h 以及 mobile.m 檔,改為使用 @synthesize 的方式,完全不需要動到 main.m,若你現在再次執行 main.m,一樣可以執行。
#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}
你可以自己玩玩看有哪些變化跟差別: 在下面這個範例中,先是用 setCharge 改變 charge 的值為 40,然後 NSLog 使用 getter 取得 charge 的值。
#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:40];
        NSLog(@"charge value is %i", [win_mobile charge]);
    }
    return 0;
}

(2) static 關鍵字

若要講 static 關鍵字的用法,必須要回溯到之前講到的區域變數的概念,還記得區域變數只有在定義他們的方法中可以使用嗎?那麼如果你希望有一個區域變數也可以被其他的方法呼叫且改變其值,那該怎麼辦? 使用 static 關鍵字來宣告那個變數,就可以讓該變數仍然保有他的值。
如果你將某個變數宣告前面的修飾詞加上 static,那該變數就會成為靜態變數,例如宣告一個靜態變數 dountsNumber,初始值為 0 (使用 static 修飾詞的變數有沒有設定初始值都沒差,因為預設都會為 0)。
(其實我不太習慣稱 static 為關鍵字,這是很多翻譯書的問題,其實其他程式語言的 class 也會有 static,只是有些書翻 static 為靜態成員或是靜態變數都有)

我們來看一個簡單的例子,寫一個簡單的 Test Class :

這個類別只有一個方法 count,count 內部實作有一個 static 靜態成員 number,預設值為 0,只要呼叫(傳遞) count 這個方法給實體,就會將 number 的累加 1。
@interface Test : NSObject
-(int) count;
@end


@implementation Test
-(int) count{
    static int number;
    number++;
    return number;
}
@end

程式部分:

程式的部分非常單純,建立一個 firstTime 實體,NSLog 的值為呼叫 count 方法,取得 count 方法的回傳值:
Test *firstTime = [[Test alloc] init];
NSLog(@"now number is %i", [firstTime count]);

Test *secondTime = [[Test alloc] init];
NSLog(@"now number is %i", [secondTime count]);

執行結果:

2014-01-16 23:14:35.039 ch07[1481:303] now number is 1
2014-01-16 23:14:35.040 ch07[1481:303] now number is 2
你會發現 firstTime 以及 secondTime 一樣都是呼叫 count 的值,但 secondTime 所回傳的 number 的值卻是累加後的結果,這就是 static 關鍵字的用處,讓該區域變數被呼叫時能然保有其值 。

[燈泡]或許你把 static 關鍵字拿掉,就會知道結果的不同了,試試看!:

我們把 static int number 改成 int number=0,讓 number 變成非靜態成員;
@interface Test : NSObject
-(int) count;
@end


@implementation Test
-(int) count{
    int number=0;
    number++;
    return number;
}
@end
程式的部分如下:
Test *firstTime = [[Test alloc] init];
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);

Test *secondTime = [[Test alloc] init];
NSLog(@"now number is %i", [secondTime count]);
執行結果: 執行結果會發現,count 方法每次都是從初始值 0 開始累加 1,可是沒有保持其值,因為對於每次呼叫 count 方法以及 number++ 時,初始值都是 0,即便呼叫兩次 [firstTime count] 也是一樣,沒有使用 static 關鍵字就沒有保持其值。
2014-01-16 23:26:36.459 ch07[1552:303] now number is 1
2014-01-16 23:26:36.461 ch07[1552:303] now number is 1
2014-01-16 23:26:36.462 ch07[1552:303] now number is 1

改變 static 靜態變數的存取區域

承最原始的範例,既然我們是把 static 靜態區域變數宣告在 @implementation 區域的 count 方法裡面,因此只有利用實體的 count 方法才能存取 static 靜態變數,靜態成員 number 在這個範例中只是區域變數,如果你想要讓 static 靜態成員不夠透過實體方法也能改變其值,就必須將 static 靜態變數拉出方法的 @implementation 定義之外。

範例 (注意 static int number=0 的程式位置):

  • 類別宣告部分:
@interface Test : NSObject
-(int) count;
@end

static int number=0;
@implementation Test
-(int) count{
    number++;
    return number;
}
@end
  • 程式部分:
Test *firstTime = [[Test alloc] init];

//傳遞方法給實體
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);
NSLog(@"now number is %i", [firstTime count]);

//直接取得 number 的值
NSLog(@"result Number is %i", number);

//也能直接改變 number 的值
number = 10;
NSLog(@"result Number is %i", number);
  • 執行結果:
2014-01-16 23:44:05.051 ch07[1661:303] now number is 1
2014-01-16 23:44:05.053 ch07[1661:303] now number is 2
2014-01-16 23:44:05.053 ch07[1661:303] now number is 3
2014-01-16 23:44:05.054 ch07[1661:303] now number is 4
2014-01-16 23:44:05.054 ch07[1661:303] result Number is 4
2014-01-16 23:44:05.054 ch07[1661:303] result Number is 10

(3) self 關鍵字

既然講了 static 那就一起講個 self 關鍵字好了。其實大部分具有物件導向特質的程式語言,都有自己呼叫類別的方法或是呼叫父類別的方法。
而 self 關鍵字的 self 代表自己,嚴格來說應該是代表著當前方法的調用/呼叫者。self 用在一個方法裡面要呼叫另外一個方法。

舉例: 這是一個 Demo 的 class

setX: andY: 方法用來相加所帶入的兩個參數 (x,y),接著 setX: andY: 還會再自己呼叫 getResult 的方法([self getResult]),並且 return 相加的結果。
@interface Demo : NSObject
-(int) setX:(int) x andY:(int) y;
-(int) getResult;
@end


@implementation Demo
{
    int xvalue,yvalue;
}
-(int) getResult{
    return xvalue + yvalue;
}

-(int) setX:(int) x andY:(int) y{
    xvalue = x;
    yvalue = y;
    return [self getResult];
}

@end
程式部分:
Demo *testAdd = [[Demo alloc] init];
NSLog(@"testAdd 10 + 18 = %i", [testAdd setX:10 andY:18]);
執行結果:
2014-01-17 21:30:44.296 ch07[6254:303] testAdd 10 + 18 = 28
另一個關鍵字為 super,代表呼叫父類別的方法,我們會在下一章節『繼承』介紹。

下一章節,將會介紹 Objective-C 繼承 。

Ch07 - 介紹 Objective-C 類別(1)

Objective-C 程式設計學習筆記 Ch07

  • Ch07-介紹 Objective-C 的類別。
其實這章不只講類別,還會提到物件導向的觀念,實體以及方法,以及會回到 ch01 所提及的 Objective-C 承襲 Smalltalk 的訊息傳遞模型 (message passing),如何使用方法應用於類別與物件。下一章節是類別(2)。
物件其實是有點抽象複雜的概念,我的程度大概就只能表達到這樣的水平了,我一直不曉得到底該怎麼表達會比較好,希望大家多給些意見。

目錄:

  • (1) 什麼是類別
  • (2) 實體 (Instance) 與方法 (Method)
  • (3) 類別(class)的定義與實作
  • (4) 存取實體變數與資料封裝

(1) 什麼是類別

類別 (class) 是由實體變數跟可存取變數的方法 (method) 所組成,可以透過定義該類別,建立該類別的實體(Instance) 或物件 (Object)。
假如有個類別叫做 Human,我們可以說 win 是 Human 的實體 (Instance),同理 Irene 以及 Eric 也是(此例人名僅是舉例),每一個 Human 的實體 (Instance) 又稱為物件。 001.png

(2) 實體 (Instance) 與方法 (Method)

類別或實體可以進行的行為,稱為方法 (Method),方法有實體方法 (instance method) 也有類別 (class method) 方法。
方法名稱說明
類別方法由類別所呼叫的方法,其方法的宣告前面都會加上 + 號。
實體方法由類別的實體所觸發的方法。
比方說所有的 Human 的實體,都會走路 (walk), 喝水 (drink), 吃東西 (eat),這些方法都稱為實體方法。
每個人被生下來的過程其實就是產生一個 Human 類別的實體,而該實體的性別會是女生 (becomeGirl) 還是男生 (becomeBoy),這是類別方法。

如何呼叫方法

假設有個 Class 為 Mobile,Mobile 有幾個方法,比方說打電話 (call), 充電 (charge), 取得現在的電量 (getCharge) 都是。 002
如何呼叫方法,你可以之前學過其他語言呼叫方法的方式會是這樣子: obj->method(argument);。但在 Objective-C 中,做法不太一樣,假如有一個類別或實體要呼叫一個方法 (或者說傳遞訊息給類別或實體),其語法如下:
[ClassOrInstance method];
左邊是類別或是實體的名稱(ClassOrInstance),右邊為方法(method)的名稱。 另外,因為有些方法是可以傳遞參數的,因此語法也可以是這樣:
[ClassOrInstance method: argument];
要使用這些方法之前,必須要有一個物件作為對象,比方說我們先取得一隻手機 : mobile 是 class(類別名稱), 而 new 是類別方法,表示取得一支新的手機。
myMobile = [mobile new];
接下來我要讓 myMobile 做充電的動作,因此需要呼叫 charge 這個實體方法:
[myMobile charge]
由於 Objective-C 承襲 Smalltalk 的訊息傳遞模型 (message passing),比較好一點的說法應該要說,發出一個 charge 的訊息給 myMobile 物件,charge 就是訊息 (message),而 myMobile 就是訊息的接收者。myMobile 收到 charge 這個訊息之後,就看 charge 的這方法的內部如何定義實作。
接著,假如 charge 這個方法可以接受參數,該參數代表充電到幾 % 就結束充電,假設充電到 70% 就停止充電,其語法表示為:
[myMobile charge : 70];
假設我的朋友 Eric 也有一隻 Mobile,我們可以說 ericMobile 也是 Mobile 的實體 (instance)。
ericMobile = [Mobile new];
因此同樣都是 Mobile 的實體, Eric 的手機也具有相同的方法,像是打電話,充電等等。
[ericMobile charge];

想想 Mobile 這個類別還有哪些方法?以及舉例使用方式

方法名稱說明
[myMobile call : eric]myMobile 這的實體打電話給 Eric
[myMobile charge : 100]myMobile 充電到 100%
[myMobile shutdown]myMobile 關機
......

實體/物件的狀態(state)

實體所呼叫的方法,可以改變物件的狀態,比方說目前手機的剩餘的電量就是一種狀態,如果使用 charge 方法,並且還有設定參數,比方說參數設為 100 ,[myMobile charge : 100],就表示要將手機充電到 100%,當方法執行完成時,剩餘的電量狀態,也會被更變為 100。
另外手機目前是開機的,這也是一種狀態,當你傳遞(呼叫)關機的訊息(方法)時 [myMobile shutdown],手機的狀態就會變成關機。

(3) 類別(class)的定義與實作

  • Objective-C 的類別必須定義介面(interface)與實作(implementation)兩個部分。
  • 類別定義檔案的副檔名沿襲 C 語言的設計,介面檔 (interdace) 以 .h 為副檔名,實作檔案(implementation)以 .m 為副檔名。(在剛開始介紹時還不會介紹如何拆開介面檔與實作檔,會暫時先寫在同一支程式做介紹)。
  • 以下範例,我們會來如何定義一個 Mobile 的類別 (Class),分為以下幾個方面介紹:
    • interface 區塊
    • implementation 區塊
    • 使用/建立 Mobile 的實體(物件)
    • 利用 Xcode 建立 Mobile 的 Class

在開始之前,請先使用 Xcode 建立一個新的 Project,我的 Project Name 取名為 ch07。 (如何建立可參考 Ch02 的做法,在此不再多做描述)。
建立好新的 Project 之後,點擊 main.m 檔案(接下來我們會覆蓋預設產生的 code): ch07-003.png

interface 區塊

編輯 main.m,首先 interface 介面檔的定義,格式如下:
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass{
    //屬性(Property)或實體變數
}
//方法(Method)的宣告
@end
如果你沒有屬性/實體變數要宣告,可以省略大括號 { } :
// @interface  介面檔定義
@interface 類別名稱ClassName : superclass
    //方法(Method)的宣告
@end
interface
當你鍵入 @inter 時,xcode 會幫你產生提示字元,只需要按 tab 鍵即可,會幫你補足順序第一位的提示文字(或是你有選擇其他的),再接續按下一個 tab,則會跳到下一個提示字元(ex: @interface->(tab) class name -->(tab) superclass)
以下是目前的程式碼:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int) getCharge;
    -(void) setShutdown;
    -(void) print;
@end
我們定義了一個名為 Mobile 的類別 (Class),@interface Mobile: NSObject,其 : 後面所接的 superclass,代表 Mobile 類別繼承哪一個 Class (繼承的詳細內容會在下章提到)。目前 Mobile 繼承 NSObject,我們可以說 Mobile 的父類別是 NSObject,而 NSObject 有一個子類別是 Mobile,繼承的部分在此先知道這樣就可以了。
至於你可能會疑惑為什麼我們可以在這裡用到 NSObject,這是因為我們已經 import Foundation 的檔案了,Foundation.h 已經有幫我們載入 NSObject 的介面檔了(NSObject.h),你可以檢視 Foundation 的資料夾,就可以看到 Foundation 包含哪些介面檔。
foundation
接著我們在 @interface 區塊定義了我們這個類別所需要的實體方法。
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
  • 各個方法的功能:
    • setCharge 決定充電量要到多少
    • getCharge 取得目前的充電量
    • setShutdown 讓手機關機
    • print 用來印出一些手機狀態的 NSLog
通常在方法的宣告前面,如果是『-』減號,代表該方法為實體方法(instance method)。而『+』加號開頭的,代表該方法為類別方法(class Method)。因為我們的方法都是針對實體的行為,像是充電, 關機,透過實體方法改變實體的一些屬性等等,因此將這些方法定義為實體方法。類別方法是針對類別本身的行為,像是建立一個新的類別,暫時我們的範例還遇不到需要定義類別方法。
(void) 代表剛方法執行後的回傳值的形態,形態有很多種,void 只是其一,而 void 所指的就是『沒有回傳值』。
在 Objective-C 中,回傳一個值會用 return 回傳值;,如果沒有回傳值,你可以只寫 return; 即可。
假如今天我們宣告一個實體方法是需要回傳 int 型別的值,那麼你的宣告可能會是這樣: -(int) getCharge; ,也就是 getCharge 這個方法必須回傳一個 int 的數字。
接下來,我們要為我們所宣告的實體方法做一些改變,目前的實體方法,可以有一些改變,比方說充電,我們可以決定只要充電到多少 % 就可以停止,因此我們可以為我們所定義的方法,增加參數,而充電要充到多少 % ,這個數值就是該方法的參數。

帶有參數的方法可以這樣定義:

-(void) setCharge: (int) n;
此圖比對定義實體方法有無參數的差別:
ch07-007
現在我們已經將 setCharge 這個實體方法定義為,沒有回傳值(-(void)),而且可以接受一組 int 型別的參數(:(int) n),該參數名稱定義為 n (n是我們自己定義的,你也可以取其他的名字),而且 n 這個參數可以被 setCharge 這個方法所使用。

你可能會問,只能有一個參數嗎?

其實不然,參數可以不只有一個,假設今天有個日期的類別,其中有個方法要你設定一段日期的期間,那個就需要兩個參數,一個是起始日期,另一個是結束日期,你的實體方法可能會是這樣:
-(void) dateFrom: (int) n dateTo: (int) m;
口語上你可以說這是一個 dateFrom:dateTo: 方法。
interface 的介紹就先到這個部分,以下是目前 main.m 的完整版本,寫完介面檔之後,接下來就是要來定義這些方法如何實作,也就是進到下一個階段,implementation 實作檔階段。
main.m 檔:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end

implementation 區塊

撰寫 implementation 實作檔,必須要實作或定義你在介面檔所宣告的方法,在實作檔中,也會定義這個類別有哪些變數或是資料成員,這些變數就是實體變數(instance variable)。講直接一點就是,在 interface 介面檔你會定義你需要哪些方法,至於這些方法怎麼做事情,是寫在實作檔。
// @implementation 實作檔定義
@implementation className
{
    //資料成員定義 (Member Declarations)
}

//實作定義在 interface 區塊的方法 (methods)

@end
實作檔的定義一樣要給類別名稱 (className)(ex:@implementation Mobile),這裡的類別名稱要跟 interface 介面檔的一樣。
順帶一提,如果你的 implementation 實作檔如果少寫了 interface 所定義的方法,是會被警告的,你會得到 Method definition for 實體方法名稱 not found 的訊息。 
什麼是資料成員?其實就是實體變數,比方說充電量的值,會隨著 call 方法是否被呼叫而改變,這也算是一種資料成員,與該實體有關,能夠被存取,改變狀態的資料。
定義資料成員會被 { } 大括號所包覆,在這個 { } 裡所定義的變數就是實體變數,當你建立一個新的實體時,該實體的實體變數也會被建立。假設我們有個實體變數稱為 chargeValue,表示現在的充電量,如果有個新的實體稱為 win1mobile,有另一個實體稱為 win2modile,那麼 win1mobile 與 win2mobile 都會有各自的 chargeValue。
在 implementation 實作檔中方法(method)的定義,跟 interface 宣告實體變數跟方法的方式很像,一樣是先用『+』,『-』加號或減號宣告是類別方法還是實體方法,接下來使用 ( ) 小括弧宣告方法的回傳值的型別,然後接上方法名稱,若該方法有帶參數的話,一樣是需要使用 :(參數型別) 參數名稱,接著宣告的程式內容,{ } 左右括號內的程式就是實作該方法的程式。

以下是我們為 Mobile 這個類別 (class) 所寫的 implementation,不要衝動,看完再說 :

// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end
Mobile 這個類別的實作檔有兩個實體變數,分別是 chargeValue 以及 mobileStatus,chargeValue 用來儲存充電量的值,而 mobileStatus 型別是布林值(Bool),這個值用來儲存 Mobile 開/關機的狀態,因為開關這種狀態只有正反兩面的值,不是有就是沒有,因此宣告為布林值得形態。
接著是好幾個方法的宣告,方法的前面使用 『-』 減號,因為這些方法屬於實體方法。
  • setCharge 方法可以帶一個 int 型別的參數,此方法的內部實作就是將 chargeValue 的值改變為你所輸入的參數值。
  • getCharge 方法可以用來取得 chargeValue 的值(return chargeValue)。
  • setShutdown 方法可以改變 mobileStatus 的值,1 則為是/開機/YES。
  • print 方法用來 NSLog 出 chargeValue 以及 mobileStatus。
萬事俱備了,下一段教你怎麼用 Mobile 類別,如何產生一個 Mobile 的實體(物件),並且使用實體的方法。 以下是目前完整的 main.m 檔:
#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end


// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end

使用/建立 Mobile 的實體(物件)

我們繼續編輯 main.m,接下來我們會在 main 這個 function 寫我們的程式:
int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile print];
    }
    return 0;
}

(1)建立記憶體空間 (alloc) 與初始化 (init)

我們在 main 函式定義一個 win_mobile 的變數,而 win_mobile 就是 Mobile 的實體。
Mobile *win_mobile 是建立一個名為 win_mobile 的 Mobile 物件(實體)。* 表示該物件變數為一個指標 (pointer)。指標只儲存該物件的記憶體位置而不是物件本身。
宣告一個物件為指標只是指向一個記憶體位置,但不會自動初始化這個物件(實體),你會發現 Mobile *win_mobile 中間沒有任何等號 (=),他跟一般變數的指派運算子不一樣,用*來表示右邊的物件是建立的來源,如果你沒有指定 * 後面的物件,指標會不知道要指向給誰。
[Mobile alloc] 代表傳遞 alloc 這個訊息給 Mobile 這個類別,產生一個 Mobile 的實體並且分配記憶體空間給 win_mobile,alloc 這個方法重點在於分配記憶體空間。
除了要使用 alloc 分配記憶體空間之外,還要初始化 (init) 這個實體,所以必須要使用 init 這個方法,這樣你才有辦法正常的使用這個 win_mobile 實體。
alloc 是 allocate 的縮寫,英文為分配的意思。
Mobile *win_mobile =[[Mobile alloc] init];
其實上面這段程式也相當於這三段程式: (或許這樣看會比較好理解)
//將 win_mobile 變數的位置指定給 Mobile
win_mobile *Mobile;
//分配記憶體空間
win_mobile = [Mobile alloc];
//初始化 win_mobile 的實體
win_mobile = [Mobile init];
我們沒有宣告 alloc 以及 init 方法,為什麼有這兩個方法可以使用?
因為 Mobile 的 superClass(父類別) 是 NSObject,因此 alloc 以及 init 這兩個方法是繼承字 NSObject,我們會在後面繼承的章節再次遇到。
* 星號是一種有特殊意義的符號,在 Objective-C 中稱為指標 (pointer),指標是用來參考(reference) 其他物件或資料形態的值。
指標(Pointer)與地址(Address)的觀念: 我們可以宣告一個變數,該變數存的值是一個記憶體的位置,用 * 號表示,宣告指標變數時會這樣表示 : TypeNname *pointer; ,像是 win_mobile *Mobile 就是 win_mobile 這個變數儲存的值是 Mobile 類別的記憶體位置,我們也可以說 win_mobile 是一個指標變數。指標會在後面章節會再遇到,在此先知道這樣就可以了。
還有一種運算子叫做取址運算子(&),用來取得一個變數的記憶體位置。

(2)還記得 Objective-C 是如何呼叫方法的嗎?

[receiver message]
左邊是訊息的接收者,右邊是傳遞給接收者的訊息。
因此我們想要傳遞 setCharge 這個訊息 (方法) 給 win_mobile 這個實體,就會這樣表示: 另外,之所以可以傳遞參數 85 這個數值,也是因為我們在 interface 以及 implementation 有宣告此方法可以帶一個 int 型別的參數。
[win_mobile setCharge:85];
同樣的,如果要傳遞其他方法也是一樣 :
[win_mobile print];
我們 build 一下現在的程式,看看會有什麼結果,點按 xcode 左上角的 build 按鈕:
以下是執行結果的 Log: 我們可以看到第一行 Now yoy charge value is 85,這句 NSLog 是經由 print 方法所實踐的,而因為我們在 print 方法之前有先呼叫 [win_mobile setCharge:85]; ,因此我們已經改變了 chargeValue 的值。
第二行 Log : Now your mobile is on standby,顯示現在手機的開關機狀態,因為我沒有呼叫 setShutdown 方法,因此還是待機狀態。
我們來呼叫一下 setShutdown 方法,如此一來 Log 的結果就會不一樣了 :
Mobile *win_mobile =[[Mobile alloc] init];
[win_mobile setCharge:85];
[win_mobile setShutdown];
[win_mobile print];
Log 結果:

以下是目前 main.m 的完整程式碼,通常我們不會把一個類別的 interface 以及 implementation 以及使用它的程式寫在同一隻檔案,下一段教你如何建立 class 類別檔,拆分為 .h 檔以及 .m 檔。
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

// @interface  介面檔定義
@interface Mobile : NSObject
    -(void) setCharge :(int) n;
    -(int)  getCharge;
    -(void) setShutdown;
    -(void) print;
@end


// @implementation 實作檔定義
@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
      NSLog(@"Now your mobile shutdown");
    }else{
      NSLog(@"Now your mobile is on standby");
    }
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}

利用 Xcode 建立 Mobile 的 Class

這個階段,我們要把寫在 main.m 檔的 Mobile 類別製作成類別檔。
  • 第一步,選擇 『File』->『New』->『File...』 
  • 第二步,選擇 Objective-C class,然後點選『Next』。 
  • 第三步,輸入你要建立的類別(class)名稱,輸入 Mobile,接著點按 『Next』。
  • 第四步,選擇檔案儲存的路徑,通常我是按照預設的位置(project的資料夾底下)。
  • 完成後,你會看到你的目錄已經出現 mobile.h 以及 mobile.m 檔。 

編輯 mobile.h

把剛剛寫在main.m 的 interface 區段移到 mobile.h。 以下是 mobile.h:
//
//  Mobile.h
//  ch07
//
//  Created by win on 2014/1/12.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Mobile : NSObject
-(void) setCharge :(int) n;
-(int)  getCharge;
-(void) setShutdown;
-(void) print;
@end

編輯 mobile.m

把剛剛寫在 main.m 的 implement 區段移到 mobile.m。 以下是 mobile.m:
//
//  Mobile.m
//  ch07
//
//  Created by win on 2014/1/12.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import "Mobile.h"

@implementation Mobile
{
    //資料成員定義 (Member Declarations)
    int chargeValue;
    bool mobileStatus;
}
-(void) setCharge :(int) n{
    chargeValue = n;
}
-(int) getCharge{
    return chargeValue;
}
-(void) setShutdown{
    mobileStatus = 1;
}
-(void) print{
    NSLog(@"Now your charge value is %i", chargeValue);

    if(mobileStatus==1){
        NSLog(@"Now your mobile shutdown");
    }else{
        NSLog(@"Now your mobile is on standby");
    }
}

@end

編輯 main.m

既然剛剛把 mobile 的 interface 以及 implenemt 都搬到 mobile.h 以及 mobile.m 了,現在 main.m :
不過現在 main.m 出了一點問題,因為他不知道 win_mobile 要初始化的類別 Mobile 是誰,因為我們少了 import Mobile 這個類別(class),因此我們要在 #import 下面多 #import ”mobile” 的 interface (介面檔)。
修改後的 main.m:
//
//  main.m
//  ch07
//
//  Created by win on 2014/1/9.
//  Copyright (c) 2014年 win. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Mobile.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Mobile *win_mobile =[[Mobile alloc] init];
        [win_mobile setCharge:85];
        [win_mobile setShutdown];
        [win_mobile print];
    }
    return 0;
}
為什麼引入 Foundation 是這樣寫 #import ,而引入 Mobile.h 卻是要用雙引號刮起來(#import "Mobile.h")?會有這樣的差別是因為 Foundation 是系統檔案,而 Mobile 類別是你自己客製化產生的類別 (class)。

(4) 存取實體變數與資料封裝

每個實體/物件都可以存取自己的實體變數,但是類別方法不能夠『直接』存取實體變數,因為類別方法沒有辦法處理自己產生的實體/物件,類別方法只能用在類別本身。
基本上每個實體內的資料都是私有的,你可以當作在 implementation 宣告的變數都是私有變數:
@implementation ClassName {
  int memberVar3; //私有實體變數
}
也正因為類別方法不能直接存取實體變數,因此會有一些實體方法提供取得實體變數的方法出來,比方說 getCharge 方法就是一種 getter,setCharge 就是一種 setter,你會常常在實體方法的宣告中看到用 set 開頭或是 get 開頭的方法,不論是 setter 或是 getter 這些都是 Accessor method (有些書會翻譯為『存取器』或『存取方法』) 。然而 setter 方法通常是用來設定或改變某些實體變數的方法,因此 setter 通常不會需要回傳值,因為他的角色是用來改變某些值; 而 getter 用來取值,因此 getter 是需要有回傳值的。

為什麼需要製作這些 getter 跟 setter 方法?

這是因為你無法從類別以外取得/改變實體變數的值,因此可以透過 setter 與 getter 讓外界取得/改變某些實體變數的值,這就是資料封裝的概念。(有一些文章有寫道其實 getter 與 setter 就是提供獲得實體對象屬性值的API。)
資料封裝的英文為 Data Encapsulation,這個字源自於英文的『capsule』的膠囊,你只需要知道某顆膠囊的功能跟服用方式,可是你不用知道膠囊是如何製作的。因此封裝的概念其實就是資訊隱藏,將資料 (屬性) 與方法 (method) 封裝在物件/實體中,而提供訊息傳遞的方法就是 getter 與 setter。

好物 @property 以及 @synthesize

如果你覺得總是要自己寫存取某些實體變數的 getter 與 setter 很麻煩,那麼下章節會介紹到 Objective-C 提供的合成存取方法,利用 @synthesize 自動產生 getter 與 setter。

下一章節,將會介紹 Objective-C 類別(2) 。

類別(2) 會介紹如何傳遞參數到方法, static 與 self 關鍵字, 合成存取方法(@synthesize)等等。