第一章
1. Objective-C 使用的是消息結(jié)構(gòu)而非函數(shù)調(diào)用,其區(qū)別在于:
- 消息結(jié)構(gòu)的語言镣丑,其運(yùn)行時(shí)所應(yīng)執(zhí)行的代碼由運(yùn)行環(huán)境決定舔糖,編譯完并不知道應(yīng)該執(zhí)行哪個(gè)方法,即運(yùn)行時(shí)綁定
- 函數(shù)調(diào)用的語言莺匠,由編譯器決定金吗,也就是編譯完就知道應(yīng)該執(zhí)行那個(gè)方法
2.內(nèi)存
NSString *someString = @"The String";
對(duì)象所占內(nèi)存總是分配在堆空間(heap space)
,而絕不會(huì)分配在棧(stack)
上趣竣。
NSString *anotherString = someString;
兩個(gè)變量都指向此實(shí)例摇庙,這說明當(dāng)前棧幀(stack frame)
里分配了兩塊內(nèi)存,每塊內(nèi)存的大小都能容下一枚指針(在32位架構(gòu)的計(jì)算機(jī)上是4字節(jié)遥缕,64位是8字節(jié))卫袒,這兩塊內(nèi)存里的值都一樣,就是NSString實(shí)例的內(nèi)存地址单匣。
分配在堆
中的內(nèi)存必須直接管理夕凝,分配在棧
上的用于保存變量的內(nèi)存則會(huì)在其棧幀彈出時(shí)自動(dòng)清理
3.使用對(duì)象會(huì)比使用結(jié)構(gòu)體性能差
對(duì)象還需要額外的開銷,例如分配及釋放堆內(nèi)存等
4.盡量使用@class 前向引用
當(dāng)只需要聲明類型時(shí)封孙,用@class 會(huì)避免編譯.h 里不必要的東西迹冤,從而減少編譯時(shí)間
5.多用字面量語法
NSNumber *someNumber = @7;
id object1 = /*...*/
id object2 = /*...*/
id object3 = /*...*/
NSArray *arrayA = [NSArray arrayWithObjects:object1,object2,object3,nil];
NSArray *arrayB = @[object1,object2,object3];
如果object2
是nil,而arrayB
會(huì)crash 讽营,但是arrayWithObjects
方法會(huì)依次處理各個(gè)參數(shù)虎忌,直到發(fā)現(xiàn)nil 為止,所以最終arrayA = @[ object1 ]
橱鹏,類似的方法還有dictionaryWithObjectsAndKeys:
6.多用類型常量膜蠢,少用#define預(yù)處理指令
例如定義一個(gè)動(dòng)畫時(shí)長(zhǎng)
#define ANIMATION_DURATION 0.3
最好替換成
static const NSTimeInterval kAnimationDuration = 0.3;
static
表示其作用域僅限于當(dāng)前的目標(biāo)文件中,如果不加的話莉兰,如果其他編譯單元也聲明了同名變量挑围,則編譯器會(huì)拋出異常
如果只是在.m里定義的話,最好加上k 前綴糖荒,如果類外可見的話以類名為前綴
extern NSString* const XXStringConstant;
NSString* const XXStringConstant = @"XXStringConstant";
此類常量放在全局符號(hào)表里杉辙,這里的XXStringConstant
就是一個(gè)“一個(gè)常量,而這個(gè)常量是指針捶朵,指向NSString 對(duì)象”
第二章
1.屬性
- @dynamic : 告訴編譯器不需要生成存取方法
- @synthesize : 合成屬性
firstName = _firstName;
- weak : 非擁有的關(guān)系蜘矢,所指對(duì)象遭到摧毀,屬性也會(huì)清空(nil)
- unsafe_unretained : 當(dāng)所指對(duì)象摧毀時(shí)综看,屬性值不會(huì)自動(dòng)清空
2.等同性
相同的對(duì)象必須具有相同的哈希值品腹,但是兩個(gè)哈希值相同的對(duì)象不一定相同
NSMutableSet * set = [NSMutableSet new];
NSArray *arr1 = @[@1,@2];
NSArray *arr2 = @[@1];
[set addobject:arr1];
[set addobject:arr2]; //set {(1,2),(1)}
[arr2 addObject:@(2)]; //set {(1,2),(1,2)} 哈希值改變了
NSSet *set2 = [set copy]; // set {(1,2)}
3.類族模式
類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面
+ (UIButton* )buttonWithType:(UIButtonType)type;
該方法所返回的對(duì)象,其類型取決于傳于傳入的按鈕類型红碑,不管是什么類型舞吭,都是繼承與UIButton;
4.在既既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù) "關(guān)聯(lián)對(duì)象(Associated Object )"
存儲(chǔ)策略 (Storage Policy)
關(guān)聯(lián)類型 | 等效的@property 屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatommic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
void objc_setAssociatedObject(id object,void*key,id value,objc_AssociationPolicy policy)
以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
id objc_getAssociatedObject(id object,void*key)
根據(jù)給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
void objc_removeAssociatedObjects(id object)
移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
key: static void* associatedKey = @"associatedKey"
多次使用alert視圖時(shí),使用此方法較好
5.理解objc_msgSend 的作用
void objc_msgSend(id self, SEL cmd, ...)
這是個(gè)“參數(shù)個(gè)數(shù)可變的函數(shù)”,能接受兩個(gè)或兩個(gè)以上的參數(shù)羡鸥。第一個(gè)參數(shù)代表接收者蔑穴,第二個(gè)參數(shù)代表方法,后續(xù)參數(shù)就是消息中的那些參數(shù)
id returnValue = [someObject messageName:parameter];
編譯器會(huì)將這個(gè)消息轉(zhuǎn)換成如下函數(shù):
id returnValue = objc_msgSend(someObject, @selecter(messageName:), parameter)
該方法需要在接收者所屬的類中搜尋其方法列表惧浴,如果能找到與方法名字相符的方法澎剥,就跳至其實(shí)現(xiàn)代碼,若是找不到赶舆,那就沿著繼承體系繼續(xù)向上查找哑姚,等找到合適的方法之后再跳轉(zhuǎn)。最終還是沒有找到的話芜茵,那就執(zhí)行消息轉(zhuǎn)發(fā)叙量。
objc_msgSend
會(huì)將匹配結(jié)果緩存在快速映射表(fast map)里,每個(gè)類都有這樣一塊緩存九串,加快執(zhí)行速度
6.理解消息轉(zhuǎn)發(fā)機(jī)制
在編譯期向類發(fā)送了其無法解讀的消息并不會(huì)報(bào)錯(cuò)绞佩,因?yàn)樵谶\(yùn)行期可以繼續(xù)向類中添加方法,所以編譯器在編譯期還無法確定類中到底會(huì)不會(huì)有某個(gè)方法的實(shí)現(xiàn)猪钮。當(dāng)對(duì)象接收到無法解讀的消息后品山,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制,程序員可經(jīng)此過程告訴對(duì)象應(yīng)該如何處理未知消息烤低。
消息轉(zhuǎn)發(fā)分為兩大階段:
- 第一階段先征詢接收者肘交,所屬的類,看其是否能動(dòng)態(tài)添加方法扑馁,以處理當(dāng)前這個(gè)未知的選擇子涯呻,這叫做“動(dòng)態(tài)方法解析”
- 第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”。如果runtime 已經(jīng)把第一階段執(zhí)行完了腻要,那么接收者自己就無法再以動(dòng)態(tài)新增方法的手段來響應(yīng)包含該選擇子的消息了复罐,此時(shí)runtime 會(huì)請(qǐng)求接收者以其他手段來處理該消息。這又分為兩小步:
首先雄家,請(qǐng)接收者看看有沒有其他對(duì)象能處理這條消息效诅,若有,則runtime 會(huì)把這條消息轉(zhuǎn)給那個(gè)對(duì)象趟济,于是消息轉(zhuǎn)發(fā)結(jié)束乱投,一切如常。若沒有“備援的接收者”咙好,則啟動(dòng)完成的消息轉(zhuǎn)發(fā)機(jī)制篡腌,runtime 會(huì)把與消息有關(guān)的全部細(xì)節(jié)封裝到NSIvocation 對(duì)象中,再給接收者最后一次機(jī)會(huì)勾效,令其設(shè)法解決當(dāng)前還未處理的這條消息
動(dòng)態(tài)方法解析
對(duì)象在收到無法解讀的消息后嘹悼,首先將調(diào)用其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethed:(SEL)selector;
表示這個(gè)類是否能新增一個(gè)實(shí)例方法用以處理此選擇子叛甫。如果是類方法,runtime 會(huì)調(diào)用一個(gè)類似的方法杨伙,叫resolveClassMethod:
備援接收者
第二次處理未知選擇子的機(jī)會(huì)其监,runtime 會(huì)詢問類:能不能把這條消息轉(zhuǎn)給其他接收者來處理。
- (id)forwardingTargetForSelector: (SEL)selector;
若當(dāng)前接收者能找到備援對(duì)象限匣,則將其返回抖苦,若找不到,返回nil米死。通過此方案锌历,我們可以用“組合”來模擬出“多重繼承”的某些特性。在一個(gè)對(duì)象的內(nèi)部峦筒,可能還有一系列其他對(duì)象究西,該對(duì)象可經(jīng)由此方法將能處理選擇子的相關(guān)內(nèi)部對(duì)象返回,在外界看來物喷,好像是該對(duì)象親自處理了消息卤材。
完整的消息轉(zhuǎn)發(fā)
如果轉(zhuǎn)發(fā)算法走到這一步,那么唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了峦失,首先創(chuàng)建NSIvocation 對(duì)象扇丛,把與尚未處理的那條消息有關(guān)的全部細(xì)節(jié)封于其中,此對(duì)象包含選擇子尉辑,目標(biāo)及參數(shù)帆精。在觸發(fā)NSInvocation 對(duì)象時(shí),“消息派發(fā)系統(tǒng)”將親自出馬材蹬,把消息指派給目標(biāo)對(duì)象实幕。相當(dāng)于只是改變了調(diào)用目標(biāo)。
-(void)forwardInvocation:(NSInvocation *)invocation;
繼承體系中的每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求堤器,直至NSObject,繼而調(diào)用doesNotRecognizeSelector:
以拋出異常,表示最終未能得到處理末贾。
消息轉(zhuǎn)發(fā)全流程
方法調(diào)配技術(shù) (Method swizzling)
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上闸溃,使得“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠根據(jù)此找到應(yīng)該調(diào)用的方法,這些方法均以函數(shù)指針的形式來表示拱撵,這種指針叫做IMP辉川,原型如下:
id (*IMP)(id, SEL, ...)
runtime提供了一些方法來操作這張表
Method originMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originMethod, swappedMethod);
- (NSString* )my_lowercaseString{
NSString* lowercaser = [self my_lowercaseString];
NSLog(@"%@ => %@",self,lowercase);
return lowercase;
}
將上面方法和NSString 的lowercaseString
方法互換,這段代碼看上去會(huì)死循環(huán)拴测,但是乓旗,在運(yùn)行期間,my_lowercaseString
的選擇子實(shí)際對(duì)應(yīng)的是lowercaseString
集索。通常開發(fā)者可以在調(diào)試階段利用這個(gè)方法來為那些“完全不知道實(shí)現(xiàn)細(xì)節(jié)”的方法增加日志記錄功能屿愚,不宜濫用
7.理解 “類對(duì)象” 的用意
id類型定義如下:
typedef struct objc_object{
Class isa;// "is a"指針汇跨,定義了對(duì)象所屬的類
} *id;
Class 對(duì)象定義如下:
typedef struct objc_class *Class
struct objc_class{
Class isa;// "is a"指針,定義了對(duì)象所屬的類
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct obcj_cache *cache;
struct objc_protocol_list *protocols;
};
類對(duì)象所屬的類型叫做元類妆距,用來表述類對(duì)象本身所具備的元數(shù)據(jù)穷遂。每個(gè)類既有一個(gè)“類對(duì)象”,而每個(gè)“類對(duì)象”僅有一個(gè)與之相關(guān)的“元類”娱据。
繼承體系如下:
比較對(duì)象是否等同除了 isKindOfClass:
還可以用比較類對(duì)象是否相同蚪黑,原因在于,類對(duì)象是“單例”中剩,每個(gè)類的Class 僅有一個(gè)實(shí)例忌穿。
id object = /* ... */;
if ([object class] == [SomeClass class]){
// 'object is an instance of SomeClass'
}
雖然能這樣比較,但不推薦结啼。例如伴网,某個(gè)對(duì)象可能會(huì)把其收到的所有選擇子都轉(zhuǎn)發(fā)到另一個(gè)對(duì)象。這樣的對(duì)象叫做“代理(proxy)“妆棒,此種對(duì)象均以NSProxy 為根類澡腾。
如果在這種對(duì)象上調(diào)用class 方法,那么返回的是代理對(duì)象本身(此類是NSProxy 的子類)糕珊,而非接受代理的對(duì)象所屬的類动分,然而改用isKindOfClass:
就能把這條消息轉(zhuǎn)給接受代理的對(duì)象。
8.Block
塊的內(nèi)部結(jié)構(gòu)
塊本身也是對(duì)象红选,在存放塊對(duì)象的內(nèi)存區(qū)域中澜公,首個(gè)變量是指向Class 對(duì)象的指針,叫做isa 其余內(nèi)存里含有塊對(duì)象正常運(yùn)轉(zhuǎn)所需的各種信息喇肋。
void* | isa |
---|---|
int | flags |
int | reserved |
void ( ***** )(void ***,...) | invoke |
struct * | descriptor |
捕獲到的變量 |
其中最重要的就是invoke 變量坟乾,這是個(gè)函數(shù)指針,指向塊的實(shí)現(xiàn)代碼蝶防。函數(shù)原型至少要接受一個(gè)void* 型的參數(shù)甚侣,此參數(shù)代表塊。
descriptor 變量是指向結(jié)構(gòu)體的指針间学,其中聲明了塊對(duì)象的總體的大小殷费,還聲明了copy 與dispose 這兩個(gè)輔助函數(shù)所對(duì)應(yīng)的函數(shù)指針。
descriptor 對(duì)應(yīng)結(jié)構(gòu)如下:
unsigned long int | reserved |
---|---|
unsigned long int | size |
void ( ***** )(void ***** , void *****) | copy |
void ( ***** )(void ***** , void *****) | dispose |
塊還會(huì)把它所捕獲的所有變量都拷貝一份低葫,這些拷貝都放在descriptor 變量后面详羡,捕獲了多少個(gè)變量,就要占據(jù)多少內(nèi)存空間嘿悬。注意实柠,拷貝的并不是對(duì)象本身,而是指向這些對(duì)象的指針變量善涨。invoke 函數(shù)之所以把塊當(dāng)?shù)谝粋€(gè)參數(shù)傳遞進(jìn)來窒盐,原因在于草则,執(zhí)行塊時(shí),要從內(nèi)存中把這些捕獲的變量讀出來登钥。
全局塊畔师、棧塊及堆塊
定義塊的時(shí)候,其所占內(nèi)存區(qū)域是分配在占棧中牧牢,塊只在定義它的那個(gè)范圍內(nèi)有效看锉。
void (^Block)();
if (/* some condition*/){
block = ^{
NSLog(@"Block A");
};
}else{
block = ^{
NSLog(@"Block B");
}
}
block();
這段代碼就有危險(xiǎn),定義在if 里面的Block 都分配在棧內(nèi)存中塔鳍。在離開了if 語句后伯铣,編譯器有可能把分配在塊的內(nèi)存覆寫掉,可能導(dǎo)致crash轮纫。
為解決此問題腔寡,可以給塊對(duì)象發(fā)送copy 消息來拷貝,這樣可以把塊從棧復(fù)制到堆上掌唾,塊就成了帶引用計(jì)數(shù)的對(duì)象了放前。后續(xù)的拷貝操作都不會(huì)真的執(zhí)行復(fù)制,只是遞增塊對(duì)象的引用計(jì)數(shù)糯彬。
void (^Block)();
if (/* some condition*/){
block = [^{
NSLog(@"Block A");
} copy];
}else{
block = [^{
NSLog(@"Block B");
} copy]
}
block();
全局塊
void (^Block)() = ^{
NSLog(@"This is a block");
}
由于在編譯期能確定塊所需的全部信息凭语,所以可以把它做成全局塊。這是種優(yōu)化技術(shù)撩扒。
未完待續(xù)...