一、Objective-C 簡介
- 可以用 OC 開發(fā) Mac OSX 平臺和 IOS 平臺的應用程序
- 完全兼容 C 語言,文件以.m結尾
- 可以在 OC 代碼中混入 C 語言代碼,甚至是 C++代碼「混入C++代碼的OC文件以.mm結尾」
- 關鍵字:為了區(qū)分 C 語言和 C++,OC 基本所有的關鍵字都以
@開頭「包括字符串,且中間不能有空格」 - 開發(fā)過程:.m「源文件」→編譯→ .o「目標文件」→鏈接→ a.out「可執(zhí)行文件」
編譯:cc -c 文件名.m 文件名2.c 文件名3.m
連接:cc 文件名1.o 文件名2.o 文件名3.o -framework Fundation「如果包含 Fundation框架的話」 - @package「介于@private 和 @public 之間」
其他包中訪問就是 private
當前包中訪問就是 public - BOOL 類型
//BOOL類型本質
typedef signed char BOOL;
//BOOL變量有兩種取值 YES/N
#define YES (BOOL)1
#define NO (BOOL)0
//BOOL類型輸出「當做整數來用」
NSLog(@"%d %d",YES,NO);
二、Objective-C 特點
#pragma mark 「純粹是Xcode的工具,非objc語法,可以寫在任意一行,幫助閱讀和查找函數」
不允許修改OC對象的 結構體成員
1. 對象中有一個 isa 指針,指向對象的類
基于 C 底層面向過程,弱語法,在運行過程中才會檢測對象有沒有實行相應的方法
2. __kindof
類型說明關鍵字「iOS9新增」
格式:- (__kindof ClassName *)function;
作用:告訴編譯器返回值可能是 ClassName 或 ClassName的子類
3. 編譯器特性: 點語法
- 前提:給屬性提供了get、set方法
- 本質:set、get方法 的方法調用
是編譯器的特性, 會在程序翻譯成二進制的時候 將 .語法自動轉換為set、get方法 - 注意:寫在同一 set/get 方法里會造成死循環(huán)
- 在 = 的左邊, 編譯器自動轉換為set方法
在 = 的右邊, 或無 =, 編譯器自動轉換為get方法
p.name = @"haha" //編譯器轉為 [p setName:@"haha"]; 但不能寫成 p.setName
NSString *s = p.name; //編譯器轉為 [p name:@"haha"]; 但不能寫成 p.getName
- 一般給成員變量賦值, 若不是給成員變量賦值不建議使用, 但也可以用
p.test; //編譯器轉為 [p test];
<h2 id="propertyFirst">4. 編譯器特性: @property @synthesize 關鍵字</h2>
@property - 上 基礎
- 只用在 @interface 中
- 自動生成 某個變量a的 getter 和 setter 聲明
如果成員變量a 未聲明,則自動聲明一個 私有的 _a - Xcode版本是4.4及其以后
自動生成 某個變量的 getter 和 setter 聲明 和 實現
@property int age
// 編譯器會自動替換為以下兩行
- (void)setAge(int):age;
- (int)age;
// 參數不止一個 但 數據類型要相同 「一般很少這樣連在一起」
@property int min, max;
- nullable 標識符「iOS9新增」能為空的參數,默認情況,只用于對象方法
- nonnull 標識符「iOS9新增」不能為空的參數
如果為空,編譯器會警告
@property (nonatomic, nonnull) NSString *name; // getter 和 setter方法都不能為空
@property (nonatomic) NSString *__nonnull name; // 和上一句等價,表示的 name值不能為空
// setter方法能為 nil,getter方法不能為 nil
// 因此,此屬性定義后,要重寫getter方法,確保getter方法不為nil,否則 編譯器會警告
@property (nonatomic, null_resettable) NSString *name;
@property (nonatomic, null_resettable) NSString *name;
-
NS_ASSUME_NONNULL_BEGIN宏定義,期間的變量不能為 nil
NS_ASSUME_NONNULL_BEGIN
@interface class
@property NSString *__nullable name;
@property NSString *mother;
@property NSString *father;
@end
NS_ASSUME_NONNULL_END
// 等價于
@interface class
@property NSString *__nullable name;
@property NSString *__nonnull mother;
@property NSString *__nonnull father;
@end
- 泛型「iOS9新增」
// 規(guī)定數組只能裝 NSString 類型數據
@property (nonatomic) NSArray<NSString *> *names;
// 字典的key只能為NSString類型,value只能為NSNumber類型
@property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *names;
// 自定義泛型,classType只是個占位符,可以隨便起名
@interface className<classType> : NSObject
- (void)set:(classType)obj;
- (classType)get;
@end
// 使用自定義類型
// 不指名,默認為 id 類型
className *test0 = [[className alloc] init];
// 指明類型,必須為指明的類型
className<NSString *> *test1 = [[className alloc] init];
className<NSMutableString *> *test2 = [[className alloc] init];
// 類型轉換
// 以下兩行代碼不會報警告
test0 = test1;
test1 = test0;
// 以下代碼會報警告
// 為了防止警告,自定義泛型應添加 __covariant 為 @interface className<__covariant classType> : NSObject
test1 = test2; // __covariant:子類行 轉為 父類型「僅限泛型的類型」
test2 = test1; // __cotravariant:父類行 轉為 子類型「僅限泛型的類型」
@synthesize
- 只用在 @implementation 中
- 自動生成 成員變量age 的 getter 和 setter 實現
- 如果成員變量 不存在,就會自動生成 @private 作用域的 成員變量
在 implementation 里創(chuàng)建的成員變量,作用域為 @private - 注意
如果手動實現了setter/getter 方法,編譯器自動生成 一個缺少的 getter/setter 方法
如果手動實現了setter 和 getter 兩個方法,編譯器不會自動生成,且不會自動生成不存在的變量
@synthesize age = _age;
// 編譯器會自動替換為以下代碼
- (void)setAge(int):age {
age = _age;
}
- (int)age {
return _age;
}
@synthesize age;
// 編譯器會自動替換為以下代碼
- (void)setAge(int):age {
self->age = age;
}
- (int)age {
return age;
}
// 參數不止一個「一般很少這樣連在一起」
synthesize min = _min, max = _max;
5. 常用函數
+ (void)load
- 作用:程序啟動時加載所有 類和分類,加載類和分類的 +load方法
- 先調用父類的 +load 后調用子類的 +load方法
- 不管程序有沒有用到 這個類,都會調用類的 +load方法
+ (void)initialize
- 作用:在對像創(chuàng)建前進行一些初始化操作
- 第一次使用類的時候調用 +initialize方法「比如創(chuàng)建對象」
- 一個類只會調用一次 +initialize方法,先調用父類,在調用子類
*- (NSString )description
NSLog 和 %@ 輸出某個對象時
- 會調用對象的description方法,并拿到返回值進行輸出
- 默認格式:<類名: 對象內存地址>
*+ (NSString )description
NSLog 和 %@ 輸出某個類時
- 會調用類的description方法,并拿到返回值進行輸出
- 默認格式:類名
(void)NSLog 補充
- 以輸出 C語言字符格式,不能輸出中文
char *s = "C語言字符串";
NSLog(@"%s", s); // 字符串含英文不能輸出
NSString *o = @"Objective-c語言字符串";
NSLog(@"%@", o);
- <code>__FILE__</code>: 源代碼文件名
- <code>__LINE__</code>: NSLog 代碼在第幾行
- <code>_cmd</code>: 當前方法 SEL
// 會造成死循環(huán)
- (void)test{ [self performSelector:_cmd];
}
6. SEL 數據類型
本質:消息機制中的消息就是 SEL
定義:<code>typedef struct objc_selector *SEL;</code>
作用:
- 每個類的方法列表都存儲在類中
- 每個方法都有對應的 SEL類型的對象
- 根據 SEL類型對象可以找到方法地址,進而調用方法
使用:
// 方法一
SEL s1 = @seletor(方法名);
// 方法二
SEL s2 = NSSelectorFromString("方法名");
// 其他用法
// 將 SEL對象轉換成 NSString 對象
NSString *s = NSStringFromSelector(@selector(方法名));
// 間接調用
[對象名 performSelector: @selector(方法名)];
7. @protocol 協議
作用:
- 用來聲明方法「不能聲明 成員變量」
- 類遵守了協議,就會擁有協議的所有方法聲明
- 父類遵守了協議,子類也會遵守
基協議:
- 基類:NSObject ,最基本的類,任何其他類最終都要繼承它
- 基協議:NSObject,最根本的協議,與基類同名,聲明了很多基本的方法「description, retain, release」
建議每個新的協議都要遵守基協議
格式:
- 協議的編寫
@protocol 協議名稱
@required // 必須實現的方法「默認,不實現 編譯器會警告」
// 方法聲明
@optional // 可以不用實現的方法
// 方法聲明
@end // 與 @protocol 對應
- 類遵守多個協議
#import "協議名1.h" // 在 .h 文件這里也可以用 @protocol 協議名1, 協議名2
#import "協議名2.h" // 使用了提前聲明的方法,在 .m文件要 #import "協議名1.h" ...
@interface 類名 : 父類 <協議名1, 協議名2>
@end
- 協議遵守多個協議
#import "協議名1.h"
#import "協議名2.h"
@protocol 協議名 <協議名1, 協議名2>
@end
- 定義一個變量,限制該變量保存的對象 必須遵守協議 1, 2
如果沒有對應的協議,編譯器會警告
類名 <協議名1, 協議名2> *變量名;
id<協議名1, 協議名2> 變量名; // 與上面一樣效果
- @property 中聲明的屬性也可用做一個遵守協議的限制
@property (nonmatic, strong) 類名<協議名1, 協議名2> *屬性名;
@property (nonmatic, strong) id<協議名1, 協議名2> 屬性名; // 與上面一樣效果
用法:
協議可以定義在單獨 .h 文件里,也可以定義在某個類中
- 協議只用在一個類中,把協議定義在該類中
- 協議用在多個類中,把協議定義在單獨的 .h 文件里
8. 代理設計模式
原理:一個對象麻煩的事,代理給另一個對象做
原則:明確屬性、方法,低耦合
代理的作用:
- A對象 監(jiān)聽 B對象的行為,A成為 B對象的代理
- A對象想 告訴 B一些事情,B成為 A對象的代理
代理設計模式開發(fā)步驟:
- 定義一個 protocol「協議名字格式:類名+Delegate」,在協議里 聲明一些代理方法「一般代理方法是
@optional」
代理的方法「如果代理有實現這個方法,才會調用這個方法」
if ([self.delegate respondsToSelector:@selector(loadMoreFooterDidClickLoadMoreBtn:)]) {
[self.delegate loadMoreFooterDidClickLoadMoreBtn:self];
}
- 聲明一個低耦合的代理屬性:
@property (nonatomic,weak) id<protocol> delegate;
如果代理指針是 強指針 則 可能 產生循環(huán)引用
DelegateClass *d = [[DelegateClass alloc] init];
self.tableView = d; // self → tableView → d
d.delegate = self; // d → delegate → self
// 相當于:self → tableView → d → delegate → self
self.tableView.delegate = self; // 循環(huán)引用
- 在內部發(fā)生某些行為時,調用代理對應的代理方法,通知代理內部發(fā)生了什么事
- 設置代理:B對象.delegate = 監(jiān)聽B對象行為的 A對象
- A對象遵守協議,實現 B對象要求的代理方法
代理和通知的區(qū)別:
- 代理:一個對像只能告訴 一個對象 一些事情
- 通知:一個對象可以告訴 多個對象 一些事情
9. 通知
通知中心「NSNotificationCenter」:
- 每一個程序都有 一個通知中心實例,專門負責不同對象的消息傳遞
- 任何對象都可以向通知中心發(fā)布通知,描述自己在干什么
- 任何對象都可以申請在某個特定的通知發(fā)布時 收到這個通知「Observer」
初始化通知對象
/**
* @param aName 通知的名稱
* @param anObject 通知的發(fā)布者
* @param aUserInfo 額外的信息(發(fā)布者傳給接受著的信息內容)
* @return 初始化的通知對象
*/
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;
發(fā)布通知
/**
* @param aName 通知名稱
* @param anObject 發(fā)布者
* @param aUserInfo 額外信息
*/
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(id)anObject;
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
注冊通知監(jiān)聽器
/**
* @param observer 監(jiān)聽器「誰要監(jiān)聽這個通知」
* @param aSelector 收到通知后,回調監(jiān)聽器的方法,并把通知對像當參數傳入
* @param aName 通知名稱,若為nil,則全部監(jiān)聽器都可以收到這個通知
* @param anObject 通知發(fā)布者,若 aName 和 anObject 都為 nil則,監(jiān)聽器都收到所有的通知
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
/**
* @param name 通知名稱
* @param obj 通知發(fā)布者
* @param queue 決定block在那個操作隊列中執(zhí)行,若是 nil則在當前隊列中執(zhí)行
* @param block 收到對應通知時回調這個 block
*
* @return 返回注冊好的監(jiān)聽器
*/
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
取消通知監(jiān)聽器
- 通知中心不會保留(retain)監(jiān)聽器對象,注冊過的對象,必須在釋放前取消注冊。
- 否則,當相應的通知再次出現時,通知中心仍然會向該監(jiān)聽器發(fā)送消息。因為相應的監(jiān)聽器對象已經被釋放了,所以可能會導致應用崩潰
// 通知中心提供了相應的方法來取消注冊監(jiān)聽器
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
// 一般在監(jiān)聽器銷毀之前取消注冊(如在監(jiān)聽器中加入下列代碼):
- (void)dealloc {
// [super dealloc]; 非ARC中需要調用此句
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
UIDevice 通知
- UIDevice類提供了一個單例對象,它代表著設備
- 通過它可以獲得一些設備相關的信息
比如電池電量值(batteryLevel)、電池狀態(tài)(batteryState)、設備的類型(model,比如iPod、iPhone等)、設備的系統(tǒng)(systemVersion) - 通過
[UIDevice currentDevice]可以獲取這個單例對象
10. KVC、KVO
KVC:Key Value Coding,常見作用:給模型賦值「模型必須與字典類的 屬性名 相同」
KVC 原理:
- 遍歷字典的所有的 Key
- 去所在類查找 有無 與Key值名稱對應的
- set方法:,若找到則,賦值
- 沒找到,則查找 _name屬性,若找到 賦值
- 沒找到,則查找 name屬性,若找到 賦值
- 還沒找到,報錯
+ (instancetype)dealWithDic:(NSDictionary *)dict{
Deal *deal = [[self alloc]init];
[deal setValuesForKeysWithDictionary:dict];
return deal;
}
KVO:Key Value Observing,常見作用:監(jiān)聽模型屬性值的改變
原理:
- 判斷有沒有調用一個對象的 set 方法,如果沒有調用 set 方法,則不能監(jiān)聽這屬性值的改變
- 底層實現
- 動態(tài)創(chuàng)建「運行時創(chuàng)建」原來 類A 的子類
NSKVONotifying_A的類名,用來做 KVO - 修改當前對象的
isa 指針-> NSKVONotifying_A的類名 - 只要調用對象的 set 方法,就會調用
NSKVONotifying_類名的 set 方法 - 重寫
NSKVONotifying_類名的 set 方法- 調用父類
- 通知觀察者屬性改變
- 動態(tài)創(chuàng)建「運行時創(chuàng)建」原來 類A 的子類
缺點:由于時刻監(jiān)聽,耗費系統(tǒng)性能,能少用就少用
// 對象A 監(jiān)聽 對象B 的 屬性名C是否改變「只要對象B 的屬性C改變,則會通知對象A這件事」
// NSObjectA: 觀察者「誰想監(jiān)聽」
[NSObjectB addObserver:NSObjectA forKeyPath:@"屬性名C" options:(NSKeyValueObservingOptions) context:(nullable void *)];
// KVO監(jiān)聽方法:對象B 的屬性C改變后,會調用此方法「是NSObject 的方法」
// 監(jiān)聽 object的 keyPath屬性發(fā)生了改變「change 是根據上面 options的值產生的值改變的記錄」
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"監(jiān)聽到 %@對像的 %@屬性發(fā)生了 %@的改變",object,keyPath,change);
}
// 監(jiān)聽對象A銷毀后要移除,否則再次傳值給監(jiān)聽對象A的時候,會因找不到已經銷毀的對象A而崩潰「是NSObject 的方法」
- (void)dealloc{
[NSObjectB removeObserver:NSObjectA forKeyPath:@"屬性名C"];
}