Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cocoa Touch #51

Open
ShannonChenCHN opened this issue Apr 29, 2017 · 14 comments
Open

Cocoa Touch #51

ShannonChenCHN opened this issue Apr 29, 2017 · 14 comments

Comments

@ShannonChenCHN
Copy link
Owner

ShannonChenCHN commented Apr 29, 2017


参考:

@ShannonChenCHN ShannonChenCHN changed the title 【专题】Cocoa Touch Cocoa Touch Jun 30, 2017
@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Jul 30, 2017

UINavigationController

  1. 设置 navigation bar 的外观样式

  2. 自定义 navigation bar

  3. UINavigationControllerDelegate

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Oct 21, 2017

@ShannonChenCHN
Copy link
Owner Author

View Controller 的生命周期

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Dec 19, 2017

UIView 的生命周期

1. awakeFromNib

  • 用来干什么的?
    运行时微调 view

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.

  • 从 Nib 文件中加载 view 的流程

1.根视图被 alloc ,并且 UIView 的初始化方法会帮你初始化好各种子视图,并布局好视图层级( add subviews )。
2.在 [super initWithCoder:] 结束后,你所有在 XIB 里面拖入的子视图都被初始化完毕,但此时,Outlet 等属性还未被设置(子视图初始化完毕,但自己还没呢!)。
3.所有 NIB 中的视图都跑完 [[Class alloc] initWithCoder:] 流程后,按一定顺序调 awakeFromNib (这里看起来像是根据初始化开始的顺序调用)。

参考

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Jan 5, 2018

Child View Controller / Custom Container View Controller

1. 什么是 Child View Controller

2. 如何添加/移除一个 Child View Controller

// 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];

3. Child View Controller 的视图状态管理

默认情况下,当添加 Child View Controller 到 Container View Controller 中时,Container 会自动将 viewWillAppear: 等视图状态管理事件传递给 Child。

如果需要自己管理 Child 的视图状态,我们可以重写 Container 的 shouldAutomaticallyForwardAppearanceMethods 方法,并且返回 NO,然后再在恰当的时机手动调用 beginAppearanceTransition:animated: 或者 endAppearanceTransition 方法来管理Child 的视图状态。

4. Child View Controller 与 UINavigationController 的关系

默认情况下,当添加 Child View Controller 到 Container View Controller 中时,Container 会自动将自己的 navigation controller 传递给 Child。

5. Child View Controller 之间的切换

我们可以自己定义 Child View Controller (view)之间的切换效果。

6. 一些建议

  • Container 应该只对 Child 的 root view 进行访问
  • Child 应该尽量不要直接控制 Container,建议采用 delegation 方式来进行通信

7. Container 可以适当把一些控制权交给 Child

  • status bar style
  • preferred content size

参考:

@ShannonChenCHN
Copy link
Owner Author

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Feb 1, 2018

NSNotification & NSNotification​Center

相关代码

两种注册通知观察者的方式:

  1. 传统的添加观察者的方式是使用 –addObserver:selector:name:object:,一个对象(通常是 self)把自己添加进去,当某个通知发出时,执行自己特定的 selector。

  2. 现代的基于 Block 的用于添加通知观察者的 API 是 –addObserverForName:object:queue:usingBlock:。与前面提到的把一个已有的对象注册成观察者不同,这个方法创建一个匿名对象作为观察者,当收到对应的通知时,它在指定的队列(如果队列参数为 nil 的话就在调用者的线程)里执行一个 block。另外一点和基于 @selector 的方法不同的是,这个方法会返回构造出的观察者对象,在移除观察者的时候会用到它。

注意点

  1. KVO != NSNotificationCenter。Key-Value Observing 是在 keypaths 上添加观察者,监听的是属性,而 NSNotificationCenter 是在通知上添加观察者,是以发广播的形式来实现的,是中心化的模式。

  2. 不再需要通知时,需要移除观察者,而且添加和移除要配对出现。对于第一种注册方式,移除的是显示注册的 observer,对于第二种注册方式,需要拿到 –addObserverForName:object:queue:usingBlock: 方法返回的隐式 observer,然后移除掉。

  3. –removeObserver:适合于在类的dealloc方法中调用,这样可以确保将对象从通知中心中清除;而在viewWillDisappear:这样的方法中,则适合于使用-removeObserver:name:object:方法,以避免不知情的情况下移除了不应该移除的通知观察者。不过 iOS 9 开始就不需要了,但是用 block 的api还是需要的。

  4. 为什么需要在 dealloc 中移除注册的观察者?因为通知中心维护的是观察者 unsafe_unretained 引用而不是weak引用,所以当观察者从堆上移除后,通知中心维护的引用没有变为 nil,就变成了垂悬指针,一旦给这个引用发送消息,就会奔溃。为什么苹果在这里的实现是使用 unsafe_unretained 而不是 weak 呢?在《斯坦福大学公开课:iOS 7应用开发》的第5集的第57分50秒中得到了解答:之所以使用unsafe_unretained,而不使用 weak,是为了兼容老版本的系统, 对 weak(对象销毁后,引用自动被置为 nil)的支持是 iOS 6 以后才有的。

  5. 使用 –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);
    }];
    
  1. 线程安全

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是线程安全的,但并不意味着在多线程环境中不需要关注线程安全问题。不恰当的使用仍然会引发线程问题。

  1. NSNotificationCenter 的实现原理以及 NSNotificationQueue(TODO)

每次添加一个观察者时,会将这个观察者的引用添加到它的分发表中。
每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。

另外,通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。

参考:

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Mar 2, 2018

推送通知(Notification)

1. 本地推送

2. 远程推送

2.1 推送原理

iOS 系统的推送(APNS,即 Apple Push Notification Service)依托一个或几个系统常驻进程运作,是全局的(接管所有应用的消息推送),所以可看作是独立于应用之外,而且是设备和苹果服务器之间的通讯,而非应用的提供商服务器。

所以,iOS 的推送,可以不严谨的理解为:

  • 苹果服务器朝手机后台挂的一个 IM 服务程序发送的消息。(系统层有一个常驻的 TCP 长连接,一直保持的长连接,即使手机休眠的时候也在保持的长连接。)
  • 然后,系统根据该 IM 消息识别告诉哪个 Apps 具体发生了什么事。
  • 最后,系统分别通知这些 Apps 。

首先,APNs会对用户进行物理连接认证,和设备令牌认证(简言之就是苹果的服务器检查设备里的证书以确定其为苹果设备);
然后,将服务器的信息接收并且保存在APNs当中,APNs从其中注册的列表中查找该IOS设备(设备可以为iPhone、iPad、iPod Touch,版本是iOS3.0及以上)并将信息发送到该设备;
最后,设备接收到数据信息给相应的APP,并按照设定弹出Push信息。

image

其实,APNs 在分别与设备和 provider 建立连接时都会进行认证:

  • Provider-to-APNs connection trust
  • APNs-to-device connection trust

而且这两个认证环节都有两种级别的认证方式:

  • 物理认证(connection trust)
  • 设备令牌认证(device token trust)

这里以 APNs-to-device connection trust 为例,简单说一下两种不同级别的认证:
(1)物理认证
iPhone在开启Push的时候,会连接APNS建立一条TLS加密链接。每一台正常的iPhone都有一个独有的设备证书,而APNS也有一个服务器证书。两者建立的时候,会验证彼此的证书有效性。

TLS链接一旦建立,在没有数据的情况下,只需要每隔15分钟进行一次保活的握手,因此几乎不占流量。而一旦因为意外原因导致链接中断,iPhone会不断重新尝试建立TLS链接,直到成功。

image

(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消息。

image

2.2 推送流程:

(1)App 启动时调用 -registerForRemoteNotifications 方法注册通知;
(2)注册成功后会在 -application:didRegisterForRemoteNotificationsWithDeviceToken: 方法中收到回调,拿到 APNS 返回的 token;
(3)把 token 传给自家服务器;
(4)当需要发送推送通知时,自家服务器就会把推送内容 payload 和 token 一起发给 APNS 服务器;
(5)APNS 就会根据 token 将 payload 发给对应的 App。

注:APNS 判断 Push 推送消息该发给哪台 iPhone 是由 deviceToken 决定的。这里的 deviceToken 是唯一的,只在以下三种情况下会发生改变:

  • 同一个设备上重新安装同一款应用
  • 同一个应用安装在不同的设备上
  • 设备重新安装了系统,同一个应用对应的deviceToken也会改变

image

image

3. 静默推送(Background Modes - Remote notification):应用在后台运行的情况下,收到推送时可以在后台提前下载数据

参考

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Mar 2, 2018

后台模式

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.

参考

@ShannonChenCHN
Copy link
Owner Author

UITextField

1. 如何控制 UITextField 中文本的缩进?

//控制文本所在的的位置,左右缩 10  
- (CGRect)textRectForBounds:(CGRect)bounds {  
    return CGRectInset( bounds , 10 , 0 );  
}  
  
//控制编辑文本时所在的位置,左右缩 10  
- (CGRect)editingRectForBounds:(CGRect)bounds {  
    return CGRectInset( bounds , 10 , 0 );  
}  

参考:

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Mar 23, 2018

如何设计一个自定义 view?

  • 确定要实现的功能和交互逻辑
  • 选择合适的父类
  • 初始化方法
    • 指定初始化方法
    • 初始化默认值
  • API 设计
    • 易用、简洁
    • 可定制
  • 事件、数据传递
    • delegate
    • block
    • notification/KVO
    • target-action
  • 交互逻辑
    • 手势
  • 渲染方式(尽量采用图片的方式来实现、如果是自己绘制的,一定要测试一下性能)
    • 使用 UIView 及其子类实现普通 UI 元素的显示
    • 使用图片实现非常规图形的显示
    • 使用 CALayer 及其子类实现非常规图形
    • 重写 drawRect: 方法使用 Core Graphics 绘制非常规图形
    • 使用 OpenGL ES 等专业图形框架来绘制
    • 使用 webView 来渲染
  • 布局方式
    • 手写 frame 布局
    • autolayout
  • 动画
    • UIView Animation
    • Core Animation(CALayer)
    • CADisplayLink
  • 测试
  • API 注释、文档
  • 鲁棒性
  • 其他
    • Accessibility
    • Localization

延伸阅读

@ShannonChenCHN
Copy link
Owner Author

理解 UIScrollView 的原理

参考

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Oct 15, 2018

@ShannonChenCHN
Copy link
Owner Author

ShannonChenCHN commented Nov 20, 2018

保存和恢复状态(Preserving Your App's UI Across Launches)

延伸阅读

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant