• 2019-04-21

    NSMutableArray 空数组相等, 导致 NSOutlineView 无法正确展开

    Views: 26522 | 2 Comments

    对于 Objective-C, 两个 NSMutableArray isEqual 返回 YES, 无论它们是否相同的范型类型. 这个特性导致 NSOutlineView 在展开两个空的节点(对应着不同的空数组实例)时, 出现混乱. 当你点击展开 A 时, 它却展开 B. 这个可能是 NSOutlineView 内部使用 isEqual 来判断 item 是否相等, 而不是用 "==".

    即使数组不是空的, 只要它们包含相同的内容, 都会出现这个 bug. 原因就是, 不应该用 isEqual 来判断, 而应该用 == 来判断指针是否相等.

    Posted by ideawu at 2019-04-21 15:19:22
  • 2019-03-21

    CVPixelBufferRef 与 CVOpenGLTextureRef: 图像处理中内存与显存的交互

    Views: 30775 | No Comments

    现代计算机系统在进行图像处理时,可以利用 CPU 或者显卡两种芯片之一进行处理,也可以同时使用两者。本文讨论在 macOS 上的图像处理。

    1. 完全使用 CPU 进行图像处理

    图像(如 png)从硬盘中读取并解压到内存中,之后的图像处理完全用 CPU 操作内存,和显卡无关。

    2. 完全使用显卡进行图像处理

    图像(如 png)从硬盘中读取并解压到内存中,然后传到显卡显存中(创建 OpenGL texture),图像一旦传到显卡,内存中的图像数据就可以删除了。

    3. 同时使用 CPU 和显卡进行图像处理

    图像同时存于内存以及显存中,有时利用显卡进行处理,有时利用 CPU 进行处理。因为图像数据同时保存于内存和显存中,所以需要某种绑定机制,关联内存中的图像和显存中的图像,并在一方变更时,更新另一方。

    # CVPixelBufferRef 与 CVOpenGLTextureRef

    CVPixelBufferRef 表示内存中的图像。

    CVOpenGLTextureRef 表示显存中的图像,内部使用 OpenGL 的 texture。

    苹果的 Core Video 框架提供了显存图像到内存图像的单向实时绑定机制,也即绑定 CVPixelBufferRef 和 CVOpenGLTextureRef,当后者(显存)更新时,框架自动更新前者(内存)。注意,这种实时绑定是单向的,更新的传导只有一个方向,即从显存到内存。

    使用 CVOpenGLTextureCacheCreateTextureFromImage() 函数来建立这种绑定关系。绑定建立后,用户(也即你的代码)对显存或者内存的操作,都必须涉及到锁,因为框架本身同时也会操作这两份数据。

    涉及到多者同时操作一份数据的情况,都需要锁。

    使用 CVPixelBufferLockBaseAddress() 和 CVPixelBufferUnlockBaseAddress() 两个函数进行锁操作。显然,当你的代码要操作内存数据时,你应该 lock CVPixelBufferRef,当你想操作显存数据时,就应该 unlock CVPixelBufferRef。

    前面提到,这种单向绑定会将显存的更新传导到内存,所以,当你更新完显存(即执行 OpenGL glFinish 操作,文档提到是 glFlush 之后)之后,你就获得了 OpenGL 渲染的图像数据(OpenGL 截图),其内部实现应该是用 glReadPixels() 或者 glGetTexImage() 函数。这时,你就可以把 OpenGL 渲染的结果保存成 png 文件了。

    # 关于 Metal 框架的 CVMetalTextureRef

    CVMetalTextureRef 是用来替代 CVPixelBufferRef 的,因为苹果已经发布了 Metal 框架用来替代所有苹果操作系统上的 OpenGL。

    提一句,从面向对象的角度 CVPixelBufferRef CVOpenGLTextureRef CVMetalTextureRef 这三者都是 CVImageBufferRef 的子类。不过,在 C 语言里,这三者是同一个结构体,只不过用 typedef 命了 4 个名字而已。

    typedef CVImageBufferRef CVPixelBufferRef;
    typedef CVImageBufferRef CVOpenGLTextureRef;
    typedef CVImageBufferRef CVMetalTextureRef;
    

    CVImageBufferRef(也即 CVBufferRef)内部应该是用了 type 字段来表示不同的子类,毕竟 C 语言没有 C++ 那样的类和继承,只能用结构体来实现子类,都是惯用法,大同小异。

    如果你使用 Core Graphics, 那么从内存到显存的路径是这样的: CGImage => CGBitmapContext => OpenGL texture

    Posted by ideawu at 2019-03-21 17:07:19
  • 2019-02-28

    macOS NSView 实现 drag and drop 拖放

    Views: 27694 | No Comments

    macOS 实现 drag and drop 拖放很复杂,因为苹果的开发者中心把很多 demo 代码都不知道放到哪里去了,你用苹果开发者网站找不到 Sample Code,用 Xcode 帮助也没法找到(补充:sample code 在 https://developer.apple.com/library/archive/navigation/#section=Resource%20Types&topic=Sample%20Code )。而通过搜索引擎找的话,各种例子不是老旧就是没讲对。

    其实 drag and drop 很简单。有两个概念:DraggingSource 和 DraggingDestination。你可以把拖放理解两个容器之间的交互,你要将某物从A拖到B然后放下。

    最重要的一点,不管实现什么接口什么方法,你必须告诉系统什么时候拖放开始了。我看了很多文章,都没有重点提到这一点。也就是说,不管你实现什么接口都没有用,你必须主动告诉系统拖放已经开始了。所以,在你的代码中明确地告诉系统这一点,一般是鼠标按住的时候。

    当你决定拖放已经开始了,就调用这个方法:

    // NSView 的方法,当你决定 drag-n-drop 可以开始的时候,调用此方法
    - (NSDraggingSession *)beginDraggingSessionWithItems:(NSArray *)items event:(NSEvent *)event source:(id)source;
    

    调用这个方法之后,系统会自动地更新拖动过程的示意图的位置。当然,你要告诉系统示意图的开始位置和图片,通过上面的方法中的参数。另外,drag-n-drop 通过剪贴板在源和目的之间进行通信,所以剪贴板充当通信信道。下面的代码配置了通信信道和示意图:

    // NSPasteboardItem 用于在 drag-n-drap 的双方之间进行通信
    NSPasteboardItem *pbItem = [NSPasteboardItem new];
    // 你可以直接传输数据, type 可自定义.
    [pbItem setString:@"Transition" forType:NSPasteboardTypeString];
    // 或者指定传输的数据由 NSPasteboardItemDataProvider 提供。
    //[pbItem setDataProvider:self forTypes:@[NSPasteboardTypeString]];
    // NSDraggingItem 用于显示 drag-n-drop 过程的示意图
    NSDraggingItem *dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
    NSImage *img = [NSImage imageNamed:NSImageNameHomeTemplate];
    NSRect frame = NSMakeRect(0, 0, img.size.width, img.size.height);
    // draggingFrame 用于指定示意图的初始位置(在当前 NSView 中),contents 是示意图(NSImage)
    [dragItem setDraggingFrame:frame contents:img];
    

    这认为用网络通信模型来理解会更好,网络和通信是一个广义的概念,可以解释很多东西,我之前有文章介绍过:http://www.ideawu.net/blog/archives/528.html

    弄明白了上面的之后,你再查看这几个接口就懂了:NSDraggingSource, NSDraggingDestination, NSPasteboardItemDataProvider

    Posted by ideawu at 2019-02-28 19:52:20
  • 2019-02-20

    Xcode 静态链接库找不到的问题

    Views: 25748 | No Comments

    如果你的 Xcode 程序编译正常,但执行时遇到类似的报错:

    +[NSCharacterSet SVGWhitespaceCharacterSet]: unrecognized selector sent to class
    

    这种问题是因为静态链接库中的 Objective-C 的 Category 符号没有被链接进你的程序。你可以用下面的命令看看符号有没有被链接:

    nm Cli | grep SVGWhitespaceCharacterSet
    

    网上说你可以在 Other Linker Flags 里增加 -ObjC,但如果你的项目还要用别的非 OC 的库,则会报错,例如你同时使用 C++ 库。正确的做法是,在 Other Linker Flags 里增加

    -force_load $(PROJECT_DIR)/...../libxxx.a
    

    -force_load 后面跟绝对路径。

    Posted by ideawu at 2019-02-20 19:44:17
  • 2019-02-16

    苹果Cocoa框架中关于NSView和NSViewController的关系

    Views: 22080 | No Comments

    开始接触苹果的 GUI 框架时,经常对 NSView/UIView 和NSViewControoler/UIViewController 的关系感到疑惑,因为包括 Windows 界面开发在内的很多GUI框架,都只有 View,而没有 ViewController 的概念。而且在很长一段时间的实际使用中,也经常感到 ViewController 是多余,特别是开发自定义 View,逻辑代码者写在 View 类中更方便。

    虽说 View 和 ViewController 是 MVC中 的概念,但我想从内存管理的角度来说说为什么 ViewController 必须存在。假设在这样的场景,这一个用户界面,上面随机地生成不定数量的框,然后把我们的 subview 添加到其中一个框里面。因为这些框是随机生成的,所以某一个代码框的对象可能在生成之后又被销毁了。如果这个框对象(也即 subview 的父节点)在被销毁时,也把 subview 销毁,那就不符合预期了。同时,也不能在销毁框之前把 subview 从框中移除,因为从代码逻辑上这样的实现并不优雅,而且违反很多代码的逻辑。

    所以,subview 的内存应该次给父节点之外的其它对象管理,自然而然地想到给 subview 创建一个 ViewController,逻辑上表示 View 属于 ViewController,由后者管理其内存,这样逻辑上比较通。不然只能交给业务代码管理,这样逻辑上不太合理。

    同时,允许存在不属于任何一个 ViewController 的独立的 View,实践上需要这种规则,例如我们动态地给某个父节点添加子节点,并且希望在销毁父节点时自动销毁子节点,因为这样代码更简洁。

    所以,有两个动机:

    1. 希望有些情况下销毁父节点时,不自动销毁子节点。
    2. 希望有些情况下销毁父节点时,自动销毁子节点。

    解决方案是 ViewController 和引用计数。

    这是 GUI 框架设计的一种思路。

    Posted by ideawu at 2019-02-16 22:32:55
  • 2018-02-07

    NSView NSImage NSData转换

    Views: 25056 | No Comments
    NSBitmapImageRep *bitmap =  [view bitmapImageRepForCachingDisplayInRect:[view visibleRect]];
    [view cacheDisplayInRect:[view visibleRect] toBitmapImageRep:bitmap];
    
    NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
    [image addRepresentation:bitmap];
    
    NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:data];
    
    NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc] initWithCGImage:CGImage];
    
    Posted by ideawu at 2018-02-07 16:10:47
|<<<1234>>>| 1/4 Pages, 20 Results.