[CS193P] 第三堂課摘要及心得筆記

讓大家久等了,Stanford大學的iPhone開發課程筆記第三堂課來了!或許大家有注意到,在第三堂課的課堂上,老師有提到今年的iTunes U影片會較去年慢一點釋出,所以一直拖到現在才恢復連載,還請大家見諒!
評論
評論

讓大家久等了,Stanford 大學的 iPhone 開發課程筆記第三堂課來了!或許大家有注意到,在第三堂課的課堂上,老師有提到今年的 iTunes U 影片會較去年慢一點釋出,所以一直拖到現在才恢復連載,還請大家見諒!

這次的課程內容包含如何建立自訂的類別、以及一個類別的生命週期以及記憶體管理,和 Objetive-C 中特別的 Property 概念。

建立自訂類別


在 Objetive-C 中,如果要建立自訂的類別,就跟 C++中的模式很類似,需要一個.h 標頭檔先宣告類別的內容(方法和變數),然後搭配.m 檔來實做這些方法的功能。

而誠如第一次筆記中所談的,所有的 Objective-C 物件都繼承成至 NSObject 這個根物件。

#import <Foundation/Foundation.h> @interface Person : NSObject { 	// 宣告類別所擁有的變數 	NSString *name; int age; } // 宣告類別所擁有的方法 - (NSString *)name; - (void)setName:(NSString *)value; - (int)age; - (void)setAge:(int)age; - (BOOL)canLegallyVote; - (void)castBallot; @end

以上的這段程式碼就是一個簡單類別的定義,在@interface 下的括號中包含了這個類別的狀態,也就是他所擁有的變數,而在後面的部份則是方法的定義。

還記得我們曾經在之前的課程筆記中提到,在 Objetive-C 中,由減號 (-) 開頭的方法就是實體方法,必須由類別產生出實體之後才能使用。而由加號 (+) 開頭的方法則是類別方法,不需產生出實體就可以使用。

順便提醒,最後可別忘記在宣告完類別後加入@end 的標籤。

在物件實做的過程,也就是在.m 中實做方法的時候,我們可能會需要傳遞訊息給自己本身的物件,也就是呼叫自己本身帶有的方法,此外,也有可能需要傳遞訊息給父類別、呼叫父類別的方法。而在 Objetive-C 採用了類似 C++和 Java 的模式,用 self 代替自己、用 super 替代父類別。也就是:

- (void)doSomething { 	// 呼叫父類別的方法 	[super doSomething]; 	// 呼叫同類別中的方法 	[self doAnotherThing]; }

物件的生命週期

當我們設計好類別之後,隨之便是要在程式中將這些類別拿來使用,也就是產生實體。而在 Objective-C 中,我們通常會透過 [[anyClass alloc] init]; 這樣的方式來產生新的物件。

+ alloc 這個類別方法就像是 C 語言中的 malloc 或是 C++中的 new 一樣,會在記憶體中產生一個新的物件出來,並回傳該記憶體的位置給變數。

而在我們產生完新的物件之後,我們需要呼叫建構子來幫助我們初始化該物件的內容,也就是- init 這個實體方法以及其他以 initWith…開頭的實體方法。

也因為上述的原因,我們需要在自己的類別中定義 init 以及 initWith…的相關方法。

- (id)init { 	// 先讓父類別進行初始化 	if (self = [super init]) { 		// 再進行其它初始化的工作 		age = 0; 		name = @“Bob”; 	} 	return self; }

上面這種寫法是 Cocoa 中很常見的寫法,先呼叫父類別的初始化方法,在進行自己所需要初始化的動作。

而我們也需要建立一系列不同的初始化方法,來幫助我們用不同的參數初始化物件,向下面這些就是很好的例子:

- (id)init; - (id)initWithName:(NSString *)name; - (id)initWithName:(NSString *)name age:(int)age;

在我們有多重的初始化方法之後,我們實做的時候要記得一個關鍵:只要寫最複雜的那個方法就好,其他的都呼叫那個方法就對了。像是以下就是個例子:

- (id)init { 	return [self initWithName:@“No Name”]; }  - (id)initWithName:(NSString *)name { 	return [self initWithName:name age:0]; }

除了建立物件之外,學習如何刪除物件也是很重要的,在 Cocoa Touch 的環境下,我們並沒有像 Java 一樣方便的 Garbage Collection 物件自動回收機制可以使用,而必須像 C++一樣,自己去追蹤物件的使用,在適當的時候把物件刪除。

在 Objetive-C 裡面,當物件要從記憶體中刪除的時候,會呼叫-dealloc 這個方法。然而我們並不需要自己去呼叫這個方法,因為 Objetive-C 為了方便大家能夠持續追蹤物件的使用狀況,提供了 reference count 的機制,也就是物件被參考的次數,會被儲存在一個變數中(相關的方法跟變數實際上是繼承至 NSObject 物件)。

而我們可以透過 release 這個方法來減少 count、用 retain 方法來增加 count。當 count 的值降到零的時候,物件就會從記憶體中被釋放出來。

每當物件被 alloc + init 建立的同時,他的 reference count 就會變成 1,隨著物件的操作或傳遞,我們在適時的進行 release 和 retain 的呼叫,這樣當物件的 conut 歸零的同時,物件就會被呼叫 dealloc 方法、進而從記憶體中刪除。

Memory count

在上面我們提到,我們曾經提到在 Objetive-C 裡面必須自己去處理物件在記憶體的建立以及刪除,而為了方便我們解決這個問體,NSObject 提供了 memory count 的機制:當物件產生時這個數字會是 1,透過 retain 可以再增加 1,而 release 會減一,當數字小於等於 0 的時候就會呼叫該物件的 dealloc 方法,將物件從記憶體中刪除。

此外,我們可以透過呼叫物件的 retainCount 方法來檢查目前物件的 memory count。不過需要注意像是下面這種狀況:

Person *person = [[Person alloc] init]; // 建立物件 [person release]; // 物件從記憶體中被移除  [person doSomething]; // 程式錯誤!

因為 person 這個變數仍然存著一個記憶體的位置,系統無從得知 person 所指向的記憶體位置是不是已經被釋放了,所以必須在第三行加入:

person = nil;

誠如在第一次課堂筆記中所說的,Objetive-C 可以接受呼叫一個不存在的方法,因此對於 nil 呼叫任何方法都不會產生錯誤。

物件的所有權

一般來說,如果我們使用了某個 initWith…開頭的方法來初始化物件的話,我們必須記得 retain 這些傳入的參數:

- (void)setName:(NSString *)newName {     if (name != newName) {         [name release];         name = [newName retain]; // 物件的 memory count 加一     } }

然而,這會面臨一個問題是,傳入的 newName 跟物件自己擁有的 name 兩個其實是在記憶體中指向同一個物件,所以如果在物件中對 name 這個變數做內容的修改,一樣會影響到當初傳入的 newName。所以針對這個問題,Objetive-C 也提供了相對的解決方式::

- (void)setName:(NSString *)newName {     if (name != newName) {         [name release];         name = [newName copy]; //在記憶體中另外複製了一份 newName,並且 memory count 為 1     } }

這樣的話我們就算修改物件 name 變數的內容,那也並不會影響到本來傳入的參數。

實做 dealloc

當 A 物件如果 retain 了 B 的話,那我們在 A 物件被 dealloc 的時候需要一同 release B 物件,這樣才不會造成 memory count 無法歸零的狀況,以下是一個常見的作法:

- (void)dealloc { // 先處理本身所擁有的物件變數     [name release]; // 當處理完之後就可以呼叫父類別的 dealloc 方法     [super dealloc]; }

autorelease


在我們呼叫物件的方法時,這個方法可能需要會回傳另外一個物件給我們,像是以下的範例:
- (NSString *)getSomething {     NSString *result = [[NSString alloc] initWithString: @"Something"];      return result; }

然而,像是以上這樣的範例會造成記憶體上的問題,因為我們在建立了 result 並且回傳之後,就不會在用到這個物件了,但是我們卻沒有 release 這個物件。所以,你可能會想要這樣寫:

- (NSString *)getSomething {     NSString *result = [[NSString alloc] initWithString: @"Something"];     [result release];     return result; }

可惜,這樣的寫法也是有問題的,因為回傳的 result 物件再回傳之前就先 release、memory count 歸 0 了,所以再回傳之前就先在記憶體中被刪除了!針對這個問題,Objective-C 有一個特別的機制可以幫助我們,也就是 autorelease。我們之需要將 [result release]; 這行換成 [result autorelease]; 就可以解決這個問題了!

而這樣方便的功能是怎樣完成的呢?或許大家已經注意到,在之前每個作業中的程式碼通常都會以下列的形式呈現:

int main(int argc, char *argv[]) {      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];     // do something     [pool release]; }

這邊的 NSAutoreleasePool 就是幫助我們完成這個功能的重要角色,他會將所有被最近 autorelese 的物件收集起來,當這個 pool 本身被 release 的時候,他就會把這些物件一一 release 掉。而 UIKit 針對每一個處理的事件會自動的用一層 AutoreleasePool 包起來,所以每當一個 UI 事件處理完成的時候,這些物件都會相繼被 release 掉。

命名慣例

我們都知道了 autorelease 是如此的方便,而在 Cocoa 的 API 中有一些命名習慣就是與記憶體管理息息相關的:

  • 所有以 init、copy、new 開頭的方法會回傳物件帶有 memory count= 1,使用者必須在適當的時候 release 該物件
  • 而其他所有的方法會直接回傳一個被 autorelease 過的物件,所以使用者必須自己 retain 該物件才能做後續的使用

我會建議各位讀者在自行撰寫方法的時候,也要盡量遵循這些命名慣例,以講求未來使用上的一直性。

@property

在物件導向一般的開發流程中,我們需要針對物件實做很多方法來讓使用者能夠操作物件中的變數。假設我們的物件定義擁有這些方法:

- (NSString *)name; - (void)setName:(NSString *)value; - (int)age; - (void)setAge:(int)age; - (BOOL)canLegallyVote;

但我們可以注意到,這些方法其實都是為了讀取或是操作物件內的變數而所產生的方法。透過@property 的語法,我們可以寫成:

@property int age; @property (copy) NSString *name; @property (readonly) BOOL canLegallyVote;

是不是簡潔很多呢?事實上,如果當我在.h 檔中宣告了:

@property int foo;

其實就等同於我寫了:

- (int)foo; - (void)setFoo:(int)value;

而在.m 檔中,我們也不需要一一實做這些@property 所產生的方法,我們只要寫:

@synthesize foo;

就會等同於產生了:

- (int) foo { 	return foo; }  - (void) setFoo:(int)value { 	foo = value; }

透過@property 和@synthesize 就可以減少很多這類重複的工作,這樣方便的功能可得牢記在心!

但需要注意的是,@property 並非一定要搭配@@synthesize 使用,你可以只在.h 檔中宣告@property 而在.m 檔中自行實做方法,而不透過系統自動產生。這樣做的原因通常是你需要在修改物件變數內容時做一些檢查,檢查設定的值是否合法。

Property 的屬性

除了單純簡化變數的存取之外,@property 也支援了許多不同的屬性。像是設定了 (readonly) 屬性的@property 只會產生讀取用的方法、不會有設定變數的方法產生。而針對記憶體管理的部份,Obj-C 提供了三種不同的屬性,分別是 (assign)、(retain) 和 (copy)。

一般的@property 在沒有設定屬性的狀況下預設值為 (assign),而 (assign) 屬性會直接傳入的參數物件直接指派給物件的變數,這樣會造成兩個問題。一個是沒有 retain、另外一個是物件變數的內容及傳入的參數其實兩個是指到相同的內容。

因為上面這兩個問題,分別有了 (retain) 和 (copy) 兩個參數來解決,搭配 (retain) 的 property 會在@synthesize 的時候一併對傳入的物件 retain,而 (copy) 則是會對傳入物件另外拷貝一份。

點號和 self

當我們針對某個物件變數設定了@property 屬性之後,我們就可以使用像 foo.name 這樣的方式來取用變數,或是用 foo.name = @"Apple"; 這樣來設定變數。

但是需要注意的是,當我們在實做物件的時候可千萬要分清楚兩者:

@implementation Person - (void)doSomething { 	name = @“Fred”; // 直接設定物件變數 	self.name = @“Fred”; // 呼叫 setName 這個方法 }

錯誤的使用可能會造成無窮迴圈:

-(void) setName: (NSString *)value { 	self.name = value; // 這樣又會呼叫 setName 這個函式 }

結論

在漫長的基礎訓練之後,我們終於把 Obj-C 中比較困難的部份學完了!也就是我們已經具備了足夠的基礎,接下來就是開發 iPhone App 了!在下一次的連載中,我們將介紹 iPhone 上的介面開發,還請大家繼續指教!

參考資料


Cookie 消失?試試看全新 AI 影像內容辨識:讓用戶看的內容決定看到的廣告

Google Chrome 即將淘汰幫助廣告主的工具—— Cookie ,它的離去將再一次地影響數位廣告產業。
評論
Photo Credit:<a href="https://www.shutterstock.com/zh/image-photo/ai-artificial-intelligence-big-data-internet-1075853384" target="_blank">shutterstock</a>
評論

透過GA分析進站者發現, Safari的新客數越來越多,難道這表示 iOS 的用戶數也跟著增加了嗎?注意了,這有可能是 Apple 封鎖第三方 Cookie 帶來的影響。隨著 Google 即將淘汰 Chrome 上的 Cookie ,這個幫助廣告主記住用戶受眾的小工具,將要再一次地影響數位廣告產業。

Photo Credit:驚點股份有限公司( FreakOut Taiwan )

後 Cookie 時代的廣告受眾如何鎖定?

各大廣告平台在過去幾年不斷地透過 Cookie 以及其它方式,悄悄收集使用者的用戶數據,隨著這幾年用戶的隱私權意識抬頭, Apple 與 Google 對於藉由 Cookie 辨識用戶資料的廣告投遞方式,持有不同的態度,這也將是所有廣告主的極大挑戰。當 Cookie 不復存在,要如何辨識使用者資料?

Cookie 消失了,或許會有新的數據辨識工具來取代,但是任何試圖跟蹤受眾的方式,都難以符合大眾對於保護隱私權的期望。另一方面,也極有可能無法再通過日趨嚴格的媒體監管限制。無論如何,數位廣告不能像過去一樣,無條件地使用類似 Cookie 的追蹤方式,來達到與現在同樣的廣告效果。

後 Cookie 時代內容辨識類型的廣告鎖定方式,將成為未來具指標性的投放策略。廣告與瀏覽平台或內文主題的高相關性,不僅能顯著提高受眾的互動度,更重要的是,完全不需要收集任何個人數據。

FreakOut Taiwan 不斷嘗試更友善的廣告投遞形式, 自 2016 年進入台灣市場的原生廣告,到 2020 年末引進「 Mirrors 」 AI 影像視覺內容辨識系統,都是以網路使用者的角度出發。「 Mirrors 」不需要使用傳統的受眾興趣設定,即可針對「目標受眾在觀看的影像內容」、「品牌自身競爭對手或相關指定系列產品出現的影片」來投遞 YouTube 上的影音廣告。

Photo Credit:驚點股份有限公司( FreakOut Taiwan )

AI 人工智慧影像技術突破,推動新一代內容辨識功能

傳統的內容比對廣告皆為自然語言處理 NLP 中心,基於「純文字」的比對來顯示相關廣告,如大家很熟悉的 Google AdSense 。但是,結合新興的 AI 演算和複雜的圖像辨識,已然能夠達到「影片」的內容偵測,透過增加多個比對層和基於自然語言處理 NLP 的基準定位,可深度學習並提供更精細的辨識洞察力。

舉例來說,若想要將汽車廣告投放給對汽車有興趣的人,我們要先從可能對汽車感興趣的受眾中開始推估,並且根據過去的經驗加入不同的興趣條件,最後針對素材更進一步地測試。透過 Mirrors ,我們可以讓廣告出現在有滿足特定條件的影片內,例如:在消費者觀看的影片中,出現汽車評測報告、自家品牌或競爭對手的 LOGO 、代言人等指定條件,再依照不同廣告主設定的預算判斷是否露出廣告。

藉由這樣的影像比對方式,可以更有效地根據消費者行為觸發廣告投遞條件,而不再是現行的用戶受眾數據。人的興趣是多樣且多變的,當對汽車有興趣的用戶在完成汽車的選購後,短期內將不會再瀏覽相關資訊,轉而瀏覽其他更具時效性的內容。透過消費者當下正在觀看的影片內容,取代消費者身上被貼上的數位標籤,將更貼近消費者本身的使用行為。

Photo Credit:驚點股份有限公司( FreakOut Taiwan )

Mirrors AI 影像辨識:用消費者看的內容決定廣告

2021 年台灣數位報告指出,台灣人在各網路內容服務中,最愛「網路影片」的比例高達 97.9%,遠超過 Vlog、串流音樂、網路廣播、Podcast 。

影音廣告早已是品牌經營的趨勢:根據 DMA 2019 年台灣數位廣告量統計報告指出,台灣各類型廣告中,影音廣告以 37.2% 的成長比例大幅領先奪冠。其中 YouTube 持續蟬聯台灣最常被造訪網站第 2 名(僅次於 Google ) ,在台灣各大影音平台中的觸及率及影響力不容小覷。

2021 年 FreakOut Taiwan 已與客戶合作,進行搭載新系統的 YouTube 串流內廣告投遞,在針對品牌及產品客製化的多層鎖定策略建議下,房地產廣告的 CTR 表現高於平均,並發現「人臉」群組辨識表現為佳,其中多為財經、名嘴等名人。而美妝品牌廣告 VTR 表現優異,則以品牌「 Logo 」、「人臉」群組有最出色的表現。

本文章內容由「驚點股份有限公司( FreakOut Taiwan )」提供,經關鍵評論網媒體集團廣編企劃編審。