Chapter 14 iPhone 平台簡介
大綱 iPhone簡介與OS介紹 建立開發環境 開發工具介紹 實作範例 iPhone 相關學習資源 課後習題
前言 近年來,智慧型手持裝置不停的推陳出新,各家平台大放異彩百家爭鳴,連 Apple 也宣布在 2008 年 6 月在美國開始販售 iPhone,並成功的創造出新的手機產業,讓開發者透過 App Store 販售自己撰寫的軟體,而讓使用者透過網路軟體商店進行付費與下載,創造了數個個人創業的成功史。不只如此,連過去在 PC 上的遊戲產業也紛紛移植其下的遊戲軟體到 iPhone 上進行販售,吸引了越來愈多的開發者投入於 iPhone 手機應用程式的開發。
iPhone OS 介紹 iPhone OS 乃基於 Mac OS X 作業系統,為蘋果為行動裝置平台量身打造的作業系統 提供 iPhone Software Development Kit (SDK),供開發者方便且快速的在 iPhone、iPod touch、iPad 上撰寫應用程式。
iPhone OS 乃基於 Mac OS X 作業系統,為蘋果為行動裝置平台量身打造的作業系統 Mac OS X 與 iPhone OS Cocoa Media Core Services Core OS Mac OS X iPhone OS Cocoa touch Media Core Services Core OS iPhone OS 乃基於 Mac OS X 作業系統,為蘋果為行動裝置平台量身打造的作業系統
iPhone OS 由下至上依序分成四層各司不同功能,越底層越接近硬體 Core OS & Core Services Layer 系統核心 Cocoa touch Media Core Services Core OS Core OS & Core Services Layer 系統核心 檔案系統存取 低階的資料型態 Bonjour Socket 由C語言撰寫而成
這層的相關技術有些是用C語言撰寫,有些則是由Objective-C所撰寫 iPhone OS 由下至上依序分成四層各司不同功能,越底層越接近硬體 Cocoa touch Media Core Services Core OS Media Layer 支援2D、3D繪圖、音視訊格式等技術 這層的相關技術有些是用C語言撰寫,有些則是由Objective-C所撰寫
分層的好處讓開發者僅需專注於學習如何利用 iPhone SDK,撰寫高階語言建構其應用程式,而毋須考慮底層的運作與執行 iPhone OS 由下至上依序分成四層各司不同功能,越底層越接近硬體 Cocoa touch Media Core Services Core OS Cocoa Touch Layer 提供許多應用程式最基本的框架(framework),讓程式能夠進行檔案管理、網路操作、使用加速器等功能 由Objective-C語言撰寫 分層的好處讓開發者僅需專注於學習如何利用 iPhone SDK,撰寫高階語言建構其應用程式,而毋須考慮底層的運作與執行 (當不考慮效能或是建構系統程式時)
若要將app佈署於實機或於app store進行販賣,需註冊 iPhone Developer Program (USD 99/Year) 瞭解 iPhone OS 基本概念後,欲進行 iPhone 開發 物件導向程式語言的基礎 學習Objective-C & Cocoa touch 在iPhone Dev Center 註冊使用者ID後,下載且安裝 iPhone SDK app的建置與執行僅能夠於模擬器上執行 若要將app佈署於實機或於app store進行販賣,需註冊 iPhone Developer Program (USD 99/Year)
iPhone 開發環境 有台 MAC OS X 電腦 安裝 Xcode 與最新版的 iPhone SDK 本教材的開發環境 Mac OS X 10.6 Snow Leopard Xcode 3.2.1 iPhone SDK 3.1.2
iPhone SDK下載與安裝 (1/6) 連到 iPhone Dev Center,網址 http://developer.apple.com/iphone/
iPhone SDK下載與安裝 (2/6) Register as an Apple Developer
iPhone SDK下載與安裝 (3/6) 依照指示註冊一個 Apple ID
iPhone SDK下載與安裝 (4/6) 註冊成功登入後,會見到下載畫面
iPhone SDK下載與安裝 (5/6) 下載成功後,會在下載位置見到此映像檔圖示 點選兩下後,選擇 iPhone SDK and Tools for Snow Leopard進行安裝
iPhone SDK下載與安裝 (6/6) 依安裝程序,依序同意許可證、遵守iPhone SDK License、選擇安裝位置、選擇安裝工具及欲安裝的 SDK 版本 若看到成功安裝,表示iPhone SDK安裝完成
最後一項已經解禁,例如可透過 3G 網路進行 skype 語音通話,但 skype 會對使用者另外收取費用 iPhone SDK Agreement 開發者有幾點限制需要注意 僅可使用 Apple 公開的 API 進行程式開發 未經使用者允許,不可隨意公開使用者的現有位置 錄音或錄影前,須提醒使用者 不可開發即時自動導航系統 應用程式不得包含情色字眼、不雅內容或違反犯罪等行為 VoIP 應用程式僅可透過 Wifi 使用,不得透過電話網路使用 最後一項已經解禁,例如可透過 3G 網路進行 skype 語音通話,但 skype 會對使用者另外收取費用
開發工具路徑 在路徑 /Developer/Application 下可見剛剛安裝的開發工具
模擬器路徑 在路徑下可看到 iPhone 模擬器 /Developer/Platforms/iPhoneSimulator.platform/Developer/Applications
開發工具 安裝 iPhone SDK 後,包含下列幾種開發工具 其中前三項工具較為常用,建議可拉至 Dock 上方便使用 Xcode Interface Builder iPhone Simulator Instruments Dashcode 其中前三項工具較為常用,建議可拉至 Dock 上方便使用 Xcode Interface Builder iPhone Simulator
Xcode Xcode 是在開發 iPhone 應用程式時會使用到的整合開發環境 (Integrated Development Environment,簡稱 IDE),提供開發人員 圖形化介面的文字編輯器(text editor) 編譯器(compiler) 除錯器(debugger) 自動生成工具 尚提供各式樣板(template)建立應用程式
應用程式樣板 Navigation-based Application OpenGL ES Application Tab Bar Application Utility Application View-based Application Window-based Application
Navigation-based Application OpenGL ES Application Tab Bar Application Utility Application View-based Application Window-based Application 使用 UINavigationController,建立含有導覽列的視窗程式,使用者可在畫面中進行上下頁切換
OpenGL ES Application Navigation-based Application Tab Bar Application Utility Application View-based Application Window-based Application OpenGL ES (OpenGL for Embedded Systems)是 OpenGL 三維圖形 API 的子集,係針對嵌入式系統如手機所設計,提供標準的圖形程式函式庫。選用此樣板會引入 OpenGL framework,適合開發畫面多樣化的遊戲
Tab Bar Application Navigation-based Application OpenGL ES Application Utility Application View-based Application Window-based Application 使用 UITabBarController,顧名思義,會建立含有頁籤的應用程式,讓使用者可以在不同頁籤中進行切換
Utility Application Navigation-based Application OpenGL ES Application Tab Bar Application Utility Application View-based Application Window-based Application 此樣板會產生一個 i 的按鈕,按下後會進行頁面翻轉,具有前端頁面與後端頁面
View-based Application Navigation-based Application OpenGL ES Application Tab Bar Application Utility Application View-based Application Window-based Application 使用 UIViewController,並產生一個灰色背景的 UIView,開發者可以在 View 上面添增元件。
Window-based Application Navigation-based Application OpenGL ES Application Tab Bar Application Utility Application View-based Application Window-based Application 使用 UIWindow,產生一個空白的頁面,適合開發者重新打造應用程式畫面
Xcode 的使用與介紹 以下將透過範例介紹 Xcode 介面與使用方式
使用 Xcode 新增專案 選擇 Create a new Xcode project
建立View-based Application
為新專案命名
若程式 (app) 尚未編譯,執行檔會呈紅色 專案建立畫面 若程式 (app) 尚未編譯,執行檔會呈紅色
Group & Files (檔案目錄架構)
Prefix header file 的功用在於不需要每個檔案都引入一些必須的標頭檔且各自編譯 檔案目錄架構 (1/2) Classes 放置應用程式會使用到的 標頭檔 (header file, *.h) 實作檔 (implementation file, *.m) Other Sources 目錄下會看到 “專案名稱_Prefix.pch” 的檔案 此例為 Xcode_Example_Prefix.pch 開啟程式碼會發現程式裡引入了兩個檔案 Foundation.h UIKit/UIKit.h Prefix header file 的功用在於不需要每個檔案都引入一些必須的標頭檔且各自編譯
檔案目錄架構 (2/2) Resources Frameworks Products 其他檔案 目錄下會看到 Interface builder file(*.xib),另外一些資源檔案,像是聲音檔、圖像檔也都會放在此目錄下 Frameworks 目錄下會放置程式用到的相關框架(framework) UIKit.framework Foundation.framwork CoreGraphics.framework 當需用到其他框架(framework)時,請置放於此目錄下 Products 此專案編譯後的執行檔(*.app),即最後會安裝到 iPhone 上的檔案 其他檔案 Info.plist (描述程式的相關 meta information) main.m (程式開始執行的 main function)
編譯與執行 通常專案一開始連程式碼都還不需要撰寫,即可執行 選擇編譯後於模擬器上執行,並按下 Build and Go 1 2
執行後的畫面 編譯後,則會跳出 iPhone 模擬器 程式隨即載入並執行 以上為 Xcode 工具的使用與介紹
iPhone 模擬器 模擬程式執行的情形,方便開發者直接在模擬器上觀看結果,不需要立刻佈署到實機上執行。除了有些硬體裝置有所限制之外,例如 GPS 定位、加速度計 (Accelerometer)、錄影照相功能只能透過實機執行之外,大部分的功能幾乎都能夠模擬。
iPhone 模擬器 若執行的電腦有連接網路,模擬器則可透過電腦上網 同實機的操作方式,直接以滑鼠點選即可進入所選的應用程式執行,要離開則按下 home 鍵即可 使用(Command + →)或是(Command + ←)可旋轉模擬器,模擬直立或橫立的畫面 若要刪除應用程式,在畫面上按住滑鼠左鍵不放,桌面上的程式圖示會開始抖動,此時直接點選要刪除的應用程式即可 雖然模擬器無法模擬照相功能,但是仍能將圖片置入,可在電腦上任選一張圖片,將圖片拖曳到模擬器中,在圖片上按住滑鼠左鍵不放,選擇 Save Image 後即可在照片程式中看到剛剛拉進來的圖片
Interface Builder 幫忙產生 iPhone UI 與進行元件佈置 (layout) 的工具
副檔名為 .xib 表示為 Interface Builder 工具的圖形界面設計檔案 Xcode_ExampleViewController.xib 先前的 Xcode Example 範例中,Resources資料夾底下有個Xcode_ExampleViewController.xib 副檔名為 .xib 表示為 Interface Builder 工具的圖形界面設計檔案
點擊並開啟 .xib 檔案後
熟悉 Interface Builder 以下作個範例練習 作品如右圖所示 熟悉 Interface Builder 的使用 佈置文字標籤(Label)與圖像(Image View)
佈置畫面 透過 Library (UI Class) ,將元件拖曳至畫面(View)中,在此會用到兩個元件,佈置如右 Label (UILabel) Image View (UIImageView)
修改 Label 屬性 於 View 中先點選 Label 後,由 [Tools] → [Attributes Inspector] 叫出屬性面板 修改 Label 的文字內容為 Welcome to iPhone Programming Attributes Inspector 提供元件屬性設定的面板,如右為文字標籤屬性設定面板,可自行練習更改屬性觀察變化
拖曳圖檔並複製至 Resource 將圖片放入 Image View 中 首先選擇一張圖檔,將圖檔拉進 Resources 資料夾中,記得勾選 copy item 2 1
設定 Image View 於 View 中點選 Image View 後,由 [Tools] → [Attributes Inspector] 叫出屬性面板 在 Image,選擇匯入的圖片檔名
編譯與執行 完成後,建置應用程式,按下 Build and Go 進行編譯與執行,即出現剛剛佈置的畫面 從此範例中,學習並瞭解如何使用Interface Builder,除了佈置元件畫面,尚提供元件屬性設定,讓開發者可以輕鬆的安排應用程式畫面,毋須為畫面額外撰寫其他程式碼。
iPhone 實戰練習 iPhone範例程式 Hello World 倒數計時器
Hello World 範例 Hello World 範例 當輸入文字並按下確定後,畫面會顯示”Hello + 輸入的文字”
1. 建立新專案 選擇View-based Application
2.命名專案名稱 此範例專案名稱為"HelloExample"
3. 專案建立畫面
4. 佈置畫面元件 (1/2) 開啟"HelloExampleViewController.xib"
4. 佈置畫面元件 (2/2) 此範例需要的元件與佈置如右: Label x 2 Round Rect Button x 1 Text Field x 1 Label1 Label2 Button TextField
5. 設定元件屬性(1/3) 文字標籤 (Label1) 設定其文字對齊方式為置中 設定文字大小為18
5. 設定元件屬性(2/3) 文字輸入區 (TextField) Placeholder 輸入”姓名”以提醒使用者輸入區要填寫的內容 設定文字大小為18 設定 Return Key 為 Done
5. 設定元件屬性(3/3) 按鈕 (Button) 設定 Title 為”確定” 設定完各元件屬性後,記得儲存.xib檔案 [File] → [Save] 或是 [Command+s]
6. 撰寫標頭檔程式碼 撰寫標頭檔 HelloExampleViewController.h 01 #import <UIKit/UIKit.h> 02 03 @interface HelloExampleViewController : UIViewController { 04 // 宣告文字標籤與輸入欄位變數 05 IBOutlet UILabel *displayLabel; 06 IBOutlet UITextField *inputTextField; 07 } 08 09 // 使用@property自動產生getter及setter 10 @property(nonatomic, retain) IBOutlet UILabel *displayLabel; 11 @property(nonatomic, retain) IBOutlet UITextField *inputTextField; 12 13 // 宣告一個函式displayButton 14 - (IBAction)displayButton:(id) sender; 15 16 @end
程式碼說明 第1行 引入 UIKit 函式庫讓我們可使用 UILabel、UITextView 等類別UIViewController 第3行 以 @interface 宣告 HelloExampleViewController 類別名稱,繼承自父類別 UIViewController,以 @end 作為類別結束(第16行) 第5-6行 宣告各元件的變數名稱,加上 IBOutlet 僅為告知 Interface Builder 在元件上進行提示 第10-11行 使用@property來自動產生getter及setter 第14行 宣告方法 displayButton,加上 IBAction 同 IBOutlet 僅為告知Interface Builder 在元件上進行提示 第16行 @end作為類別結束
property 參數nonatomic,表示存取變數時不需進行鎖定。而在多執行緒的程式下,為了維護變數的一致性,則須設定atomic(atomic是預設的,因此不寫nonatomic即可) 參數retain,若變數被設定時,會將其retain count值加一,避免變數被記憶體釋放 @property是Objective-C 2.0中的語法,用途如下 在物件導向程式(OOP)語言中,實體變數(instance variale)會被封裝,因此必須撰寫getter及setter的方法來取得或設定實體變數, 通常這樣的過程相當瑣碎且麻煩,在Objective-C中提供property的語法幫我們自動產生getter及setter,若變數名稱為 Variable,可直接呼叫下列的方法對變數進行存取,毋須額外宣告。 getter: getVariable setter: variable 提供點運算子語法
7. 繫結畫面元件與程式碼 開啟"HelloExampleViewController.xib” 繫結元件的方式,任意選擇一畫面元件,選擇 [Tools] → [Connections Inspector],進行繫結
繫結文字標籤 (Label1) 文字標籤 (Label1) 點選 Label1,叫出 Label Connections,會看到 New Referencing Outlets,按著其上方的加號,會出現一條藍色的線,將它連到 File Owner,選擇 displayLabel,此控制項就會對應到 displayLabel 這個變數
繫結文字輸入區 (TextField) 文字輸入區 (TextField) 點選 TextField,叫出 TextField Connections,會看到 New Referencing Outlets,按著其上方的加號,會出現一條藍色的線,將它連到 File Owner,選擇 inputTextField,此控制項就會對應到inputTextfield 這個變數
繫結按鈕 (Button) 按鈕 (Button) 點選 Button,叫出 Button Connetctions,與前兩者不同,這邊是希望當按鈕按下後,會觸發事件並呼叫觸發的方法,所以針對 Events 下的 Touch Up Side 的加號進行繫結,將它連到 File Owner,選擇 displayButton,當按鈕按下觸發事件時,則會對應到 displayButton 這個方法
8. 撰寫實作檔程式碼 撰寫實作檔 HelloExampleViewController.m 01 #import "HelloExampleViewController.h" 02 03 @implementation HelloExampleViewController 04 05 //對應@property,使用@synthesize來自動產生變數的存取或設定方法 06 @synthesize displayLabel; 07 @synthesize inputTextField; 08 09 // 實作displayButton方法 10 - (IBAction)displayButton:(id) sender { 11 NSString *str; 12 13 if([inputTextField.text length] == 0) 14 str = @"請輸入名字"; 15 else 16 str = [[NSString alloc] initWithFormat:@"Hello %@", inputTextField.text]; 17 18 displayLabel.text = str; 19 [str release]; 20 } 21 22 @end
程式碼說明 第1行 第3行 第6-7行 第10-20行 第11行 第13-14行 第15-16行 第18行 第19行 第22行 引入 HelloExampleViewController.h 標頭檔 第3行 以 @implementation 宣告開始實作 HelloExampleViewController,以@end作為結束(第22行) 第6-7行 使用@synthesize來自動產生變數的存取或設定方法(對應@property) 第10-20行 實作displayButton 方法 第11行 以 NSString 類別產生字串物件 第13-14行 檢查輸入框的內容是否為空,否則秀出」請輸入名字」 第15-16行 若輸入框內容不為空,則初始化一個新的字串,Hello+輸入的文字 第18行 設定文字標籤的內容為Hello+輸入的文字 第19行 由於NSString物件已使用,記得自記憶體釋放 第22行 @end 表實作結束
9. 編譯與執行
10. 執行結果
倒數計時器 進一步撰寫倒數計時器範例,由目前時間倒數至23時59分59秒,畫面中會顯示距離23時59分59秒還剩多久,倒數完畢會播放警示音通知。 此範例中會使用到 Timer 與音樂播放器,完成畫面如下
1. 建立新專案 選擇 View-based Application
2.命名專案名稱 此範例專案名稱為"CountDownTimer"
3. 佈置畫面元件 (1/2) 開啟 “CountDownTimerViewController.xib”
3. 佈置畫面元件 (2/2) 此範例需要的元件與佈置如下 Label x 1 Label1
4. 設定元件屬性 (1/2) 文字標籤 (Label) 屬性設定 Text內容為空 設定其文字對齊方式為置中 設定文字大小為48 設定文字顏色為紅色
4. 設定元件屬性 (2/2) 設定 Autosizing,按下 “Command+3” 切換到Label Size,設定如圖,這是為了讓 iPhone 橫躺時,文字能夠自動旋轉橫放。
5. 撰寫標頭檔程式碼 撰寫標頭檔 “CountDownTimerViewController.h” 01 #import <UIKit/UIKit.h> 02 #import <AVFoundation/AVAudioPlayer.h> 03 04 @interface CountDownTimerViewController : UIViewController { 05 IBOutlet UILabel *timeLabel; 06 NSTimer *timer; 07 AVAudioPlayer *filePlayer; 08 } 09 10 - (void)startTimer; 11 - (void)onTimer; 12 13 @end
程式碼說明 (1/2) 第2行 引入”AVAudioPlayer.h”,由於播放警示音時會使用AudioPlayer 此標頭檔位於AVFoundation函式庫,故在使用前必須將其加入至Frameworks中。在Frameworks上按右鍵,選擇Add,從Existing Frameworks中選擇AVFoundation.framework
程式碼說明 (2/2) 第5行 第6行 第7行 第10行 第11行 宣告文字標籤的變數名稱 宣告NSTimer 宣告AVAudioPlayer 第10行 宣告產生計時器的方法 第11行 宣告計時器執行時呼叫時的方法
6. 繫結畫面元件與程式碼 開啟 “CountDownTimerViewController.xib” 繫結元件的方式,任意選擇一畫面元件,選擇 [Tools] → [Connections Inspector],進行繫結
繫結文字標籤 (Label) 文字標籤 (Label) 方法一:按住File’s Owner,將藍線拉到 Label上,並點選timeLabel 方法二:如同上個範例,先點選 Label 叫出 Label Connections,會看到 New Referencing Outlets,按著其上方的加號,會出現一條藍色的線,將它連到 File’s Owner,選擇 timeLabel
7. 撰寫實作檔程式碼 (1/3) 撰寫實作檔 “CountDownTimerViewController.m” 01 #import "CountDownTimerViewController.h" 02 @implementation CountDownTimerViewController 03 04 - (void)startTimer { 05 // 每隔一秒呼叫onTimer 06 timer = [[NSTimer scheduledTimerWithTimeInterval:(1.0) target: self selector: @selector(onTimer) 07 userInfo: nil repeats: YES] retain]; 08 } 09 10 - (void)onTimer { 11 // 計算倒數剩幾時幾分幾秒 12 NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; 13 NSDate *now = [NSDate date]; 14 15 NSDateComponents *nowHour = [cal components:NSHourCalendarUnit fromDate:now]; 16 NSDateComponents *nowMinute = [cal components:NSMinuteCalendarUnit fromDate:now]; 17 NSDateComponents *nowSecond = [cal components:NSSecondCalendarUnit fromDate:now]; 18
7. 撰寫實作檔程式碼 (2/3) 19 int hour = 23 - [nowHour hour]; 20 int min = 59 - [nowMinute minute]; 21 int sec = 59 - [nowSecond second]; 22 23 // 時間倒數結束 24 if (hour <= 0 && min <= 0 && sec <= 0) { 25 // 關閉計時器 26 [timer invalidate]; 27 28 // 時間結束 29 timeLabel.text = @"時間終了!"; 30 31 // 播放音樂 32 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"]; 33 NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath]; 34 filePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil]; 35 [filePlayer play]; 36 } 37 else { 38 // 倒數尚剩幾時幾分幾秒 39 timeLabel.text = [NSString stringWithFormat:@"%02d時%02d分%02d秒", hour, min, sec]; 40 } 41 } 42
7. 撰寫實作檔程式碼 (3/3) 43 - (void)viewDidLoad { 44 [super viewDidLoad]; 45 45 46 // 啟動計時器 47 [self startTimer]; 48 } 49 50 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 51 return YES; 52 } 53 54 - (void)didReceiveMemoryWarning { 55 [super didReceiveMemoryWarning]; 56 } 57 58 - (void)viewDidUnload { 59 } 60 61 - (void)dealloc { 62 [super dealloc]; 63 } 64 65 @end
程式碼說明 (1/2) 第4-7行 第10-48行 第12-17行 第19-21行 第24-36行 實作 startTimer 方法,使用 NSTimer 類別中提供用以建立 Timer 的方法 scheduledTimerWithTimeInterval,定義 Timer 每一秒呼叫一次,選擇器(@selector)會執行 onTimer 方法 第10-48行 實作 onTimer 方法,每秒被呼叫執行時,計算目前時間至23時59分59秒還剩下多少時間,而倒數完畢後會播放警示音 第12-17行 使用時間函數取得目前時間的時分秒 第19-21行 計算目前時分秒與23時59分59秒的差距 第24-36行 檢查是否時間已經倒數完畢,若結束時則停止 Timer ,並且設定文字為”時間終了”。
程式碼說明 (2/2) 第32-35行 第37-40行 第43-47行 第50-52行 播放警告音,記得將 test.mp3 放入至 Resources 中,第32行設定檔案路徑時,可依據檔案類型設定播放的音樂格式,此例為使用 mp3 第37-40行 設定目前時間距離倒數還剩幾時幾分幾秒 第43-47行 設定畫面載入時開始執行計時器 第50-52行 設定 iPhone 會依據直立或橫放時,自動旋轉畫面
8. 編譯與執行 按下 Build and Go 進行編譯與執行
iPhone 相關學習資源 官方開發網站提供的文件、範例檔、影片http://developer.apple.com/iphone/index.action Apple 與 Stanford 合作開設的課程(CS 193P iPhone Application Development),除了提供講義下載,上課錄影皆可在 iTunes U 上免費下載,影片含英文字幕 http://www.stanford.edu/class/cs193p/cgi-bin/drupal/ iPhone Programming Tutorial,提供免費的教學範例http://icodeblog.com/ iPhone Tutorialz for Programmers http://adeem.me/blog/