Open ShannonChenCHN opened 7 years ago
设置 navigation bar 的外观样式
自定义 navigation bar
UINavigationControllerDelegate
awakeFromNib
Typically, you implement awakeFromNib for objects that require additional set up that cannot be done at design time. For example, you might use this method to customize the default configuration of any controls to match user preferences or the values in other controls. You might also use it to restore individual controls to some previous state of your application.
1.根视图被 alloc ,并且 UIView 的初始化方法会帮你初始化好各种子视图,并布局好视图层级( add subviews )。 2.在 [super initWithCoder:] 结束后,你所有在 XIB 里面拖入的子视图都被初始化完毕,但此时,Outlet 等属性还未被设置(子视图初始化完毕,但自己还没呢!)。 3.所有 NIB 中的视图都跑完 [[Class alloc] initWithCoder:] 流程后,按一定顺序调 awakeFromNib (这里看起来像是根据初始化开始的顺序调用)。
// add child view
UIViewController* controller = [self.storyboard instantiateViewControllerWithIdentifier:@"test"];
[self addChildViewController:controller];
controller.view.frame = CGRectMake(0, 44, 320, 320);
[self.view addSubview:controller.view];
[controller didMoveToParentViewController:self];
// remove child view
UIViewController *vc = [self.childViewControllers lastObject];
[vc willMoveToParentViewController:nil];
[vc.view removeFromSuperview];
[vc removeFromParentViewController];
默认情况下,当添加 Child View Controller 到 Container View Controller 中时,Container 会自动将 viewWillAppear:
等视图状态管理事件传递给 Child。
如果需要自己管理 Child 的视图状态,我们可以重写 Container 的 shouldAutomaticallyForwardAppearanceMethods
方法,并且返回 NO,然后再在恰当的时机手动调用 beginAppearanceTransition:animated:
或者 endAppearanceTransition
方法来管理Child 的视图状态。
默认情况下,当添加 Child View Controller 到 Container View Controller 中时,Container 会自动将自己的 navigation controller 传递给 Child。
我们可以自己定义 Child View Controller (view)之间的切换效果。
传统的添加观察者的方式是使用–addObserver:selector:name:object:
,一个对象(通常是 self)把自己添加进去,当某个通知发出时,执行自己特定的 selector。
现代的基于 Block 的用于添加通知观察者的 API 是 –addObserverForName:object:queue:usingBlock:
。与前面提到的把一个已有的对象注册成观察者不同,这个方法创建一个匿名对象作为观察者,当收到对应的通知时,它在指定的队列(如果队列参数为 nil 的话就在调用者的线程)里执行一个 block。另外一点和基于 @selector 的方法不同的是,这个方法会返回构造出的观察者对象,在移除观察者的时候会用到它。
KVO != NSNotificationCenter。Key-Value Observing 是在 keypaths 上添加观察者,监听的是属性,而 NSNotificationCenter 是在通知上添加观察者,是以发广播的形式来实现的,是中心化的模式。
不再需要通知时,需要移除观察者,而且添加和移除要配对出现。对于第一种注册方式,移除的是显示注册的 observer,对于第二种注册方式,需要拿到 –addObserverForName:object:queue:usingBlock:
方法返回的隐式 observer,然后移除掉。
–removeObserver:
适合于在类的dealloc方法中调用,这样可以确保将对象从通知中心中清除;而在viewWillDisappear:
这样的方法中,则适合于使用-removeObserver:name:object:
方法,以避免不知情的情况下移除了不应该移除的通知观察者。不过 iOS 9 开始就不需要了,但是用 block 的api还是需要的。
为什么需要在 dealloc 中移除注册的观察者?因为通知中心维护的是观察者 unsafe_unretained 引用而不是weak引用,所以当观察者从堆上移除后,通知中心维护的引用没有变为 nil,就变成了垂悬指针,一旦给这个引用发送消息,就会奔溃。为什么苹果在这里的实现是使用 unsafe_unretained 而不是 weak 呢?在《斯坦福大学公开课:iOS 7应用开发》的第5集的第57分50秒中得到了解答:之所以使用unsafe_unretained,而不使用 weak,是为了兼容老版本的系统, 对 weak(对象销毁后,引用自动被置为 nil)的支持是 iOS 6 以后才有的。
使用 –addObserverForName:object:queue:usingBlock:
时,需要注意避免循环引用的出现,见下面的示例代码。
...
@property (nonatomic, weak) id <NSObject> observer;
...
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
__weak typeof(self) weakSelf = self;
self.observer = [center addObserverForName:@"TestNotification"
object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
// 为什么这个 block 中必须要用 weakSelf ?
// observer 会引用这个 block,这个 block 会 copy self,对 self 强引用,而 observer 要等到被 remove 掉时,才会被移除,所以如果等到在 dealloc 时才 remove observer 的话,就会出现循环引用。
NSLog(@"%@ did receive notification %@", NSStringFromClass(weakSelf.class), note.name);
}];
7.1 根据官方文档,在多线程应用中,Notification在哪个线程中post,就在哪个线程中被转发,但不一定是在注册观察者的那个线程中。
7.2 如果我们的Notification是在主线程中post的,如何能在后台线程中对这个Notification进行处理呢?或者换个提法,如果我们希望一个Notification的post线程与转发线程不是同一个线程,应该怎么办呢? 官方文档Delivering Notifications To Particular Threads中给出的一个建议是:
In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
也就是先在默认的线程中捕捉到这些通知,然后再将它们重定向到合适的线程。
大概的实现思路是: 首先定义一个通知队列(注意,不是NSNotificationQueue对象,而是一个数组),让这个队列去维护那些我们需要重定向的Notification,然后还要定义一个发送信号给处理通知的线程的 Mach Port 对象,以及保证队列线程安全的锁和记录目标线程的 NSThread 对象。
我们仍然是像平常一样去注册一个通知的观察者,当Notification来了时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,则将这个Notification存储到我们的队列中,并通过 Mach Port 发送一个信号(signal)到期望的线程(其中会有 Runloop 监听到信号)中,来告诉这个线程需要处理一个Notification。指定的线程在收到信号后,将Notification从队列中移除,并进行处理(具体的实现代码见官方文档)。
TODO:其实,我的想法是直接在接收到 notification 时,切换到目标线程处理不就可以了吗?为什么还要这么麻烦呢?目前还没有找到原因。
另外,YYKit 中的 NSNotificationCenter+YYAdd.h 提供了直接在主线程发通知的便捷方法,也就是保证通知的收发都在主线程,这种方式可能是实际开发中遇到的更多的情况。
7.3 线程安全
苹果之所以采取通知中心在同一个线程中post和转发同一消息这一策略,应该是出于线程安全的角度来考量的。官方文档告诉我们,NSNotificationCenter是一个线程安全类,我们可以在多线程环境下使用同一个NSNotificationCenter对象而不需要加锁。我们可以在任何线程中添加/删除通知的观察者,也可以在任何线程中post一个通知。
NSNotificationCenter是线程安全的,但并不意味着在多线程环境中不需要关注线程安全问题。不恰当的使用仍然会引发线程问题。
每次添加一个观察者时,会将这个观察者的引用添加到它的分发表中。 每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。
另外,通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。
iOS 系统的推送(APNS,即 Apple Push Notification Service)依托一个或几个系统常驻进程运作,是全局的(接管所有应用的消息推送),所以可看作是独立于应用之外,而且是设备和苹果服务器之间的通讯,而非应用的提供商服务器。
所以,iOS 的推送,可以不严谨的理解为:
首先,APNs会对用户进行物理连接认证,和设备令牌认证(简言之就是苹果的服务器检查设备里的证书以确定其为苹果设备); 然后,将服务器的信息接收并且保存在APNs当中,APNs从其中注册的列表中查找该IOS设备(设备可以为iPhone、iPad、iPod Touch,版本是iOS3.0及以上)并将信息发送到该设备; 最后,设备接收到数据信息给相应的APP,并按照设定弹出Push信息。
其实,APNs 在分别与设备和 provider 建立连接时都会进行认证:
而且这两个认证环节都有两种级别的认证方式:
这里以 APNs-to-device connection trust 为例,简单说一下两种不同级别的认证: (1)物理认证 iPhone在开启Push的时候,会连接APNS建立一条TLS加密链接。每一台正常的iPhone都有一个独有的设备证书,而APNS也有一个服务器证书。两者建立的时候,会验证彼此的证书有效性。
TLS链接一旦建立,在没有数据的情况下,只需要每隔15分钟进行一次保活的握手,因此几乎不占流量。而一旦因为意外原因导致链接中断,iPhone会不断重新尝试建立TLS链接,直到成功。
(2)设备令牌认证 APNS 判断 Push 推送消息该发给哪台 iPhone 是由 deviceToken 决定的。
设备令牌是怎么生成的呢?是每次建立TLS连接时,APNS通过前一层次(TLS层)里我们提到的每台正常的iPhone唯一的设备证书(unique device certificate),并用令牌密钥(token key)加密生成的。
在令牌生成了之后,APNS会把设备令牌(device token)返回给iPhone,而对应的Push应用程序(如BeejiveIM),则把返回来的设备令牌(device token)直接发送给Provider(如BeejiveIM服务器)。这样,当Provider有Push消息要发送时,就会把对应帐号的设备令牌(device token)和消息一起发送给APNS,而APNS再依据设备令牌(device token),找到相应TLS链接的iPhone,并发送相应的Push消息。
(1)App 启动时调用 -registerForRemoteNotifications
方法注册通知;
(2)注册成功后会在 -application:didRegisterForRemoteNotificationsWithDeviceToken:
方法中收到回调,拿到 APNS 返回的 token;
(3)把 token 传给自家服务器;
(4)当需要发送推送通知时,自家服务器就会把推送内容 payload 和 token 一起发给 APNS 服务器;
(5)APNS 就会根据 token 将 payload 发给对应的 App。
注:APNS 判断 Push 推送消息该发给哪台 iPhone 是由 deviceToken 决定的。这里的 deviceToken 是唯一的,只在以下三种情况下会发生改变:
Xcode background mode | UIBackgroundModes value | Description |
---|---|---|
Audio and AirPlay | audio | The app plays audible content to the user or records audio while in the background. (This content includes streaming audio or video content using AirPlay.)The user must grant permission for apps to use the microphone prior to the first use; for more information, see Supporting User Privacy. |
Location updates | location | The app keeps users informed of their location, even while it is running in the background. |
Voice over IP | voip | The app provides the ability for the user to make phone calls using an Internet connection. |
Newsstand downloads | newsstand-content | The app is a Newsstand app that downloads and processes magazine or newspaper content in the background. |
External accessory communication | external-accessory | The app works with a hardware accessory that needs to deliver updates on a regular schedule through the External Accessory framework. |
Uses Bluetooth LE accessories | bluetooth-central | The app works with a Bluetooth accessory that needs to deliver updates on a regular schedule through the Core Bluetooth framework. |
Acts as a Bluetooth LE accessory | bluetooth-peripheral | The app supports Bluetooth communication in peripheral mode through the Core Bluetooth framework.Using this mode requires user authorization; for more information, see Supporting User Privacy. |
Background fetch | fetch | The app regularly downloads and processes small amounts of content from the network. |
Remote notifications | remote-notification | The app wants to start downloading content when a push notification arrives. Use this notification to minimize the delay in showing content related to the push notification. |
//控制文本所在的的位置,左右缩 10
- (CGRect)textRectForBounds:(CGRect)bounds {
return CGRectInset( bounds , 10 , 0 );
}
//控制编辑文本时所在的位置,左右缩 10
- (CGRect)editingRectForBounds:(CGRect)bounds {
return CGRectInset( bounds , 10 , 0 );
}
参考:
保存和恢复状态(Preserving Your App's UI Across Launches)
参考: