代理
// 制定協議
@protocol LLBabyDelegate <NSObject>
//這里可以編寫代理方法(該案例中用不到所以就不寫了)
@end
@interface Baby : NSObject /** * baby的代理屬性(這里用的是weak修飾,正確的做法) */
@property(nonatomic, weak) id<LLBabyDelegate>delegate;
@end
如果strong的話,在程序運行的時候會造成循環引用(意思就是reatainCount不為0,只要有實線引用,計數器就+1),對象都不會的銷毀,所以會調用嬰兒類和保姆類不會調用delloc方法,從而造成了內存泄露的問題,weak和assign都不會增加引用計數,區別是修飾的對象在釋放時所做的操作不同,weak是會把對象置為nil,assign則不會,assign一般適用與基本數據類型。
通知
1、不傳遞參數, 最常用的一種
// 發送通知
-(void)btn1Click{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti1" object:nil];
}
//監聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti1) name:@"noti1" object:nil];
//調用方法
-(void)noti1{
NSLog(@"接收 不帶參數的消息");
}
//移除
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"noti1" object:nil];
2、使用object 傳遞消息//發通知
-(void)btn2Click:(UIButton *)btn{
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti2" object:[NSString stringWithFormat:@"%@",btn.titleLabel.text]];
}
//監聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti2:) name:@"noti2" object:nil];
//調用方法
-(void)noti2:(NSNotification *)noti{
//使用object處理消息
NSString *info = [noti object];
NSLog(@"接收 object傳遞的消息:%@",info);
}
//移除
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"noti1" object:nil];
3、使用userInfo 傳遞消息//發通知
-(void)btn3Click{
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"userInfo消息" forKey:@"param"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti3" object:nil userInfo:dic];
}
//監聽
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti3:) name:@"noti3" object:nil];
//調用方法
-(void)noti3:(NSNotification *)noti{
//使用userInfo處理消息
NSDictionary *dic = [noti userInfo];
NSString *info = [dic objectForKey:@"param"];
NSLog(@"接收 userInfo傳遞的消息:%@",info);
}
最后,記得在發送通知消息的頁面,在dealloc方法里面移除觀察者。
-(void)dealloc{
//移除觀察者,Observer不能為nil
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
KVO
最常見的KVO運用是監聽scrollView的contentOffset屬性,來完成用戶滾動時動態改變某些控件的屬性實現效果,包括漸變導航欄、下拉刷新控件等效果。
//observer觀察者 (觀察self.view對象的屬性的變化)
//KeyPath: 被觀察屬性的名稱
//options: 觀察屬性的新值,舊值等的一些配置(枚舉值)
//context:上下文 可以為kvo的回調方法傳值
//這兒的self.view是被觀察者 //注冊觀察者(可以是多個)
/* options: 有4個值,分別是:
NSKeyValueObservingOptionOld 把更改之前的值提供給處理方法
NSKeyValueObservingOptionNew 把更改之后的值提供給處理方法
NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。 NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后。 */
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
pragma mark - kvo的回調方法(系統提供的回調方法)
//keyPath:屬性名稱 //object:被觀察的對象
//change:變化前后的值都存儲在change字典中
//context:注冊觀察者的時候,context傳遞過來的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
id oldName = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"oldName----------%@",oldName);
id newName = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"newName-----------%@",newName);
}
//當界面要消失的時候,移除kvo //
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name"];
self.person = nil;
}
block
捕獲外界變量
block還可以訪問外界的局部變量,在我的從UIView動畫說起中有這么一段代碼,其中block內部使用到了外部的局部變量:
CGPoint center = cell.center;
CGPoint startCenter = center;
startCenter.y += LXD_SCREEN_HEIGHT;
cell.center = startCenter;
[UIView animateWithDuration: 0.5 delay: 0.35 * indexPath.item usingSpringWithDamping: 0.6 initialSpringVelocity: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
cell.center = center;
} completion: ^(BOOL finished) {
NSLog("animation %@ finished", finished? @"is", @"isn't");
}];
而block會捕獲代碼外的局部變量,當然這只局限于只讀操作。對于希望在block中修改的外界局部對象,我們可以給這些變量加上__block關鍵字修飾,這樣就能在block中修改這些變量。
循環引用
開頭說過,block在iOS開發中被視作是對象,因此其生命周期會一直等到持有者的生命周期結束了才會結束。另一方面,由于block捕獲變量的機制,使得持有block的對象也可能被block持有,從而形成循環引用,導致兩者都不能被釋放:
@implementation LXDObject{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self); //引發循環引用
};
}
@end
系統提供給我們__weak的關鍵字用來修飾對象變量,聲明這是一個弱引用的對象,從而解決了循環引用的問題:
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf); //弱指針引用,不會造成循環引用
};
\在swift中提供了包括map、filter、reduce等十分簡潔優秀的高階函數供我們對數組數據進行操作,同樣情況下,遍歷一個數組并求和在使用oc(不使用kvc)和swift的環境下的代碼是這樣的:
pragma mark - OC code
NSArray numbers = @[@10, @15, @99, @66, @25];
NSInteger totalNumber = 0;
for (NSNumber number in numbers) {
totalNumber += number.integerValue;
}
pragma mark - swift code
let numbers = [10, 15, 99, 66, 25];
let totalNumber = numbers.reduce(0, { $0+$1 })
無論是代碼量還是簡潔性,此時的oc都比不上swift。那么接下來就要通過神奇的block來為oc添加這些高階函數的實現。為此我們需要新建一個NSArray的分類擴展,命名為NSArray+LXDExtension
import /// 數組元素轉換
typedef id(^LXDItemMap)(id item);
typedef NSArray (^LXDArrayMap)(LXDItemMap itemMap);
/// 數組元素篩選
typedef BOOL(^LXDItemFilter)(id item);
typedef NSArray (^LXDArrayFilter)(LXDItemFilter itemFilter);
/
- 擴展數組高級方法仿swift調用
*/
@interface NSArray (LXDExtension)
@property (nonatomic, copy, readonly) LXDArrayMap map;
@property (nonatomic, copy, readonly) LXDArrayFilter filter;
@end
前面說了為了實現鏈式編程,函數調用的前提是具有返回對象。因此我使用了typedef聲明了幾個不同類型的block。雖然本質上LXDArrayMap和LXDArrayFilter兩個block是一樣的,但是為了區分它們的功能,還是建議這么做。其實現文件如下:
typedef void(^LXDEnumerateHandler)(id item);
@implementation NSArray (LXDTopMethod)
- (LXDArrayMap)map
{
LXDArrayMap map = (LXDArrayMap)^(LXDItemMap itemMap) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
[items addObject: itemMap(item)];
}
return items;
};
return map;
} - (LXDArrayFilter)filter
{
LXDArrayFilter filter = (LXDArrayFilter)^(LXDItemFilter itemFilter) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
if (itemFilter(item)) { [items addObject: item]; }
}
return items;
};
return filter;
} - (void)setFilter:(LXDArrayFilter)filter {}
- (void)setMap:(LXDArrayMap)map {}
@end
我們通過重寫setter方法保證block不會被外部修改實現,并且在getter中遍歷數組的元素并調用傳入的執行代碼來實現map和filter等功能。對于這兩個功能的實現也很簡單,下面舉出兩個調用高階函數的例子:
pragma mark - 篩選數組中大于20的數值并轉換成字符串
NSArray * numbers = @[@10, @15, @99, @66, @25, @28.1, @7.5, @11.2, @66.2];
NSArray * result = numbers.filter((LXDArrayFilter)^(NSNumber * item) {
return item.doubleValue > 20
}).map((LXDArrayMap)^(NSNumber * item) {
return [NSString stringWithFormat: @"string %g", item.doubleValue];
});
pragma mark - 將數組中的字典轉換成對應的數據模型
NSArray * jsons = @[@{ ... }, @{ ... }, @{ ... }];
NSArray * models = jsons.map((LXDArrayMap)^(id item) {
return [[LXDModel alloc] initWithJSON: item];
})
由于語法上的限制,雖然這樣的調用跟swift原生的調用對比起來還是復雜了,但通過block讓oc實現了函數鏈式調用的代碼看起來也清爽了很多。