分類(Category)
- 分類可以做些什么
- 聲明私有方法
- 分解體積龐大的類文件
- 把Framework的私有方法公開
如通過method_exchangeImplementations方法將私有方法替換為自己的方法+ (void)load{ //通過runtime進(jìn)行方法交換 /* 如這里通過方法交換將UIVeiw.intrinsicContentSize的getter方法 替換為自定義的intrinsicContentSizeSwizzle方法 */ Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"intrinsicContentSize")); Method method2 = class_getInstanceMethod([self class], @selector(intrinsicContentSizeSwizzle)); method_exchangeImplementations(method1, method2); }
- 分類有哪些特點
- 運(yùn)行時決議:分類的內(nèi)容如方法列表、屬性(通過關(guān)聯(lián)對象實現(xiàn))等是在運(yùn)行時(runtime)動態(tài)的添加到宿主類中(最大的特點也是與擴(kuò)展最主要的區(qū)別)
- 可以為系統(tǒng)類添加方法(擴(kuò)展不能為系統(tǒng)類添加方法)
- 分類添加的方法優(yōu)先級大于宿主類中的同名方法(原因:通過
runtime源碼得知,分類的方法列表會在運(yùn)行時動態(tài)的插入到宿主類方法列表的前面,在通過消息機(jī)制進(jìn)行方法查找時,優(yōu)先找到的是靠前的分類方法,所以會產(chǎn)生“覆蓋”的效果)- 多個分類中出現(xiàn)了
同名方法,誰能生效取決于編譯順序(Build Phases->Compile Sources),最后編譯的分類方法生效- 名字相同的分類會引起編譯報錯
分類重寫原類方法時,如何調(diào)用原類方法:通過遍歷類的方法列表,獲取方法在方法列表methods的索引(獲取最后一次出現(xiàn)的索引即為主類同名方法的索引),然后調(diào)用即可。
- (void)callClassMethod:(NSString *) methodName className:(Class)className{ u_int count; Method *methods = class_copyMethodList([className class], &count); NSInteger index = 0; for (int i = 0; i < count; i++) { SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; if ([strName isEqualToString: methodName]) { index = i; // 先獲取原類方法在方法列表中的索引 } } // 調(diào)用方法 id obj = [[className alloc] init]; SEL sel = method_getName(methods[index]); IMP imp = method_getImplementation(methods[index]); ((void (*)(id, SEL))imp)(obj,sel); }
- 分類中都可以添加那些內(nèi)容
- 實例方法
- 類方法
- 協(xié)議
- 屬性(setter、getter,需要通過關(guān)聯(lián)對象來創(chuàng)建實例變量)
- 分類的實現(xiàn)原理
- 分類的內(nèi)容如方法列表、屬性(通過關(guān)聯(lián)對象實現(xiàn))等是在運(yùn)行時(runtime)動態(tài)的添加到宿主類中
- 由于分類的方法列表會在運(yùn)行時動態(tài)的插入到宿主類方法列表的前面,在通過消息機(jī)制進(jìn)行方法查找時,優(yōu)先找到的是靠前的分類方法,所以會產(chǎn)生“覆蓋”宿主類方法的效果)
關(guān)聯(lián)對象相關(guān)
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object);
關(guān)聯(lián)對象的本質(zhì)
關(guān)聯(lián)對象由AssociationsManager(靜態(tài))管理并在AssociationsHashMap 中存儲
所有對象的關(guān)聯(lián)內(nèi)容都在同一個全局容器中。
思考:
如果有關(guān)聯(lián)對象,在dealloc中需要對關(guān)聯(lián)對象進(jìn)行清理嗎
不需要的,在dealloc的內(nèi)部實現(xiàn)中,系統(tǒng)會幫我們清楚關(guān)聯(lián)對象(_object_remove_assocations())
擴(kuò)展(Extension)
- 擴(kuò)展可以做什么
- 聲明私有屬性
- 聲明私有方法
- 聲明私有變量
- 擴(kuò)展的特點
- 編譯時決議
- 只以聲明的形式存在,多數(shù)情況下寄生于宿主類的.m中。
- 不能為系統(tǒng)類添加擴(kuò)展
分類和擴(kuò)展的區(qū)別
代理(Delegate)
- 代理是什么
-
代理模式是一種設(shè)計模式
- iOS當(dāng)中以
@protocol形式體現(xiàn)- 傳遞方式
一對一
代理的工作流程
協(xié)議中即可以定義屬性也可以定義方法,由代理方負(fù)責(zé)實現(xiàn)。
@optional下的為代理方可以選擇實現(xiàn)的方法(默認(rèn)),@required下的為代理方必須實現(xiàn)的方法。
2.代理的使用中需要注意的問題
一般聲明為weak以避免產(chǎn)生循環(huán)引用。
通知(NSNotification)
- 通知是什么
- 是使用
觀察者模式來實現(xiàn)的用于跨層傳遞消息的機(jī)制。- 傳遞方式為
一對多。
- 通知機(jī)制是如何實現(xiàn)的
由NofificationCenter維護(hù)一個Notification_Map,key為notificationName,value為觀察者列表,成員為一個存有觀察者的對象指針以及所要執(zhí)行的SEL方法指針
如何實現(xiàn)簡單的通知機(jī)制
KVO
1.KVO是什么
- KVO是Key-value observing的縮寫
- KVO是Objective-C對
觀察者設(shè)計模式的一種實現(xiàn)- Apple使用了isa混寫(
isa-swizzling)來實現(xiàn)KVO
isa混寫
isa混寫:通過addObserver: forKeyPath: options: context:為ClassA的屬性添加觀察者對象后,會動態(tài)的生成ClassNSKVONotifying_A(ClassA的子類),并將原本指向A對象的isa指針指向NSKVONotifying_A,NSKVONotifying_A會重寫Setter方法,重寫后的Setter方法負(fù)責(zé)通知所有觀察對象屬性的修改。這種改變被觀察者A對象的isa指針的方式就成為isa混寫
觀察者對象通過實現(xiàn)observeValueForKeyPath: ofObject: change: context:方法來監(jiān)聽的屬性修改- 通過兩個斷點及命令行方法
po object_getClassName(obj)來查看調(diào)用addObserver: forKeyPath: options: context:方法前后obj指針指向的ClassName
動態(tài)的生成Class NSKVONotifying_A- NSKVONotifying_A 對Setter方法的重寫
//NSKVONotifying_A重寫后的Setter方法 - (void)setValue:(id)obj{ [self willChangeValueForKey:@"keyPath"]; [super setValue: obj] //didChangeValueForKey:方法會觸發(fā)observeValueForKeyPath: ofObject: change: context:方法回掉,來通知觀察者Value發(fā)生變化 [self didChangeValueForKey:@"keyPath"]; }
- 需要注意的問題
- 通過KVC設(shè)置Value能否生效:可以的,KVC方法會調(diào)用Setter方法,進(jìn)而觸發(fā)KVO使之生效
[obj setValue:@2 forKey:@"value"];
- 通過成員變量直接賦值value能否生效:不可以的,因為沒有調(diào)用Setter方法
//可以通過手動添加KVO方法實現(xiàn) //變量賦值之前添加willChangeValueForKey:方法,賦值之后添加didChangeValueForKey:方法來實現(xiàn)手動添加KVO [self willChangeValueForKey:@"keyPath"]; _value += 1; [self didChangeValueForKey:@"keyPath"];總結(jié):
- 使用
setter方法改變值KVO才會生效- 使用
KVC(setValue:forKey:)改變值KVO才會生效- 成員變量直接修改需要
手動添加KVO才會生效
KVC
1.KVC 是什么
- KVC是Key-Value coding的縮寫(Apple為我們提供的一種
鍵值編碼技術(shù))- 允許開發(fā)者通過
key直接訪問對象的屬性方法或者成員變量,而不需要調(diào)用明確的存取方法。valueForKey:獲取對象中和key同名或相似名稱對象的值setValue:forKey:設(shè)置對象中和key同名或相似名稱對象的值valueForKey:與setValue:forKey:詳細(xì)的搜索方法可以在KVC官方文檔中找到。
+ (BOOL)accessInstanceVariablesDirectly方法,如果返回NO,可以避免KVC訪問自己的成員變量(沒有setter、getter方法的變量)
相關(guān)KVC底層的一些介紹 iOS底層原理探究之----KVC
- 這種鍵值編碼技術(shù)是否會破壞面向?qū)ο蟮木幊谭椒ɑ蛘呤欠駮羞`面向?qū)ο蟮木幊趟枷?/strong>
由于key是沒有限制的,所以只要我們知道了對應(yīng)類的私有成員變量(變量、屬性都可以)的名字,就可以通過setValue:forKey對其值進(jìn)行修改,所以是有違面向?qū)ο蟮木幊趟枷氲?/p>
屬性關(guān)鍵字
@property的本質(zhì)是什么?
@property=ivar+getter+setter;
屬性(property)有兩大概念:ivar(實例變量)、存取方法(access method =getter+setter)。
屬性(property)作為 Objective-C 的一項特性,主要的作用就在于封裝對象中的數(shù)據(jù)。
@synthesize和@dynamic分別有什么作用?
①@property有兩個對應(yīng)的詞,@synthesize(默認(rèn))和@dynamic。如果@synthesize和@dynamic都沒寫,那么默認(rèn)的就是@syntheszie var = _var;
②@synthesize的語義是如果你沒有手動實現(xiàn)setter方法和getter方法,那么編譯器會自動為你加上這兩個方法。
③@dynamic告訴編譯器:屬性的ivar、setter和getter不自動生成。(當(dāng)然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var,然后你沒有提供setter和getter方法,編譯的時候沒問題,但是當(dāng)程序運(yùn)行到instance.var = someVar,由于缺少setter方法會導(dǎo)致程序崩潰;或者當(dāng)運(yùn)行到someVar = instance.var時,由于缺少getter方法同樣會導(dǎo)致崩潰。編譯時沒問題,運(yùn)行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定。@interface TestClass() //{ // @private // NSString *_name; //} @property (nonatomic, copy) NSString *name; @end @implementation TestClass @dynamic name; //不會自動生成 _ name 變量和 get、set 方法 - (NSString *)name{ //方法①:可以通過關(guān)聯(lián)對象來實現(xiàn)setter、getter方法 NSString *str = objc_getAssociatedObject(self, @selector(name)); if (str) { return str; } [self setName:name]; return name; //return _name; 方法②:可以通過成員變量來實現(xiàn)setter、getter方法 } - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, @selector(name), @"", OBJC_ASSOCIATION_COPY); //_name = name; } @end
1.屬性關(guān)鍵字主要類別
- 讀寫權(quán)限:
reloadonly只讀,readwrite(默認(rèn))可讀寫- 原子性:
atomic(默認(rèn))原子性,nonatomic非原子性- 引用計數(shù):
retain(MRC)/strong(ARC),assin(默認(rèn))/unsafe_unretained(MRC中使用較多,ARC中基本不使用),weak,copyARC下,不顯示指定任何屬性關(guān)鍵字時,默認(rèn)的關(guān)鍵字都有哪些?
對應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是
atomic,readwrite,assign
對于普通的OC對象
atomic,readwrite,strong
- atomic關(guān)鍵字
atomic關(guān)鍵字的線程安全是針對setter、getter方法即對象的讀寫操作來說的,對于對象的操作并不能保證線程安全,如對atomic修飾的NSMutableArray進(jìn)行增刪操作就無法保證是線程安全的。atomic內(nèi)部是通過@synchronized來實現(xiàn)原子性的
關(guān)于@synchronized的文章: 正確使用多線程同步鎖@synchronized//atomic時,setter、getter方法的默認(rèn)實現(xiàn) - (void)setAge:(int)age { @synchronized(self){ _age = age; } } - (int)age { @synchronized(self){ return _age; } }
- assing關(guān)鍵字
assing關(guān)鍵字的特點:修飾基本數(shù)據(jù)類型,如int,BOOL等;修飾對象類型時,不改變其引用計數(shù);當(dāng)其修飾對象被銷毀時仍指向原內(nèi)存地址,從而造成野指針(懸垂指針)。
- weak關(guān)鍵字
weak關(guān)鍵字特點:修飾對象,不改變其引用計數(shù);當(dāng)其修飾對象被銷毀時,會自動將指針置nil;weak修飾的指針默認(rèn)值是nil。
weak可以自動將空指針置nil原理:runtime維護(hù)了一個Weak表,weak_table_t用于存儲指向某一個對象的所有Weak指針。Weak表其實是一個哈希表,key是所指對象的地址,value是weak指針的地址的數(shù)組。在對象回收的時候,就會在weak表中進(jìn)行搜索,找到所有以這個對象地址為鍵值的weak對象,從而置為nil。由于維護(hù)了一個weak表,所以對性能有些許影響。
- unsafe_unretained關(guān)鍵字
unsafe_unretained關(guān)鍵字特點:修飾基本數(shù)據(jù)類型,如int,BOOL等;修飾對象時,不改變其引用計數(shù);當(dāng)其修飾對象被銷毀時,指針仍指向原內(nèi)存地址,從而造成野指針(懸垂指針)(基本被assing替代)。
- copy關(guān)鍵字
copy關(guān)鍵字:修飾對象,對其執(zhí)行copy操作淺拷貝:對內(nèi)存地址的復(fù)制,讓目標(biāo)對象指針與源對象指針指向同一片內(nèi)存空間,不會引起內(nèi)存空間的分配;會讓源對象內(nèi)存引用計數(shù)加1。深拷貝:對內(nèi)存的復(fù)制,讓目標(biāo)對象指針指向一片內(nèi)容與源對象指針內(nèi)容相同的內(nèi)存空間,會引起內(nèi)存的分配;不會讓源對象內(nèi)存引用計數(shù)加1。區(qū)分深拷貝與淺拷貝:
① 是否開辟了新內(nèi)存空間(深拷貝會開辟新內(nèi)存空間而淺拷貝不會);
② 是否影響引用計數(shù)(深拷貝不會影響引用計數(shù)而淺拷貝會影響被拷貝對象的引用計數(shù));
copy與mutableCopy:
① 對可變對象的copy操作和mutableCopy操作都是深拷貝;
② 對不可變對象的copy操作是淺拷貝,mutableCopy操作是深拷貝;
③copy返回的都是不可變對象,mutableCopy返回的都是可變對象;@property(copy)NSMutableArray *array;// 會產(chǎn)生什么問題
①如果賦值過來的是NSMutableArray,copy操作之后變成了NSArray,是深拷貝;
②如果賦值過來的是NSArray,copy操作之后是NSArray,是淺拷貝;
③不論賦值過來的是NSArray還是NSMutableArray,copy操作之后都是NSArray,如果調(diào)用NSMutableArray相關(guān)的如addObject:等方法,會引起crash;
- MRC下如何重寫retain修飾變量的setter 方法
@interface Test
@property (nonatomic, retain) id obj;
@end
@implementation Test
- (void)setObj:(id)obj{
/*
這里如果不進(jìn)行!=判斷,如果恰好_obj==obj,并且_obj的引用計數(shù)為1,
調(diào)用[_obj release]后obj對象會被銷毀,obj對象變?yōu)閚il,
這個時候_obj = [obj retain]相當(dāng)于_obj = [nil retain],
后續(xù)程序?qū)obj對象進(jìn)行操作時,會引起crash。
*/
if (_obj != obj){
[_obj release];
_obj = [obj retain];
}
}
@end