梦幻泡影

孔思哲的博客空间

0%

模拟器运行报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
post_install do |installer|

installer.pods_project.targets.each do |target|

target.build_configurations.each do |config|

\# 处理 M1 芯片上不支持 模拟器 运行问题:不仅仅编译活跃的架构,反之就是 i386、x86_64、arm64 等架构都编译。

config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'

config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = "arm64"

end

end

end

YYText YYTextAsyncLayer UIGraphicsBeginImageContext Deprecated #984

https://github.com/ibireme/YYText/issues/984

iOS 18适配问题记录(Xcode16正式版)

问题1:ADClient编译报错问题

报错信息

1
2
3
4
5
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_ADClient", referenced from:
in ViewController.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

相关代码(demo)

原因

苹果对AdClicent API加了一个标识
ADCLIENT_DEPRECATED_IOS_90_145_OBSOLETED_180
表示:iOS7.1-iOS14.5可用,iOS18彻底废弃,会在iOS18系统上编译失败。

解决办法

使用AdService库的AAAttribution替代,注意iOS14.3才可以使用。

代码

1
2
3
4
5
6
7
if (@available(ios 14.3, *)) {
NSError *error;
NSString *token = [AAAttribution attributionTokenWithError:&error];
if (token != nil) {
...
}
}

参考

https://developer.apple.com/forums/thread/759156
https://developer.apple.com/documentation/iad?language=objc

问题2:Xcode16(正式版)运行时,YYCache导致crash

报错信息

原因

在 iOS18 中,需要提前对 sqlite3_stmt 执行 sqlite3_finalize。

解决办法

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- (BOOL)_dbClose {
if (!_db) return YES;

int result = 0;
BOOL retry = NO;
BOOL stmtFinalized = NO;

if (@available(iOS 18, *)) {
if (_dbStmtCache) {
CFIndex size = CFDictionaryGetCount(_dbStmtCache);
CFTypeRef *valuesRef = (CFTypeRef *)malloc(size * sizeof(CFTypeRef));
CFDictionaryGetKeysAndValues(_dbStmtCache, NULL, (const void **)valuesRef);
const sqlite3_stmt **stmts = (const sqlite3_stmt **)valuesRef;
for (CFIndex i = 0; i < size; i ++) {
sqlite3_stmt *stmt = stmts[i];
sqlite3_finalize(stmt);
}
free(valuesRef);
CFRelease(_dbStmtCache);
}
} else {
if (_dbStmtCache) CFRelease(_dbStmtCache);
_dbStmtCache = NULL;
}

do {
retry = NO;
result = sqlite3_close(_db);
if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
if (!stmtFinalized) {
stmtFinalized = YES;
sqlite3_stmt *stmt;
while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {
sqlite3_finalize(stmt);
retry = YES;
}
}
} else if (result != SQLITE_OK) {
if (_errorLogsEnabled) {
NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result);
}
}
} while (retry);
_db = NULL;
return YES;
}

参考

https://giters.com/ibireme/YYCache/issues/166

问题3:Xcode16正式版,addSubView crash (maskView)

报错信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
*** Assertion failure in -[TestMaskView _addSubview:positioned:relativeTo:], UIView.m:18496
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Set `maskView` (<UIView: 0x14a21ddd0; frame = (0 0; 0 0); layer = <CALayer: 0x60000314b480>>) to `nil` before adding it as a subview of <TestMaskView: 0x153e08350; frame = (0 0; 393 852); layer = <CALayer: 0x60000314b440>>'
*** First throw call stack:
(
0 CoreFoundation 0x00000001063540ec __exceptionPreprocess + 172
1 libobjc.A.dylib 0x00000001048bede8 objc_exception_throw + 72
2 Foundation 0x0000000109d21aa8 _userInfoForFileAndLine + 0
3 UIKitCore 0x0000000128c0151c -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1124
4 ADClientTest 0x0000000104609660 -[TestMaskView initWithFrame:] + 276
5 ADClientTest 0x0000000104609434 -[ViewController viewDidLoad] + 164
6 UIKitCore 0x0000000127f1e69c -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 80
7 UIKitCore 0x0000000127f23238 -[UIViewController loadViewIfRequired] + 908
8 UIKitCore 0x0000000127f234e0 -[UIViewController view] + 20
9 UIKitCore 0x00000001286c3a08 -[UIWindow addRootViewControllerViewIfPossible] + 132
10 UIKitCore 0x00000001286c343c -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 168
11 UIKitCore 0x00000001286c4288 -[UIWindow _setHidden:forced:] + 228
12 UIKitCore 0x00000001286d3344 -[UIWindow _mainQueue_makeKeyAndVisible] + 36
13 UIKitCore 0x000000012892dcd8 -[UIWindowScene _performDeferredInitialWindowUpdateForConnection] + 204
14 UIKitCore 0x0000000127af58f0 +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1164
15 UIKitCore 0x000000012868f45c -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 808
16 UIKitCore 0x000000012868f70c -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 304
17 UIKitCore 0x000000012815ec08 -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 260
18 FrontBoardServices 0x0000000113090ce4 __95-[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:]_block_invoke + 260
19 FrontBoardServices 0x00000001130910a4 -[FBSScene _callOutQueue_coalesceClientSettingsUpdates:] + 60
20 FrontBoardServices 0x0000000113090b64 -[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:] + 408
21 FrontBoardServices 0x00000001130bdd50 __93-[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:]_block_invoke.156 + 216
22 FrontBoardServices 0x000000011309d618 -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 160
23 FrontBoardServices 0x00000001130bc220 -[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:] + 388
24 libdispatch.dylib 0x000000010bdea7b8 _dispatch_client_callout + 16
25 libdispatch.dylib 0x000000010bdee3bc _dispatch_block_invoke_direct + 388
26 FrontBoardServices 0x00000001130e0b58 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 44
27 FrontBoardServices 0x00000001130e0a34 -[FBSMainRunLoopSerialQueue _targetQueue_performNextIfPossible] + 196
28 FrontBoardServices 0x00000001130e0b8c -[FBSMainRunLoopSerialQueue _performNextFromRunLoopSource] + 24
29 CoreFoundation 0x00000001062b8324 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
30 CoreFoundation 0x00000001062b826c __CFRunLoopDoSource0 + 172
31 CoreFoundation 0x00000001062b7a2c __CFRunLoopDoSources0 + 324
32 CoreFoundation 0x00000001062b20b0 __CFRunLoopRun + 788
33 CoreFoundation 0x00000001062b1960 CFRunLoopRunSpecific + 536
34 GraphicsServices 0x0000000117edfb10 GSEventRunModal + 160
35 UIKitCore 0x000000012868db40 -[UIApplication _run] + 796
36 UIKitCore 0x0000000128691d38 UIApplicationMain + 124
37 ADClientTest 0x0000000104609064 main + 140
38 dyld 0x00000001047cd410 start_sim + 20
39 ??? 0x000000010490a274 0x0 + 4371554932
)
libc++abi: terminating due to uncaught exception of type NSException

原因

iOS 18 对 UIView的maskView 增加了断言,导致如果业务代码里有同名属性可能导致触发该断言。

经测试发现:
1.自定义UIView子视图,存在同名属性maskView,会崩溃
2.自定义cell,添加到cell视图上会崩溃,添加到contentView上,则不会崩溃
3.控制器里的maskView视图属性,添加到控制器view,不会崩溃

解决办法

修改自定义视图,将自定义子组件名为maskView的视图进行重命名。

参考

https://github.com/Tencent/QMUI_iOS/issues/1557

将一个远程分支的部分修改提交到另一个远程分支

将一个远程分支的部分修改提交到另一个远程分支,可以使用 git cherry-pick 命令。这个命令可以选择特定的提交(commit)从一个分支应用到另一个分支。

  1. 切换到目标本地分支
    首先,确保您在您的本地机器上切换到了目标分支(即您想要应用修改的分支)。例如,想将修改应用到 master 分支,应该先切换到该分支:
1
git checkout master
  1. 确保本地分支是最新的:
1
git pull origin master
  1. 找到特定的提交
    在另一个分支上,找出想要应用的特定提交。可以使用 git log 命令查看提交历史:
1
git log origin/feature-branch
  1. 找到您想要的提交,并复制它的提交哈希(commit hash)。
  2. 使用 cherry-pick 应用修改
    使用 git cherry-pick 命令将该提交应用到当前的分支上:
1
git cherry-pick <commit-hash>
  1. 如果想应用多个连续的提交,可以使用提交范围:
1
git cherry-pick <start-commit-hash>^..<end-commit-hash>
  1. 解决可能出现的冲突
    如果 cherry-pick 过程中出现冲突,需要解决这些冲突。完成后,继续 cherry-pick 过程:
1
2
git add .
git cherry-pick --continue
  1. 或者,如果决定不进行 cherry-pick,可以取消:
1
git cherry-pick --abort
  1. 推送到远程仓库
    一旦完成,将您的更改推送到远程仓库:
1
git push origin master

为什么说基于TCP的移动端IM仍然需要心跳保活?

(转自 http://www.52im.net/thread-281-1-1.html)

前言

很多人认为,TCP协议自身先天就有KeepAlive机制,为何基于它的通讯链接,仍然需要在应用层实现额外的心跳保活?本文将从移动端IM实践的角度告诉你,即使使用的是TCP协议,应用层的心跳保活仍旧必不可少。

在使用 TCP 长连接的 IM 服务设计中,往往都会涉及到心跳。心跳一般是指某端(绝大多数情况下是客户端)每隔一定时间向对端发送自定义指令,以判断双方是否存活,因其按照一定间隔发送,类似于心跳,故被称为心跳指令。

TCP协议不是自带KeepAlive的吗?

那么问题就随之而来了:为什么需要在应用层做心跳,难道 TCP 不是个可靠连接吗?我们不能够依赖 TCP 做断线检测吗?比如使用 TCP 的 KeepAlive 机制来实现。应用层心跳是目前的最佳实践吗?怎么样的心跳才是最佳实践。

很多做移动端IM的同行,以前确实没有仔细考虑过这些问题,潜意识里想当然的认为这仅仅只是个简单的心跳而已啊。好吧,事实并非这么简单,请继续往下看。

IM中保持有效长连接的重要性

对于客户端而言,使用 TCP 长连接来实现业务的最大驱动力在于:在当前连接可用的情况下,每一次请求都只是简单的数据发送和接受,免去了 DNS 解析,连接建立等时间,大大加快了请求的速度,同时也有利于接受服务器的实时消息。但前提是连接可用。

如果连接无法很好地保持,每次请求就会变成撞大运:运气好,通过长连接发送请求并收到反馈。运气差,当前连接已失效,请求迟迟没有收到反馈直到超时,又需要一次连接建立的过程,其效率甚至还不如 HTTP。而连接保持的前提必然是检测连接的可用性,并在连接不可用时主动放弃当前连接并建立新的连接。

基于这个前提,必须要有一种机制用于检测连接可用性。同时移动网络的特殊性也要求客户端需要在空余时间发送一定的信令,避免连接被回收。详见微信和运营商的撕B(另一篇针对微信的信令风暴技术研究文章请见:《微信对网络影响的技术试验及分析》)。

而对于服务器而言,能够及时获悉连接可用性也非常重要:一方面服务器需要及时清理无效连接以减轻负载,另一方面也是业务的需求,如游戏副本中服务器需要及时处理玩家掉线带来的问题。

TCP的KeepAlive无法替代应用层心跳保活机制的原因

上面说了保持连接的重要性,那么现在回到具体实现上。为什么我们需要使用应用层心跳来做检测,而不是直接使用 TCP 的特性呢?

我们知道 TCP 是一个基于连接的协议,其连接状态是由一个状态机进行维护,连接完毕后,双方都会处于 established 状态,这之后的状态并不会主动进行变化。这意味着如果上层不进行任何调用,一直使 TCP 连接空闲,那么这个连接虽然没有任何数据,但仍是保持连接状态,一天、一星期、甚至一个月,即使在这期间中间路由崩溃重启无数次。举个现实中经常遇到的栗子:当我们 ssh 到自己的 VPS 上,然后不小心踢掉网线,此时的网络变化并不会被 TCP 检测出,当我们重新插回网线,仍旧可以正常使用 ssh,同时此时并没有发生任何 TCP 的重连。

有人会说 TCP 不是有 KeepAlive 机制么,通过这个机制来实现不就可以了吗?但是事实上,TCP KeepAlive 的机制其实并不适用于此。Keep Alive 机制开启后,TCP 层将在定时时间到后发送相应的 KeepAlive 探针以确定连接可用性。一般时间为 7200 s(详情请参见《TCP/IP详解》中第23章),失败后重试 10 次,每次超时时间 75 s。显然默认值无法满足我们的需求,而修改过设置后就可以满足了吗?答案仍旧是否定的。

因为 TCP KeepAlive 是用于检测连接的死活,而心跳机制则附带一个额外的功能:检测通讯双方的存活状态。两者听起来似乎是一个意思,但实际上却大相径庭。

考虑一种情况,某台服务器因为某些原因导致负载超高,CPU 100%,无法响应任何业务请求,但是使用 TCP 探针则仍旧能够确定连接状态,这就是典型的连接活着但业务提供方已死的状态,对客户端而言,这时的最好选择就是断线后重新连接其他服务器,而不是一直认为当前服务器是可用状态,一直向当前服务器发送些必然会失败的请求。

从上面我们可以知道,KeepAlive 并不适用于检测双方存活的场景,这种场景还得依赖于应用层的心跳。应用层心跳有着更大的灵活性,可以控制检测时机,间隔和处理流程,甚至可以在心跳包上附带额外信息。从这个角度而言,应用层的心跳的确是最佳实践。

心跳保活机制的实现方案参考

从上面我们可以得出结论,目前而言,应用层心跳的确是检测连接有效性,双方是否存活的最佳实践,那么剩下的问题就是怎么实现。

最简单粗暴做法当然是定时心跳,如每隔 30 秒心跳一次,15 秒内没有收到心跳回包则认为当前连接已失效,断开连接并进行重连。这种做法最直接,实现也简单。唯一的问题是比较耗电和耗流量。以一个协议包 5 个字节计算,一天收发 2880 个心跳包,一个月就是 5 * 2 * 2880 * 30 = 0.8 M 的流量,如果手机上多装几个 IM 软件,每个月光心跳就好几兆流量没了,更不用说频繁的心跳带来的电量损耗。

既然频繁心跳会带来耗电和耗流量的弊端,改进的方向自然是减少心跳频率,但也不能过于影响连接检测的实时性。基于这个需求,一般可以将心跳间隔根据程序状态进行调整,当程序在后台时(这里主要考虑安卓),尽量拉长心跳间隔,5 分钟、甚至 10 分钟都可以。

而当 App 在前台时则按照原来规则操作。连接可靠性的判断也可以放宽,避免一次心跳超时就认为连接无效的情况,使用错误积累,只在心跳超时 n 次后才判定当前连接不可用。当然还有一些小 trick 比如从收到的最后一个指令包进行心跳包周期计时而不是固定时间,这样也能够一定程度减少心跳次数。

Online聊天卡顿崩溃原因和聊天数据源的优化

测试反馈的问题有一些是界面卡顿,测试的问题不易重现和追查困难。

怎么判断主线程是不是发生了卡顿?

一般来说,用户感受得到的卡顿大概有三个特征:

  1. FPS 降低
  2. CPU 占用率很高
  3. 主线程 Runloop 执行了很久

我们先思考一下,界面卡顿是由哪些原因导致的?

阅读全文 »

Online近期优化记录及还需优化点

Online近期添加的新功能

  1. 会员中心的钻石会员和VIP会员功能。
  2. 聊天中发送iCloud文件功能。
  3. 类似QQ侧滑菜单和单聊和群聊中消息列表侧边栏功能。

Online近期优化记录

  1. 聊天查看大图时黑屏问题修复。
  2. 替换navigationBar隐藏的方法。
  3. 修改界面返回时偶尔卡住的问题。
  4. 音频通话开始后显示视频通话界面才有的翻转摄像头按钮问题修改,个人视频通话接通后自己的默认视图改为小视图,对方的视图默认是大视图。
  5. 杀死程序时发送的消息添加容错处理。
  6. 保存资料后消息列表小红点消失问题修改。
  7. 朋友圈发表评论删除消息等去掉文字提示和网络加载框优化体验。
  8. 朋友圈加入文字链接点击(网址和电话号码)功能,和长按朋友圈文字复制功能。
  9. 进入后台保活策略修改(改为播放无声音乐方式),进入后台断开socket改为定时器发送socket心跳,保证用户的在线状态。
  10. 朋友圈显示字体显示优化,刷新列表显示体验优化。
  11. 加载提示框加载时屏幕不可点击体验优化,网络请求超时时间统一设置为30秒。
  12. Online官方公众号聊天页面的头像显示修改为默认app图标。
  13. 聊天中的数据源由不安全的数组改为安全数组。
  14. 图片发送的进度显示优化。

准备优化点

  1. 用户登录过后,过段时间又重新打开app时直接进入到主界面体验优化。现在的逻辑是先走登录接口,拿到后台返回的数据后直接存储显示。这样的导致的一个体验问题是,当用户网络不好时,重新打开app会显示启动页,上面显示登录接口的加载框。直到登录接口返回数据,最多30秒的时间不能进入到app。
    优化思路,把现在临时的个人数据存储改为文件数据存储,重新登录后先拿到存储的数据进行展示,再异步请求更新数据,用户重新打开app后无缝进入app主界面优化用户体验。

NAT(Network Address Translation)

NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
这种方法需要在专用网连接到因特网的路由器上安装NAT软件。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址。这样,所有使用本地地址的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。

1.NAT功能

NAT不仅能解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。
1.宽带分享:这是 NAT 主机的最大功能。
2.安全防护:NAT 之内的 PC 联机到 Internet 上面时,他所显示的 IP 是 NAT 主机的公共 IP,所以 Client 端的 PC 当然就具有一定程度的安全了,外界在进行 portscan(端口扫描) 的时候,就侦测不到源Client 端的 PC 。

2. NAT实现方式

NAT的实现方式有三种,即静态转换Static Nat、动态转换Dynamic Nat和端口多路复用OverLoad。
静态 转换是指将内部网络的私有IP地址转换为公有IP地址,IP地址对是一对一的,是一成不变的,某个私有IP地址只转换为某个公有IP地址。借助于静态转换,可以实现外部网络对内部网络中某些特定设备(如服务器)的访问。
动态转换是指将内部网络的私有IP地址转换为公用IP地址时,IP地址是不确定的,是随机的,所有被授权访问上Internet的私有IP地址可随机转换为任何指定的合法IP地址。也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。动态转换可以使用多个合法外部地址集。当ISP提供的合法IP地址略少于网络内部的计算机数量时。可以采用动态转换的方式。
端口多路复用( Port address Translation,PAT)是指改变外出数据包的源端口并进行端口转换,即端口地址转换(PAT,Port Address Translation).采用端口多路复用方式。内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而可以最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,有效避免来自internet的攻击。因此,目前网络中应用最多的就是端口多路复用方式。

初识OpenGL ES

OpenGL是用于可视化的二维和三维数据。它是一个多用途开放标准图形库,支持2D和3D数字内容创建,机械和建筑设计,虚拟原型,飞行模拟,视频游戏等应用。您可以使用OpenGL配置3D图形管道并提交数据。顶点被变换和点亮,组合成原始图像,并进行光栅化以创建2D图像。OpenGL旨在将函数调用转换为可发送到底层图形硬件的图形命令。因为这个底层硬件专门用于处理图形命令,因此OpenGL绘图通常非常快。
用于嵌入式系统的OpenGL(OpenGL ES)是OpenGL的简化版本,可以消除冗余功能,从而提供更容易学习和易于在移动图形硬件中实现的库。
OpenGL ES允许应用程序利用底层图形处理器的功能。iOS设备上的GPU可以执行复杂的2D和3D绘图,以及最终图像中每个像素的复杂阴影计算。您应该使用OpenGL ES,如果您的应用程序的设计要求要求最直接和全面的访问可能对GPU硬件

在iOS中构建OpenGL ES应用程序需要几个注意事项,其中一些是OpenGL ES编程的通用,其中一些特定于iOS。

  • 1.确定哪个版本的OpenGL ES具有适合您应用程序的功能集,并创建一个OpenGL ES上下文。
  • 2.在运行时验证设备是否支持您要使用的OpenGL ES功能。
  • 3.选择在哪里渲染您的OpenGL ES内容。
  • 4.确保您的应用程序在iOS中正常运行。
  • 5.实现您的渲染引擎。
  • 6.使用Xcode和Instruments调试您的OpenGL ES应用程序,并调整它以获得最佳性能。

选择哪些OpenGL ES版本支持

决定您的应用程序是否应支持OpenGL ES 3.0,OpenGL ES 2.0,OpenGL ES 1.1或多个版本。
OpenGL ES 3.0是iOS 7中的新功能。它增加了许多新功能,可以实现更高性能,通用GPU计算技术,以及以前只能在桌面级硬件和游戏机上使用更复杂的视觉效果。
OpenGL ES 2.0是iOS设备的基准配置文件,具有基于可编程着色器的可配置图形流水线。
OpenGL ES 1.1仅提供基本的固定功能图形管道,主要用于向后兼容的iOS中。

配置OpenGL ES上下文

EAGL是iOS实现的OpenGL ES渲染上下文

在您的应用程序可以调用任何OpenGL ES函数之前,它必须初始化一个
EAGLContext对象。EAGLContext类还提供用于OpenGL ES的内容与核心动画结合的方法。

每个上下文定位一个特定版本的OpenGL ES

一个EAGLContext对象只支持一个版本的OpenGL ES。例如,为OpenGL ES 1.1编写的代码与OpenGL ES 2.0或3.0上下文不兼容。使用核心OpenGL ES 2.0功能的代码与OpenGL ES 3.0上下文兼容,为OpenGL ES 2.0扩展设计的代码通常可以在OpenGL ES 3.0上下文中进行微小更改。许多新的OpenGL ES 3.0功能和增加的硬件功能需要OpenGL ES 3.0上下文。
您的应用程序决定时,它支持哪种OpenGL ES的版本创建和初始化的EAGLContext对象。如果设备不支持所需版本的OpenGL ES,则该initWithAPI返回nil。您的应用程序必须进行测试,以确保在使用之前成功初始化上下文。
为了支持OpenGL ES的多个版本作为应用程序中的渲染选项,您应该首先尝试初始化要定位的最新版本的渲染上下文。如果返回的对象是nil,则初始化旧版本的上下文。

1
2
3
4
5
6
7
- (EAGLContext *)createBestEAGLContext {
EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if(context == nil){
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
return context;
}

绘制OpenGL ES和GLKit

GLKit框架提供视图和视图控制器类,可以消除绘图和动画化OpenGL ES内容所需的设置和维护代码。本GLKView类管理OpenGL ES的基础设施,为您的绘制代码提供一个场所,而GLKViewController类提供了在GLKit视图的OpenGL ES内容的流畅的动画渲染循环。这些类扩展了用于绘制视图内容和管理视图呈现的标准UIKit设计模式。因此,您可以将重点放在OpenGL ES渲染代码上,使您的应用程序快速启动并运行。GLKit框架还提供了其他功能来简化OpenGL ES 2.0和3.0开发。

使用GLKit视图渲染OpenGL ES内容

点击下载 OpenGL+ES应用开发实践指南:iOS卷 源码

1.进入通讯录-添加朋友界面,输入手机号搜索用户信息。
2.搜索到调用接口发送添加好友信息。

20200616
凡凡
1.消息下滑到顶部时自动拉取上一页信息显示添加。
2.时间戳显示添加,全局配置时间戳显示时间间隔。(删除和插入消息的时间戳显示进行中)
3.选择联系人界面添加。
4.添加创建群组接口,群管理类封装创建群接口。(后台暂未返回群ID)
5.添加查询用户群列表接口(后台返回结果暂时是空)

20200617
凡凡
1.发送消息插入时间戳显示逻辑优化。
2.发送消息生成消息ID的唯一标识符方法修改。
3.发送图片消息的构造方法修改,用消息ID+文件名构造消息一一对应的缓存文件,方便消息的缓存管理。
4.压缩图片获取缩略图的资源文件管理方法修改。
5.音频和视频消息构造和缓存处理方法修改,用消息ID+文件名构造消息一一对应的缓存文件(进行中)
Online
1.iOS13.5通知收不到处理,通知设置更新。

filePath = [SIMFileLocationHelper filepathForAudio:audioObject.path];