OpenGL + Xcode 开发环境搭建

一、OpenGL 相关库的下载与安装 GLFW GLFW 是配合 OpenGL 使用的轻量级工具程序库,缩写自 Graphics Library Framework(图形库框架)。GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能。 就是用来创建窗口界面。 // 使用 Homebrew 安装: brew install glfw3 // 头文件路径 /usr/local/Cellar/glfw/3.3.4/include/GLFW // 库路径 /usr/local/Cellar/glfw/3.3.4/lib GLEW OpenGL Extension Wrangler Library (GLEW), a cross-platform C/C++ library that helps in querying and loading OpenGL extensions. 实现对底层OpenGL接口封装。 // 使用 Homebrew 安装: brew install glew // 头文件路径 /usr/local/Cellar/glew/2.2.0_1/include/GL // 库路径 /usr/local/Cellar/glew/2.2.0_1/lib Glad 作用与 GLEW 类似。 下载地址:https://glad.dav1d.de/ Language: C/C++ Specification: OpenGL API: Version 3....

August 4, 2021 · Darren Ou

macOS 解决 App Nap 导致 Timer 回调变慢问题

背景 在开发 macOS 上的窗口共享过程中,发现 demo 被其它应用覆盖后,在25s左右,观众收到的窗口画面开始变得卡顿。 经过排查发现问题出在主播端的采集逻辑处。在我的上一篇文章 macOS 屏幕共享开发 提到,窗口共享的实现跟屏幕共享不一样,窗口共享没有现有的系统接口,需要启动一个 Timer 去定时对窗口截图,再对截图进行处理。 通过打印日志发现,demo 进入后台后,Timer 在25s左右,回调开始变慢,大约2s才回调一次,问题的原因就出在这里。 排查原因 为了避免其它逻辑影响了结果,新建一个干净的 demo。 我在窗口共享中用的是 GCD Timer,一开始猜测是 GCD Timer 的某些参数没有设置,查看文档后没找到相关的参数;然后猜测 GCD Timer 的问题,但是换成 NSTimer 后,发现也有一样的问题,这时候我开始猜测是系统导致的问题了。 开始查阅资料,发现了 一篇文章 里面提到: App Nap 是 OS X 10.9 Mavericks 的一项新功能。它能帮你在同时运行多个应用程序时节省电能。 这时候有了切入点,查询关键字 App Nap 就可以发现相关资料。 App Nap 附上苹果官方说明文档:Extend App Nap If an app isn’t performing user-initiated work such as updating content on screen, playing music, or downloading a file, the system may put the app in App Nap....

July 5, 2021 · Darren Ou

macOS 屏幕共享开发

一、屏幕录制权限 应用首次创建屏幕截图或者窗口截图时(以下任一接口都可以),就会显示系统的权限申请弹窗,通过选中Screen Recording里面的相关应用,重启应用后即可开启权限。 CGDisplayCreateImage() CGDisplayCreateImageForRect() CGWindowListCreateImage() CGWindowListCreateImageFromArray() 二、获取screenId 通过AppKit的NSScreen.screens或者通过CoreGraphics的CGGetActiveDisplayList()都可以获取当前的所有显示器信息,下面用NSScreen.screens举例子: [NSScreen.screens enumerateObjectsUsingBlock:^(NSScreen * _Nonnull screen, NSUInteger idx, BOOL * _Nonnull stop) { NSNumber *screenNumber = screen.deviceDescription[@"NSScreenNumber"]; if (screenNumber) { CGDirectDisplayID displayId = screenNumber.unsignedIntValue; NSLog(@"displayId: %d", displayId); } }]; 注意:如果是镜像显示,则只会打印一个显示器信息。 三、获取windowId 1、获取windowId列表 通过接口CGWindowListCopyWindowInfo()可以获取windowId列表。 其中kCGWindowListExcludeDesktopElements参数表示从列表中排除所有属于桌面元素的窗口。 CGWindowListCopyWindowInfo()返回的是一个CFArrayRef,使用完后需要主动release。 这里有一个技巧,利用CFBridgingRelease()函数,可以把CFArrayRef桥接到NSArray,并且会把内存管理转移到ARC,就是说我们不需要再去主动release了。 NSArray *windowDictArr = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListOptionAll | kCGWindowListExcludeDesktopElements, 0)); 2、过滤无用windowId 通过以上接口获取到的windowId列表可能会包含很多我们不关心的windowId: 1. 没显示出来的window 通过kCGWindowLayer跟kCGWindowAlpha两个key,可以获取到当前window是否有layer以及是否透明,我们需要过滤掉不可见的window。 2. 过滤信息不全的window kCGWindowNumberkey可以从字典里获取到windowId,需要过滤没有windowId的字典信息。 3. 过滤无法获取截图的window 通过CGWindowListCreateImage()接口,传入对应windowId,可获取到window的截图,如果无法获取到截图,可能没有录屏权限、不是window主体、window不可见。 3、获取window frame 通过CGRectMakeWithDictionaryRepresentation()可获取窗口的frame,某些业务可能需要用到frame。 四、屏幕共享 屏幕共享的视频输入总体来说跟摄像头的视频输入相当类似,可以理解成视频流从摄像头改成了屏幕输入(AVCaptureDeviceInput->AVCaptureScreenInput),视频输出的处理则跟摄像头的输出处理完全一样。 1、关键代码: - (void)startCapture { self....

May 18, 2021 · Darren Ou

iOS UI 自动化测试简介

前言 发布内测版的时候,每次在提交代码前、上传ipa包前、发布前都需要手动地跑一下主流程,比如在视频详情页划几页,在直播页划几个直播间等。都是比较重复且有规律的操作,可以利用自动化测试来代替手工操作。 我们在Jenkins上面打包时,会有一个UI自动化测试的选项,是架构组自己弄的一套自动化测试环境,想要自己搭建估计不太方便。所以需要有一套轻量级切足够易用的UI自动化测试框架。 XCUITest 简介 XCUITest是苹果在iOS9.3提供的自动化测试框架(iOS9.3之前提供的是UI Automation),Xcode自带的框架,不需要搭建其它环境,由于是苹果提供的框架,运行稳定。使用Swift或者OC编写测试用例(可能对于测试不是很友好)。 使用 func testExample() throws { // UI tests must launch the application that they test. let app = XCUIApplication(bundleIdentifier: "appium.test") app.launch() // 启动应用 for _ in 1 ..< 5 { app.swipeUp() // 上划 } for _ in 1 ... 3 { app.swipeRight() // 右划 } app.swipeDown() // 下划刷新 sleep(5) // 等待5s再执行下一步 app.cells.firstMatch.tap() // 点击当前的第一个cell for _ in 1 ... 5 { app.swipeUp() // 上划 sleep(2) // 等待2s再执行下一步 } app....

November 28, 2020 · Darren Ou

在树莓派上搭建 Homebridge,实现 HomeKit 控制智能家居

一、什么是Homebridge Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It supports Plugins, which are community-contributed modules that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of “smart home” devices. HomeKit是苹果的智能家居平台,是iOS系统层面支持的平台,拥有其它平台无法比拟用户体验。可惜大部分米家设备及其它平台的设备都不支持HomeKit,但是可以通过Homebridge作为桥接,实现HomeKit控制其它平台的设备。 二、如何在树莓派上搭建Homebridge https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Raspbian 三、安装插件 在这个网址https://www.npmjs.com/search?q=keywords%3Ahomebridge-plugin上可以通过关键字搜索插件, 常用插件: 1.Aqara套装:homebridge-mi-aqara 2.米家空调伴侣:homebridge-mi-acpartner 3.米家yeelight台灯:homebridge_yeelight 4.小米风扇:homebridge-mi-fan 5.米家智能摄像机:homebridge-mi-camera 6.米家智能排插:homebridge-mi-outlet 7.米家空气净化器:homebridge-mi-airpurifier 8.飞利浦灯泡:homebridge-mi-philips-light 9.智能插座:homebridge-mi-outlet ... 四、配置设备 在Homebridge上,插件利用设备的IP地址及设备的token即可以实现控制某个设备,IP地址我们可以登录路由器查看已连接的设备获取,获取token有以下几种方法: 1、使用安卓手机下载旧版本的米家app,导出里面的数据库找到设备token 2、使用miio discover命令获取(米家新的设备基本都隐藏了token,这种方法基本失效) 3、利用Charles抓包 Homebridge具体配置如下: { "bridge": { "name": "Homebridge D535", "username": "0E:E2:26:BC:D5:35", "port": 51762, "pin": "720-92-067" }, "accessories": [], "platforms": [ { "name": "Config", "port": 8581, "platform": "config" }, { "deviceCfgs": [ { "type": "MiPhilipsSmartBulb", "ip": "192....

October 11, 2020 · Darren Ou

【利用基于 Lint 的启动代码监控 Android 进房间流程】iOS 平台的可行性调研

一、目的 通过标识关键代码,利用Lint定期检测关键代码,当关键代码发生变更时,可以在报告中体现,避免引入意外的修改。 二、Lint Lint介绍 Lint 作为一种工具程序,主要负责静态源码分析,负责代码规范、代码缺陷等检测。(https://zh.wikipedia.org/wiki/Lint) Android 原生已经提供了 Lint 静态分析工具,而 iOS 则有对应的 OCLint 和 SwiftLint。 三、android实现代码监控的原理 Android Lint可以对Java/Kotlin源码使用uast进行语法分析,最后生成一棵AST抽象语法树,这棵树的每个节点都是一个方法,包含方法签名和方法体。 通过自定义的Lint规则,找到应用启动的入口方法。从入口方法开始遍历整棵调用树,即可对每个方法节点的代码段进行分析。 1. 获取关键代码段 open fun shouldMonitor(node: UMethod): Boolean { return node.getAnnotations().find { it.qualifiedName == LINT_MONITOR_ANNOTATION } != null } android通过注解的方式,标识需要检测的方法实现。通过Android Lint的自定义规则判断注解标识的方法,找到入口方法,然后对代码段进行分析。 iOS对应的OCLint也可以通过自定义规则进行静态分析,但是iOS没有注解,iOS如果需要标识关键代码段,目前想到的只能hardcode检测列表(包括类和方法名)来实现标识关键代码段的效果。但是这样的维护成本比较大。 iOS可以利用类似以下的代码片段进行类和方法的判断: for(auto it = parent->meth_begin(), end = parent->meth_end(); it != end; ++it) { const auto method = *it; if(declHasEnforceAttribute(method, *this)) { const auto selector = method->getSelector(); if(!implementation->getMethod(selector, method->isInstanceMethod())) { const string className = parent->getNameAsString(); const string methodName = selector....

August 20, 2020 · Darren Ou

利用 git worktree 并行开发

一、什么是worktree? Manage multiple working trees attached to the same repository. A git repository can support multiple working trees, allowing you to check out more than one branch at a time. 管理连接到同一存储库的多个工作树。一个git仓库可以支持多个工作树,允许一次迁出多个分支。 二、为什么要用worktree? 场景:正在开发新的需求,这时候还需要对已提测功能的bug fixes 方法1:先stash正在开发的代码,再check out到原分支进行bug fixes 缺点:分支切换后需要重新pod install,然后xcode重建索引并重新编译,耗时耗力 方法2:clone两个仓库 缺点:.git目录会占空间,且两个仓库之间毫无关联,无法相互查看本地提交 方法3:git worktree 三、worktree的相关命令 ➜ worktree_test git:(develop) git worktree -h usage: git worktree add [<options>] <path> [<commit-ish>] or: git worktree list [<options>] or: git worktree lock [<options>] <path> or: git worktree move <worktree> <new-path> or: git worktree prune [<options>] or: git worktree remove [<options>] <worktree> or: git worktree unlock <path> 四、常用操作 1、添加一个worktree git worktree add ....

May 10, 2020 · Darren Ou

树莓派简介

一、树莓派是什么 树莓派是一款基于ARM的微型电脑主板,系统基于Linux,以MicroSD卡为磁盘,树莓派主板有4个USB接口,一个以太网接口,一个3.5mm音频口,一个HDMI视频接口,40-pin的GPIO连接端口 https://www.raspberrypi.org/products/ 二、树莓派可以干什么 可以当做一个微型服务器,作为FTP文件服务器、网站服务器、代码托管等 游戏机(刷入游戏模拟器系统,可玩大部分复古游戏)https://www.jianshu.com/p/feffa629084a 遥控车(手机通过socket连接控制小车,且小车装有红外线传感器,可以自动避障), 可以结合其他传感器搭建智能家居 三、DHT11温湿度传感器 传感器文档 价格便宜,体积小,功耗低,缺点是精度不高 有四个针脚:正极、负极、串行数据总线、空脚 DHT11温湿度传感器数据读取原理 代码: __author__ = 'DuShu' import time import RPi.GPIO as GPIO def getIntVal(binayList): idx = 0 binaryStr = '' while idx < len(binayList): binaryStr += str(binayList[idx]) idx += 1 return int(binaryStr ,2) def getHumidity(): GPIO_DATA = 18 GPIO_HIGH = GPIO.HIGH GPIO_LOW = GPIO.LOW GPIO.setmode(GPIO.BCM) #一秒后开始工作 time.sleep(1) #设置GPIO接口为输出数据模式 GPIO.setup(GPIO_DATA,GPIO.OUT) #输出一个低电平信号 GPIO.output(GPIO_DATA, GPIO.LOW) time.sleep(0.02) #0.018 #0.02秒后输出一个高电平信号,启动模块测量 GPIO.output(GPIO_DATA, GPIO.HIGH) #设置GPIO接口为读取读取数据模式 GPIO.setup(GPIO_DATA,GPIO.IN) #等待,获取到高电平信号 while GPIO....

March 20, 2020 · Darren Ou

直播间横屏技术方案

一、背景 目前直播间已支持PC推流直播,但只支持竖屏播放,导致观看体验不佳,故需要把横屏模式加上。 二、面临的问题 1、只有两天开发时间,时间紧迫; 2、屏幕切换时存在UI异常风险; 3、横屏模式需要应用全局支持横屏方向,存在影响其他业务风险; 三、方案 1、切换横屏入口的显示(加开关) joinChannel/joinGroup成功 -> roomType == BSRoomType_PCRoom -> 判断开关 -> 显示入口 2、横竖屏切换初步方案 1.项目配置: 在AppDelegate里重写方法: - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { UIViewController *topViewController = [BaseViewController bl_findTopMostViewController]; if (topViewController && [topViewController isKindOfClass:NSClassFromString(@"DSLandscapeViewController")]) { return topViewController.supportedInterfaceOrientations; } return UIInterfaceOrientationMaskPortrait; } 2.竖屏转横屏: 新建一个支持横屏的DSLandscapeViewController并重写UIViewController的方法: - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait; // 这里加上竖屏,是因为横屏转竖屏时,需要把当前vc先转过来,后面会讲到 } - (BOOL)shouldAutorotate { return YES; } 从竖屏vc(DSLiveShowViewController)旋转时,初始化横屏vc(DSLandscapeViewController),并把竖屏vc当前持有的BLLiveRoomAudienceSession传递给横屏vc。然后在当前navigationController的子vc中,把竖屏vc替换成横屏vc(原竖屏vc会销毁)。 - (void)switchToLandscapeView { DSLandscapeViewController *viewController = [[DSLandscapeViewController alloc] initWithAudienceSession:self....

January 4, 2020 · Darren Ou

iOS 项目接入 CallKit 框架

CallKit是什么 苹果在WWDC 2016发布了iOS 10的新框架CallKit,它允许开发者在VoIP类型APP整合系统原生语音界面,以获得更好的用户体验。 接入CallKit后,APP里面的通话会被写入系统通话记录,而且APP通话时的优先级比一般VoIP的APP优先级要高。 CallKit主要类 我们可以把系统提供的CallKit框架理解成一个通话的界面UI,我们做的只是把这个通话界面跟我们的应用联系起来, 比如有一个语音电话过来了,那么我们就呼起这个界面,用户在这个界面上点击了静音按钮,我们相应地做静音的处理。 同样地,如果用户在应用里面点击了静音按钮,也要把状态同步到系统的界面。 呼起CallKit & CallKit回调 CXProviderConfiguration 描述系统通话界面的标题、logo、来电铃声等 CXCallUpdate 描述来电信息,如来电用户名、是否视频聊天等 CXProvider 同步连接状态到CallKit CXProviderDelegate 用户在系统通话界面上动作的回调 应用把状态同步到系统: CXCallAction 用户不同动作的描述,如:呼出电话、接听、挂断、静音等(分别对应CXStartCallAction、CXAnswerCallAction、CXEndCallAction、CXSetMutedCallAction) CXTransaction 把用户的不同动作打包成CXTransaction再传递给系统 CXCallController 把CXTransaction传递给系统 原理 1、呼入 来电 1.CallKitTestApp接收到服务器的来电信息(后台时VoIP Push) 2.创建CXCallUpdate对象记录来电信息,并记录该通话唯一标识 3.通过CXProvider对象把来电信息通知系统 4.系统接收到来电信息后显示原生来电UI 响应来电 1.用户在原生来电UI上点击接听按钮(或挂断按钮) 2.系统把动作封装成CXAnswerCallAction,通过CXProvider的Delegate回调到CallKitTestApp 3.CallKitTestApp收到回调后会开始配置mediasdk,调整UI等操作,最后开始通话 结束来电 1.CallKitTestApp在前台时,用户点击挂断按钮 2.创建CXTransaction对象,用于包装挂断动作CXEndCallAction(包含该通话唯一标识) 3.通过CXCallController对象把挂断动作通知系统 4.系统成功挂断该通话后,通过CXProvider的Delegate回调到CallKitTestApp 5.CallKitTestApp收到回调后会开始释放mediasdk(这里要注意释放顺序,不能在系统回调前把sdk释放掉),调整UI等操作 2、呼出 1.用户在CallKitTestApp呼出电话 2.创建CXTransaction对象,用于包装呼出动作CXStartCallAction(包含该通话唯一标识、呼出号码等信息) 3.通过CXCallController对象把呼出动作通知系统 4.系统成功呼出该通话后,通过CXProvider的Delegate回调到CallKitTestApp 5.CallKitTestApp收到回调后会开始配置mediasdk,调整UI等操作,最后开始通话 以上呼出逻辑是CallKitTestApp中IP-IP电话的处理逻辑,因为CallKitTestApp里面还有自动转换IP-PSTN逻辑,涉及到mediasdk资源释放问题,导致应用不稳定,内测版中暂时把呼出部分屏蔽。 CallKitTestApp如何接入 1、mediasdk做兼容 1.系统资源申请成功后再配置mediasdk 2.等系统资源释放后再释放mediasdk 为满足以上两点,mediasdk开放了下面几个接口,在CallKit模式下,等系统回调后再做mediasdk配置、激活、释放等操作 // In CallKit, it should be called by `perform Answer/start CallAction` bool callKit_test_configAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `didActivateAudioSession` bool callKit_test_startAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `didDeactivateAudioSession` void callKit_test_stopAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `performEndCallAction` void callKit_test_releaseAudio(MEDIA_HANDLE handle); 2、CallKitTestApp在前台状态 1....

May 10, 2019 · Darren Ou

多任务页面底部 banner 出现的种类

多任务页面底部banner出现的种类 1、handoff(接力) 2、xxx,早上好 3、基于您的当前位置 4、“耳机"已连接 5、(未知。。。) 实现 handoff 设置 -> 通用 -> 接力 -> 打开接力 通过“接力”功能,您在某设备开始操作后,可立即使用iCloud账号从另一设备继续相同操作。您需要的应用会显示在应用切换器和Mac的程序坞上。 这种一般应用在需要跨设备共享编辑的应用上,例如“印象笔记”,在手机上编辑了一段文字后,可以切换到Mac上继续编辑。 官方简介:https://support.apple.com/zh-cn/HT204681 这是开发者唯一能控制的底部banner种类 Siri应用建议 xxx,早上好 基于您的当前位置 “耳机”已连接 关于前两种情况: 系统会记录用户长期的使用应用的习惯,自主地向用户推荐相关的应用。例如,早上起床后我会习惯打开微博,系统早上就会给我推荐微博;来到公司我会习惯打开微信,系统也会根据地理位置给我推荐微信。系统会通过记录用户的使用频率、时间、地理位置等维度,向用户推荐应用。 关于“耳机”已连接 手机连接蓝牙耳机或者有线耳机后,系统会弹出 “耳机”已连接 的banner,如果手机装有多个音乐应用,目前观察到的情况是会弹出最常用的那个应用。但是有时候又不会弹,而且弹出维持的时间也没有规律。 国外用户如是说: This is a feature suggesting which app you normally use in a certain situation. Like location, time of day, headphones or Bluetooth connected. Phone tries to learn if you always do something. Like I always open maps when I connect to my car Bluetooth....

November 12, 2018 · Darren Ou

iOS 学习笔记

一、内存分配 结构体分配内存,内存对齐(8的倍数) 对象分配内存,内存对齐(16的倍数) 一个NSObject对象占用多少内存?(一个指针变量所占用的大小(64bit,8个字节,32bit,4个字节)) class_getInstanceSize() 至少需要多少内存 malloc_size() 实际分配了多少内存 二、对象 对象类型(实例对象、类对象、元类对象) 对象的isa指针指向哪里?(instance对象的isa指向class对象;class对象的isa指向meta-class对象;meta-class对象的isa指向基类的meta-class对象) OC的类信息存放在哪里?(对象方法、属性、成员变量、协议信息,存放在class对象中;类方法存放在meta-class对象中;成员变量的具体值存放在instance对象中) isa & ISA_MASK 才是真正的类对象或元类对象地址(arm64下是0x0000000ffffffff8ULL) 三、KVO、KVC KVO的本质(利用runtime动态生成一个子类,重写set方法、class、dealloc、isKVOA方法,并且让instance对象的isa指向这个全新的子类;当修改instance对象的属性时,会调用_NSSetXXXValueAndNotify函数;willChangeValueForKey/setter/didChangeValueForKey) 如何手动触发KVO(手动调用willChangeValueForKey/didChangeValueForKey,必须两个方法都调用) 直接修改成员变量会触发KVO吗(不会) KVC set原理(查找set方法:setKey -> _setKey;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) KVC get原理(查找get方法:getKey -> key -> isKey -> _key;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) 四、Category Category 原理(编译成struct category_t,存储这对象方法、类方法、属性、协议信息,程序运行时会将Category的数据合并到类信息中) Category和Class Extension区别(Class Extension在编译的时候数据就包含在类信息中;Category在运行时才会将数据合并到类信息中) +load方法的调用顺序(类:先编译的先调用;先调用父类的 分类:按照编译顺序调用) +initialize方法在类第一次接收到消息时调用,先调用父类的+initialize,再调用子类的+initialize +load和+initialize最大的区别是,+load通过函数地址直接调用,+initialize通过objc_msgSend进行调用,遵循消息发送的机制(如果分类实现了+initialize,则调用分类的;如果子类没有实现,则调用父类的+initialize;父类的可能被调用多次) 五、关联对象 key的设置(@selector(key) / static const char key; &key; / static const void *key = &key / @“key") 关联对象不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationManager中 AssciationsManager -> AssociationHashMap -> object: ObjectAssociationMap -> key: ObjectAssociation -> policy/value 对象销毁时会把对象对应的ObjectAssociationMap销毁 六、Block 本质是结构体对象(__main_block_impl_0) block类型(NSGlobalBlock:没有访问auto变量;NSStackBlock:访问了auto变量;NSMallocBlock:NSStackBlock copy) 捕获auto变量(NSStackBlock:不会强引用auto变量,栈block随时释放,没必要强引用) NSMallocBlock:当block拷贝到堆上时,会调用desc里面的copy函数,把auto变量拷贝到堆上;当从堆上移除时,会调用desc里面的dispose函数,release auto变量 __block 修饰符,会将其修饰的变量包装成一个对象;不能修饰全局变量、静态变量 MRC下,block不会对__block修饰的对象进行强引用(MRC下,可用__block解决循环引用问题) __weak:不会产生强引用,指向的对象销毁时会自动置nil; __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变 七、Runtime isa 位域(nonpointer 1为表示优化过的指针;has_assoc 是否有设置过关联对象;has_cxx_dtor 是否有c++的析构函数;shiftcls Class、mata-Class对象的内存地址;magic 对象是否完成初始化;weakly_referenced 是否被弱引用指向过;deallocating 对象是否正在释放;extra_rc 引用计数减1;has_sidetable_rc 引用计数器过大会存储在SideTable中) SEL 代表方法、函数名,底层结构跟 char *类似 不同类中相同名字的方法,对应的SEL是相同的 imp 指向函数实现的指针 Type Encoding “i24@0:8i16f20” 代表:返回值是int,参数总大小为24字节,第一个参数类型是id,从全部参数的第0个字节开始,第二个参数类型是SEL,从全部参数的第8个字节开始,第三个参数类型是int,从全部参数的第16个字节开始,第四个参数类型是float,从全部参数的第24个字节开始 方法缓存 cache_t 用散列表缓存曾经调用过的方法,提高方法查找速度(空间换时间) 消息发送流程:从cache查找 -> 通过isa指针找到类对象并从class_rw_t里面的方法列表里面查找 -> 从superclass的cache里面查找 -> 从superclass的方法列表里面查找 -> resolveInstanceMathod 尝试动态方法解析一次 -> 重走一次查找方法流程 -> forwardingTargetForSelector 快速消息转发 -> forwardInvocation 完整消息转发(需要先获取方法签名 methodSignatureForSelector) NSMethodSignature 方法签名:返回值类型、参数类型 NSInvocation 封装了一个方法调用,包括:消息接受者、方法名、方法参数 super:objc_msgSendSuper({self, superclass}, @selector(class)),接收者仍然是子类对象,从父类开始查找方法实现 runtime用途:关联对象,遍历所有成员变量,字典转模型,自动归档解档,交换方法实现,消息转发机制 交换方法时注意类簇,如 NSMutableArray -> __NSArrayM 八、属性 @dynamic 不要自动生成setter、getter、成员变量 九、RunLoop 每条线程都有唯一的与之对应的RunLoop对象 RunLoop保存在一个全局的字典里,线程作为key,RunLoop作为value 线程刚创建时没有RunLoop对象,RunLoop会在第一次获取它时创建 一个RunLoop包含若干个Mode,RunLoop启动时只能选择其中一个Mode作为currentMode,如果需要切换Mode,只能重启RunLoop 如果Mode里面没有任何Sources0/Source1/Timer/Observer,RunLoop会立马退出 Source0: 触摸事件/performSelector:onThread: Source1: 基于Port的进程间通信/系统事件捕捉 Timers: NSTimer/performSelector:withObject:afterDelay: Observers: 监听RunLoop状态/UI刷新/AutoreleasePool(BeforeWaiting) 作用:保持程序持续运行、处理各种事件、节省cpu资源 GCD不依赖RunLoop,但是异步到主线程则会唤醒RunLoop RunLoop响应用户操作:Source1接收事件,交给Source0处理 RunLoop应用:线程保活/卡顿监控/Timer滑动时停止/性能优化 defaultMode和trackingMode都是真实存在的模式,commonMode时一个标记 [NSRunLoop run] 相当于不停调用 [NSRunLoop runMode:beforeDate:] 子线程默认没有RunLoop,子线程中调用[performSelector:withObject:afterDelay:]无效 十、多线程 同步:在当前线程中执行任务,不具备开启新线程的能力; 异步:在新的线程中执行任务,具备开启新线程的能力; 并发:多个任务并发执行; 串行:一个任务执行完毕后,再执行下一个任务; 使用sync往当前串行队列中添加任务,会产生死锁; OSSpinLock:自旋锁,等待锁的线程会处于忙等状态,一直占用CPU资源;优先级反转;如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁; 递归锁:允许同一个线程对一把锁重复加锁 atomic,相当于在getter、setter内部加了锁,并不能保证使用属性的过程是线程安全的 多读单写:pthread_rwlock dispatch_barrier_async 通过dispatch_barrier_async实现多读单写,传入的并发队列必须时通过dispatch_queue_create创建的 十一、Timer NSTimer:内存泄漏问题,不准时问题 十二、内存布局 从低地址到高地址:保留 -> 代码段 -> 数据段(字符串常量 -> 已初始化数据 -> 未初始化数据) -> 堆 -> 栈 -> 内核区 TaggedPointer:Tag + Data,将数据直接存储到指针中;当指针不够存储数据时,才会使用动态分配内存的方式来存储数据 如何判断TaggedPointer:macOS:最低有效位为1;iOS:最高有效位为1 堆空间中对象的内存地址最后一位是0,因为结构体有内存对齐,16的倍数 引用计数:新创建的对象引用计数为1,当引用计数为0时对象会被销毁 synthesize:生成成员变量和属性的setter、getter alloc/new/copy/mutableCopy 返回了一个对象,需要调用release或者autorelease copy:不可变拷贝,产生不可变副本 mutableCopy:可变拷贝,产生可变副本 深拷贝:内容拷贝,产生新的对象 浅拷贝:指针拷贝,没有产生新的对象 自定义对象实现copy:遵守NSCopying协议,并实现-copyWithZone方法 引用计数的存储:64bits中,引用计数可以存储在isa指针中,如果不够存储,则会存储在SideTable中 AutoreleasePoolPage:每个对象占用4096个字节,存放内部成员变量和autorelease对象的地址 AutoreleasePoolPage通过双向链表的形式连接在一起 autorelease与runloop的关系:entry -> push; beforeWaiting -> pop -> push(runloop休眠之前); exit -> pop 十三、CPU卡顿优化 尽量使用轻量级的对象,如CALayer代替UIView 不要频繁修改UIView的frame、bounds、trasform等属性 提前计算好布局,有需要时一次性修改,避免多次修改 Autolayout会比直接设置frame消耗更多CPU资源 图片的size最好跟UIImageView的size保持一致 控制线程的最大并发数 把耗时操作放在子线程(文本处理(尺寸计算、绘制)、图片处理(解码、绘制)) 十四、GPU卡顿优化 避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示 GPU能处理的最大纹理尺寸是409684096,超过这个尺寸会占用CPU资源进行处理 尽量减少视图数量和层次 减少透明的视图 避免出现离屏渲染 离屏喧嚷需要创建新的缓冲区,需要多次切换上下文环境 触发离屏渲染:光栅化(layer....

March 8, 2018 · Darren Ou

iOS 项目集成 Reveal

Reveal是一款UI调试工具,在iOS开发过程中可查看UI的层级关系并可动态修改界面,可以有效提高开发效率。这篇文章主要介绍如何把Reveal集成到实际的项目中。 一、Reveal主要有两种集成方式: 1、framework集成(可忽略,直接看第二点) (1)打开Reveal,选择 Reveal –> Help –> Show Reveal Library in Finder –> iOS Library (2)把Reveal.framework导入到项目中 (3)配置Target,Target –> Build Setting –> Other Linker Flags 添加以下4项: (4)运行项目 –> 打开Reveal –> 选择连接设备 项目的UI图层就可以出来了。 这种方法有几个弊端:1、需要配置项目的Build Setting,修改Other Linker Flags,多人协作开发的话如果有人没有配置就会编译出错;2、如果不留意容易打包的时候把Reveal的framework也打包进去,增大包的体积。 以下推荐另一种集成方法。 2、LLDB集成(推荐!!) (1)运行项目 –> 点击Pause program execution。 (2)在LLDB依次输入以下两条命令 # expr (Class)NSClassFromString(@“IBARevealLoader”) == nil ? (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2) : ((void*)0) # expr (void)[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] postNotificationName:@“IBARevealRequestStart” object:nil]; 第一条命令用于加载Reveal的动态链接库; 第二条命令用于启动Reveal调试服务。 输入两条命令后,如果LLDB打印 “ INFO: Reveal Server started ” 即成功启动Reveal服务,点击Continue即可。...

December 21, 2017 · Darren Ou

iOS 利用 Speech Kit 实现语音识别

一、前言 前一段时间彩云小译上了App Store的推荐,我下载试玩了一下,效果还是非常不错的。它可以实现实时翻译的功能,我自己粗浅地分析了一下彩云小译的实现原理,其中最重要的一步就是声音转文字。 目前市面上也有很多服务商提供声音转文字的服务,有收费的有免费的,但是毕竟是第三方的服务商,接口的性能和稳定性都不一定能保证。 2016年Apple在发布重磅产品iOS10的同时也发布了Speech Kit语音识别框架,大名鼎鼎的Siri的语音识别就是基于Speech Kit实现的。有了Speech Kit,我们就可以非常简单地实现声音转文字的功能。下面我就简单介绍一下Speech Kit的用法。 二、实现 1、页面布局 因为只是实现一个Demo,页面不需要多复杂,只需要在Storyboard上拖入两个控件:一个UITextView用于展示声音转文字的结果,一个UIButton用于触发语音识别,最好布置好约束即可。具体效果如下图: 2、申请用户权限 首先需要引入Speech Kit框架 #import <Speech/Speech.h> 申请权限非常简单,在识别前(viewDidLoad:)加入以下代码即可申请语音识别的权限: - (void)viewDidLoad { [super viewDidLoad]; // 请求权限 [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) { NSLog(@"status %@", status == SFSpeechRecognizerAuthorizationStatusAuthorized ? @"授权成功" : @"授权失败"); }]; } 这时候运行起来会崩溃,原因是在iOS10后需要在info.plist文件中添加麦克分和语音识别权限申请信息: <key>NSSpeechRecognitionUsageDescription</key> <string>请允许语音识别</string> <key>NSMicrophoneUsageDescription</key> <string>请打开麦克风</string> 运行项目,会提示打开语音识别和打开麦克风权限,至此我们已经完成了权限的申请。 3、初始化语音识别引擎 添加以下代码: - (void)initEngine { if (!self.speechRecognizer) { // 设置语言 NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"]; self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale]; } if (!self.audioEngine) { self....

December 10, 2017 · Darren Ou

在树莓派上部署 ATC 网络模拟工具(Augmented Traffic Control)

一、前言 作为移动开发者的我们,为了良好的用户体验,经常需要模拟手机应用在比较差的网络环境下的表现,模拟网络环境的方式有很多,比如使用Charles,或者在手机的开发者模式下模拟网络环境等等,但是这些都有一定的门槛。 使用Charles,首先你得连接WiFi,然后设置代理,接着开启网络模拟模式,最后测试完了如果忘了关闭代理,可能手机就上不了网了。而且都连上你电脑的代理的话就一次只能模拟一种网络环境。 使用手机的开发者模式,首先你的手机能进入开发者模式。 如果你需要随便抓一位不懂技术同事帮你测试,这些方式都不太友好,有没有一种方式可以连上WiFi就可以使用的测试方式呢?有!接下来就介绍Facebook出品的一款网络模拟工具ATC。 二、简介 Augmented Traffic Control (ATC) is a tool to simulate network conditions. It allows controlling the connection that a device has to the internet. Developers can use ATC to test their application across varying network conditions, easily emulating high speed, mobile, and even severely impaired networks. ATC全名叫Augmented Traffic Control,是Facebook出品的一款网络模拟工具,移动开发者可以通过这款工具模拟不同条件下的网络环境,可以通过网页自由地模拟网络带宽(bandwidth)、延迟(latency)、丢包率(packet loss)、错包率(corrupted packets)和乱序率(packets ordering)。 而且!!!更牛逼的是:不同的设备连接到同一WiFi还可以模拟不同的网络环境互不影响。 github地址 三、准备 1、树莓派3(已内置有无线网卡) 2、已刷入最新RASPBIAN系统的SD卡 四、安装 安装主要有两步: 1、让树莓派有发射AP热点的能力; 2、安装ATC。 1、让树莓派有发射AP热点的能力 1.安装hostapd虚拟热点程序和dnsmasq配置DHCP、DNS服务程序: sudo apt-get install dnsmasq hostapd...

April 11, 2017 · Darren Ou