iOS筆記篇-熟悉OC<二>

前言

OC是面向?qū)ο蟮淖兂烧Z(yǔ)言,“對(duì)象”就是“基本構(gòu)造單元”。而對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過(guò)程,就是“消息傳遞”。對(duì)于OC這種消息結(jié)構(gòu)的語(yǔ)言理解“消息傳遞”的理解,非常有必要。
當(dāng)應(yīng)用程序運(yùn)行起來(lái),圍棋提供相關(guān)支持的代碼叫做"Object-c運(yùn)行期環(huán)境"(Objective-C runtime), 它提供了一些是得對(duì)象之間能夠傳遞信息的重要函數(shù),并且包含創(chuàng)建類實(shí)例所用的全部邏輯。理解運(yùn)行期環(huán)境中各個(gè)部分協(xié)同工作的原理,你的開(kāi)發(fā)水平將會(huì)進(jìn)一步的提升。

第一、關(guān)于屬性(property)

1.property、 synthesize、 dynamic

參考一下框圖已經(jīng)非常詳細(xì)了。


Property

2.在對(duì)象內(nèi)部使用訪問(wèn)實(shí)例變量

筆者建議在讀取實(shí)例變量的時(shí)候采用直接訪問(wèn)的形式,而在設(shè)置實(shí)例變量的時(shí)候采用屬性來(lái)做。

直接訪問(wèn)實(shí)際變量?jī)?yōu)點(diǎn)
1.由于不經(jīng)過(guò)OC的“方法派發(fā)”步驟,所以直接訪問(wèn)實(shí)例變量的速度比較快。
不會(huì)調(diào)用“設(shè)置方法”,繞過(guò)了相關(guān)屬性的“內(nèi)存管理語(yǔ)義”,比如:ARC下直接訪問(wèn)一個(gè)聲明為copy的屬性,那么并不會(huì)copy該屬性,只會(huì)保留新值,釋放舊的值。
不會(huì)觸發(fā)“KVO(key-Value Observing, KVO)”通知。這樣做是否會(huì)產(chǎn)生問(wèn)題還需要取決于具體的對(duì)象的行為

通過(guò)屬性來(lái)分文有助排查錯(cuò)誤,因?yàn)榭梢越o“獲取方法”和“設(shè)置方法”中新增斷點(diǎn),監(jiān)控調(diào)用者和調(diào)用時(shí)機(jī)。

注意點(diǎn)
1.在初始化方法中應(yīng)該如何設(shè)置屬性值,因?yàn)樽宇惪赡軙?huì)覆寫(xiě)該方法。為了防止初始化父類用set方法調(diào)用到不確定子類的set的方法,所以在初始化方法和delloc方法中,總是通過(guò)實(shí)例變量來(lái)讀寫(xiě)數(shù)據(jù)
2.當(dāng)存在著懶加載的情況,那么一定需要使用屬性的方法來(lái)讀取數(shù)據(jù)。

第二、關(guān)于“對(duì)象等同性”

== 比較出來(lái)的是指針的比較,學(xué)習(xí)C語(yǔ)言指針的話,對(duì)此將非常容易理解。

NSString *fir = @"number:123";
NSString *sec = @"number:123";
BOOL equalA = (fir == sec);                   // NO
BOOL equalB = [fir isEqual:sec];            // YES
BOOL equalC = [fir isEqualToString:sec];    //YES

從以上的例子可以看出,==是比較指針,isEqualToString是NSString提供自己獨(dú)特的等同性的判斷方法。并且調(diào)用該方法比isEqual方法快。后者還需奧執(zhí)行額外的步驟。

1.關(guān)于isEqual方法

如果isEqual的方法判定兩個(gè)對(duì)象相等,那么其hash方法也必須返回同一個(gè)值,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值,但是"isEqual:"方法未必會(huì)認(rèn)為二者相等。
如果經(jīng)常需要判斷等同性,那么可以自己創(chuàng)建等同性的方法,提高性能。正如 isEqualToString一樣
對(duì)于NSArray對(duì)象中,如果對(duì)應(yīng)位置上的對(duì)象均相等,那么這兩個(gè)數(shù)組就等同,稱之為“深度等同性判定”。當(dāng)然比如從數(shù)據(jù)庫(kù)中創(chuàng)建出來(lái)的對(duì)象代用Primary Key那么我們只需要比較readonly的Primagry Key是否相等來(lái)判定等同,不需要檢測(cè)所有的字段。所以只有類的編寫(xiě)者才可以確定兩個(gè)對(duì)象實(shí)例在何種情況下可以被判定為相等。

2.工廠模式和Class Cluster

類族模式可以把所有實(shí)現(xiàn)的細(xì)節(jié)隱藏到一套簡(jiǎn)單的公共接口的后面。
系統(tǒng)框架中經(jīng)常使用類族
從類族的公共抽象基類中繼承子類是要當(dāng)心。

3.技巧篇

在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù),如下代碼不過(guò)需要注意該部分被包含在頭文件 中

import <objc/runtime.h>

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

@implementation Person (Property)
//使用的是不透明的指針,一般選用靜態(tài)全局變量
static NSString const *kcountryKey = @"PersonCountryKey";
static NSString const *kdetailAddressKey = @"PersonDetailAddressKey";

@dynamic country, detailAddress;

-(NSString *) country{
    return objc_getAssociatedObject(self, &kcountryKey);
}

-(NSString *)detailAddress{
    return objc_getAssociatedObject(self, &kdetailAddressKey);
}

-(void) setCountry:(NSString *)country{
    objc_setAssociatedObject(self, &kcountryKey, country, OBJC_ASSOCIATION_COPY);
}

-(void) setDetailAddress:(NSString *)detailAddress{
    objc_setAssociatedObject(self, &detailAddress, detailAddress, OBJC_ASSOCIATION_COPY);
}

@end

@interface Person (Property)
@property (nonatomic, readwrite, copy) NSString* country;
@property (nonatomic, readwrite, copy) NSString* detailAddress;
@end

如何應(yīng)用,如下應(yīng)用,拓展了person的屬性,并且能很好的處理了多個(gè)AlertView的判斷問(wèn)題。但是采用該方案也需要注意:block可能要捕獲(capture)某些常量,這樣也許會(huì)造成"保留環(huán)"。筆者建議創(chuàng)建一個(gè)UIAlertView的子類,把塊作為子類的屬性。這種做法要比關(guān)聯(lián)好。

#import "ViewController.h"
#import "Person+Property.h"
#import <objc/runtime.h>

typedef void (^AlertBlock)(NSInteger buttonIndex);
@interface ViewController ()<UIAlertViewDelegate>

@end

@implementation ViewController
static NSString* const kAlertViewKey = @"MyAlertViewKey";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person* xiaoMing = [[Person alloc] initWithFirstName:@"Ren" lastName:@"XiaoMing"];
    NSString* fullName = [NSString stringWithFormat:@"你輸入的名字是:%@", xiaoMing.fullName];
    NSLog(@"%@", fullName);
    
    UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"請(qǐng)確認(rèn)" message:fullName delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
    [alertView show];
    AlertBlock nameBlock = ^(NSInteger buttonIndex){
        NSLog(@"姓名確認(rèn)中你選擇了%ld", buttonIndex);
    };
    objc_setAssociatedObject(alertView, &kAlertViewKey, nameBlock, OBJC_ASSOCIATION_COPY);
    
    xiaoMing.country = @"China";
    xiaoMing.detailAddress = @"Shen Zhen";
    NSString* address = [NSString stringWithFormat:@"小明居住的地址是:%@ %@", xiaoMing.country, xiaoMing.detailAddress];
    NSLog(@"%@", address);
    
    UIAlertView* addressAlertView = [[UIAlertView alloc] initWithTitle:@"請(qǐng)確認(rèn)" message:address delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
    [addressAlertView show];
    AlertBlock addressBlock = ^(NSInteger buttonIndex){
        NSLog(@"地址確認(rèn)中你選擇了%ld", buttonIndex);
    };
    objc_setAssociatedObject(addressAlertView, &kAlertViewKey, addressBlock, OBJC_ASSOCIATION_COPY);
    
}

#pragma mark - UIAlertViewDelegate
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    AlertBlock tempblock = objc_getAssociatedObject(alertView, &kAlertViewKey);
    tempblock(buttonIndex);
}

第三、關(guān)于objc_msgSend

1. 概述

  • 用OC的話來(lái)說(shuō),這玩樣叫做"傳遞消息(pass a message)"。消息有"名稱(name)"或者"selector",可以接受的參數(shù),而且可能還有返回值。
  • C語(yǔ)言是“靜態(tài)綁定”,也就是說(shuō),在編譯期就能決定運(yùn)行時(shí)所調(diào)用的函數(shù)。如果不考慮"內(nèi)聯(lián)"的情況,那么編譯期在編譯代碼的時(shí)候就知道了程序中存在的函數(shù),并且將其硬編址,這個(gè)進(jìn)行過(guò)單片機(jī)開(kāi)發(fā)和仿真過(guò)的,對(duì)此將深有體會(huì),調(diào)用的函數(shù)最終將映射到某個(gè)硬件地址。
  • OC語(yǔ)言使用"動(dòng)態(tài)綁定",所需要調(diào)用的函數(shù)知道運(yùn)行期才能確定。
  • 對(duì)于OC語(yǔ)言而言,如果想某個(gè)對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定的機(jī)制來(lái)決定需要調(diào)用的方法。在底層,所有的方法都是普通的C語(yǔ)言函數(shù),然而對(duì)象接收到消息之后,究竟該調(diào)用哪個(gè)方法則完全取決于運(yùn)行期,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得OC成為一門(mén)真正的動(dòng)態(tài)語(yǔ)言。

id returnValue = [someObject messageName:parameter];

  • 其中someObject就是消息的接受者receiver, messageName叫做selector,而參數(shù)parameter+selector合起來(lái)就稱為"消息"
  • 將其轉(zhuǎn)化成一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù),原型如下:
    void objc_msgSend(id self, SEL cmd, ...)
OC中消息傳遞圖.png

簡(jiǎn)單點(diǎn)歸納的來(lái)說(shuō),消息由接收者、selector以及參數(shù)構(gòu)成的。給某個(gè)對(duì)象“發(fā)送消息”也就是相當(dāng)于在該對(duì)象上“調(diào)用方法”。
發(fā)送給對(duì)象的消息都要經(jīng)過(guò)"動(dòng)態(tài)消息派發(fā)系統(tǒng)",來(lái)處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,并執(zhí)行代碼。

2. 消息轉(zhuǎn)發(fā)的過(guò)程

1.首先通過(guò)該方法來(lái)詢問(wèn)是否能處理該消息
<PS:如果想通過(guò)運(yùn)行期動(dòng)態(tài)插入方法,那么就需要考慮在這里插入該方法了CALayer里面就應(yīng)用類似的方法實(shí)現(xiàn)。于是開(kāi)發(fā)者能夠向其中新增加自定義的屬性,而這些屬性的存儲(chǔ)工作由基類來(lái)完成>

+(BOOL) resolveInstanceMethod:(SEL) selector;
+(BOOL) resolveClassMethod:(SEL) selector;

2.如果不能處理,接收還有第二次機(jī)會(huì)能處理未知的子,運(yùn)行系統(tǒng)會(huì)問(wèn)它,能不能把這條消息轉(zhuǎn)發(fā)給其他接收者。

-(id) forwardingTargetForSelector:(SEL) selector;

如果當(dāng)前接收者找到了可以處理該部分的對(duì)象的則將它返回(備援接收者), 否者返回nil.

第四、OC業(yè)界的黑魔法

1.原理概述

OC的黑魔法通常是用來(lái)調(diào)試“黑盒方法”。
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)之上,使得“動(dòng)態(tài)分配系統(tǒng)”能夠據(jù)此找應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式表示,這種指針叫做:IMP,原型如下:

id(* IMP)(id, SEL, ...)

比如以NSString的方法為例,如下圖


NSString的映射表 .png

2. 如何實(shí)現(xiàn)

下面給了一個(gè)代碼實(shí)現(xiàn)的例子

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* apple = @"apple";
    NSLog(@"執(zhí)行前===================================");
    NSLog(@"Uppercase = %@", [apple uppercaseString]);
    NSLog(@"LowerCase = %@", [apple lowercaseString]);
    Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);
    NSLog(@"執(zhí)行后===================================");
    NSLog(@"Uppercase = %@", [apple uppercaseString]);
    NSLog(@"LowerCase = %@", [apple lowercaseString]);
}

程序運(yùn)行的輸出結(jié)果:

2016-12-29 14:25:00.535 OC黑魔法[14618:194380] 執(zhí)行前===================================
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] Uppercase = APPLE
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] LowerCase = apple
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] 執(zhí)行后===================================
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] Uppercase = apple
2016-12-29 14:25:00.537 OC黑魔法[14618:194380] LowerCase = APPLE

總之通過(guò)該方案,開(kāi)發(fā)者可以為那些“完全不知道其實(shí)現(xiàn)的”黑盒方法增加日記功能,這非常有助于調(diào)試。但是筆者認(rèn)為應(yīng)該盡量少用,要不然代碼將非常不好維護(hù),不容易懂。

第四、OC中類的本質(zhì)

1.OC中類的本質(zhì)

  • 從下圖中可以才看出,每個(gè)對(duì)象的結(jié)構(gòu)體的收個(gè)成員都是Class類的變量,該變量定義了對(duì)象所屬的類,通常稱之為"is a"指針。比如:NSString的isa就是指向NSString。
  • 類的結(jié)構(gòu)體存放類的“元數(shù)據(jù)(metadata)”,例如類的實(shí)現(xiàn)實(shí)現(xiàn)了幾個(gè)方法,具備多少個(gè)變量等信息。此結(jié)構(gòu)體的首個(gè)變量也是isa指針,這個(gè)說(shuō)明Class本身也是OC對(duì)象。還有一個(gè)super_class,而且二者的類型也是Class。類對(duì)象所屬的類型又是另外一個(gè)類,叫做“元類(metaclass)”,用來(lái)表述類對(duì)象所具備的元數(shù)據(jù)??梢岳斫鉃?,每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)之相關(guān)的元類。
//參見(jiàn):objc.h
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

//參見(jiàn)Objc runtime.h中
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
//#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
//#endif
} OBJC2_UNAVAILABLE;

OC中的對(duì)象.png

2.判斷類繼承體系中查詢類型信息

    NSMutableDictionary* dict = [NSMutableDictionary new];
    [dict isMemberOfClass:[NSDictionary class]];            //NO
    [dict isMemberOfClass:[NSMutableDictionary class]];     //YES
    [dict isKindOfClass:[NSDictionary class]];              //YES
    [dict isKindOfClass:[NSArray class]];                   //NO

以上這樣類型查詢方法是使用isa指針來(lái)獲取對(duì)象所屬的類,然后通過(guò)super_class在繼承體系下游走。
比較兩個(gè)類對(duì)象是否等同的也可以采用==操作符,而不要使用Objective-C對(duì)象常用的"isEqual:"方法來(lái)做。原因在于,類對(duì)象是“單例”,再應(yīng)用程序范圍內(nèi),每個(gè)類僅僅有一個(gè)實(shí)例。

總之,盡量要使用類型查詢方法來(lái)確定對(duì)象類型,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象實(shí)現(xiàn)可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。

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

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

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