Objective-C 特性相關(guān)

分類(Category)

  1. 分類可以做些什么
  • 聲明私有方法
  • 分解體積龐大的類文件
  • 把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);
}
  1. 分類有哪些特點
  • 運(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);
}
  1. 分類中都可以添加那些內(nèi)容
  • 實例方法
  • 類方法
  • 協(xié)議
  • 屬性(setter、getter,需要通過關(guān)聯(lián)對象來創(chuàng)建實例變量)
  1. 分類的實現(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)

  1. 擴(kuò)展可以做什么
  • 聲明私有屬性
  • 聲明私有方法
  • 聲明私有變量
  1. 擴(kuò)展的特點
  • 編譯時決議
  • 只以聲明的形式存在,多數(shù)情況下寄生于宿主類的.m中。
  • 不能為系統(tǒng)類添加擴(kuò)展
    分類和擴(kuò)展的區(qū)別

代理(Delegate)

  1. 代理是什么

-代理模式是一種設(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)

  1. 通知是什么
  • 是使用觀察者模式來實現(xiàn)的用于跨層傳遞消息的機(jī)制。
  • 傳遞方式為一對多
  1. 通知機(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:為Class A屬性添加觀察者對象后,會動態(tài)的生成Class NSKVONotifying_A(Class A的子類),并將原本指向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"];
}
  1. 需要注意的問題
  • 通過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

  1. 這種鍵值編碼技術(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、settergetter不自動生成。(當(dāng)然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var,然后你沒有提供settergetter方法,編譯的時候沒問題,但是當(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,copy

ARC下,不顯示指定任何屬性關(guān)鍵字時,默認(rèn)的關(guān)鍵字都有哪些?
對應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是
atomic, readwrite, assign
對于普通的OC對象
atomic,readwrite, strong

  1. 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; 
   }
}  
  1. assing關(guān)鍵字

assing關(guān)鍵字的特點:修飾基本數(shù)據(jù)類型,如intBOOL等;修飾對象類型時,不改變其引用計數(shù);當(dāng)其修飾對象被銷毀時仍指向原內(nèi)存地址,從而造成野指針(懸垂指針)。

  1. 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是所指對象的地址,valueweak指針的地址的數(shù)組。在對象回收的時候,就會在weak表中進(jìn)行搜索,找到所有以這個對象地址為鍵值的weak對象,從而置為nil。由于維護(hù)了一個weak表,所以對性能有些許影響。

  1. unsafe_unretained關(guān)鍵字

unsafe_unretained關(guān)鍵字特點:修飾基本數(shù)據(jù)類型,如int,BOOL等;修飾對象時,不改變其引用計數(shù);當(dāng)其修飾對象被銷毀時,指針仍指向原內(nèi)存地址,從而造成野指針(懸垂指針)(基本被assing替代)。

  1. 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ù));

copymutableCopy
① 對可變對象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

  1. 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容