用objective c开发的第一个mac程序(2)——初识oc cocoa swift

第一阶段其实很蛋疼,牛人可能1个小时,半个小时,甚至10来分钟就能搞定了,而我却用了几天!这几天对我来说是挺打击的,自信心沉了一半……

接着第一阶段的记录,继续……

思路如下:

1. 编译boost库(mac版本)
2. 编译tinyxml库
3. 编译xxx-touch库
4. 在swift中调用xxx-touch库
5. 获取xxx-touch库中接收到的数据
6. 根据数据在界面上画出来

按照思路应该走到第四步了,前三步都是准备工作,后面的3步才是最关键的,仔细想想,把后3步点思路细分(当时的方案):

1. xcode怎么使用swift开发app呢?
2. 在swift中调用c++的库,来获取设备返回的信息(这个使用一条单独的线程);
3. 在swift中怎么使用线程(cocoa有个NSThread);
4. 怎么画线,需要使用什么api完成画图的功能?;
5. 将设备中的数据画出来(这个计划使用另外一条单独的线程)
6. swift怎么在2条线程中进行数据同步?怎么使用锁?

一、前期准备
http://www.cocoachina.com/industry/20131211/7517.html
https://developer.apple.com/library/mac/referencelibrary/GettingStarted/RoadMapOSX/books/RM_YourFirstApp_Mac/Articles/Introduction.html

看了之后对mac下开发app有初步多认识,《Programming With Cocoa》第14、15、16章主要讲cocoa的图形介绍,前两章讲图形的基础,16章讲交互(可惜一致都找不到第16章),当然其他的也应该看;
[当初为什么只看14 15章,因为对cocoa框架对了解基本是0,于是后面碰到了很多关于NSView的问题]

在swift与objective c之间的选择

mac上开发之前,对oc和swift做了对比,swift传说很强大,也是苹果新推出的语言,相必很有前途,于是就兴致勃勃到奔去学swift了。

看了swift都语法感觉很熟悉,像脚步语言,也像java,也像ruby,不过个人觉得最像的却是c#,语法、委托、扩展函数等(当时很有印象的,久了却忘了-_-!!)。瞬间觉得很有亲切感,想起了以前做过的一个系统(在上一个公司做过一个管理系统,用c#开发的,不过做得很失败)。

语法看了一遍,熟悉得差不多了,当然很多都没有记住,只是大概的知道而已;然后就开始干活了,怎么去调用之前编译好的库呢?查资料有点蛋疼啊...

swift使用c很方便,却不支持c++。
要使用c++就得先建个objective c的文件(CPPTouch.m),修改命名为CPPTouch.mm,(到了这一步只是oc与c++混编),swift还得访问需要通过oc才能访问调用c++到库

本人觉得oc语法比较诡异,而swift语法又那么亲切,所以起初就选择到是swift。但是发现cocoa的库也是oc写的,oc的实例也比较齐全,而swift却比较少,而且调用c++那么失败,个人也比较喜欢cocos2d-x游戏的开发的,而cocos2d-x和c++,还得用oc,那干脆直接用oc好了。so,就这样不用swift了,改用oc,虽然语法是诡异,看多两下就好了;

[个人的理解仍然比较肤浅,真想有大牛能指点迷津!]

二、调用c++的库

objective c 中调用c++只需将oc对.m后缀名改成.mm后缀即可调用c++的库。
.m包含oc和c的特性,而.mm则包涵了则包含oc、c和c++的特性,3者兼容;

混编,代码如下:

- (void)cpp_print {
    std::cout << "This is a test." << std::endl;
    return;
}

三、使用线程

-(void)startThread {
    thread = [[NSThread alloc] initWithTarget:self
            selector:@selector(cpp_print)
            object:nil];
    [thread start];
}

四、画线
新建一个NSView的子类MyView,在xib的View中show the identity inspector > custom class > class中输入MyView即可;

//
//  CustomView3.swift
//
//  实现鼠标拖动画线的功能,当然这个只是前期做的实践
//  因为前期计划是用swift开发,所以代码为swift的代码
//  但是swift与oc是完全兼容的,只是语法上的差异而已

import Cocoa

class CustomView3: NSView {
    var last_point: NSPoint?
    var triangle: NSBezierPath = NSBezierPath()

    private var currentContext : CGContext? {
        get {
            if #available(OSX 10.10, *) {
            return NSGraphicsContext.currentContext()?.CGContext
            } else if let contextPointer = NSGraphicsContext.currentContext()?.graphicsPort {
                let context: CGContextRef = Unmanaged.fromOpaque(COpaquePointer(contextPointer)).takeUnretainedValue()
                return context
            }

            return nil
        }
    }

    private func saveGState(drawStuff: (ctx:CGContextRef) -> ()) -> () {
        if let context = self.currentContext {
            CGContextSaveGState (context)
            drawStuff(ctx: context)
            CGContextRestoreGState (context)
        }
    }

    func myThreadMethod2() {
        while(true) {
            // 划线
        }
    }

    override func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)

        // Drawing code here.
    }

    override func mouseDown(theEvent: NSEvent) {
        var mp: NSPoint = self.convertPoint(theEvent.locationInWindow, fromView: nil)
        self.last_point = mp
    }

    override func mouseUp(theEvent: NSEvent) {
        self.last_point = nil
    }

    override func mouseDragged(theEvent: NSEvent) {
        var mp: NSPoint = self.convertPoint(theEvent.locationInWindow, fromView: nil)
        saveGState { ctx in
            self.draw_line(mp)
            self.last_point = mp
            CGContextFlush(ctx)
        }
    }

    private func draw_line(point: NSPoint) {
        if (last_point != nil) {
            triangle.moveToPoint(last_point!)
            triangle.lineToPoint(point)
            triangle.stroke()
        }
    }
}

五、将各个功能组合起来的过程记录

虽然这个程序很简单,画线的功能解决了,设备的数据也读取到了,但是要将这2个功能组合起来却始终有问题。
起初,是按照这个思路来做的,线程1获取设备的数据后,另一条线程把获取的数据在view中绘制出来。但绘图时却出现了invalid context 0x0的错误;
后来改用1条线程,读取到数据后直接绘制出来,依然存在invalid context 0x0;
这个问题我能想到的原因:
1. 不能在线程绘图;
2. 线程里获取的图像上下文context无效,或者说该线程没有对应的view,所以获取不到context3. 另外还有个图像上下文的哪个API(好像是NSGraphicsContext)不支持osx10.104. 后来不知道哪里看到是:在主线程以外的线程绘图被系统禁止;

想到线程与定时器的差别,换成NSTimer定时器会不会有效果,invalid context 0x0的错误依然出现。
找度娘说有个StackView的东西,允许在线程里绘图,但是写了个StackView的子类后,直接编译错误;在苹果api的文档看到StackView只支持10.9以上点系统,那万一客户的系统版本比较老到时候就得重做了,于是放弃尝试StackView了;

当时就停下来了,想着迷茫,线程又不行,定时器又不行,那搞个p啊,当时老大已经找了我好几遍,我却还卡在这个问题上,又没有朋友是做这方面开发的,心里面很是着急,无奈,无助;

但无奈归无奈,叹息归叹息,问题始终是要解决,静下来查资料。想到不知道回调函数行不行。在线程里面获取数据,然后数据回调到View控制器中,View控制器在主线程,那这样应该可行,于是就去尝试了。
结果这个卡了1天点问题终于解决了,那时的心情真无法用言语来形容;至此需求行的功能已经完成了;
#import 

@protocol TouchDelegate <NSObject>
@required
-(void)successful:(size_t)id setP:(size_t)phase setX:(double)x setY:(double)y setW:(double)width setH:(double)height;
@end
#import 
#import "TouchDelegate.h"

@interface CTouch : NSObject {
    id touchDelegate;
    NSThread* thread;
}
-(void)startThread;
@property (nonatomic,retain) id touchDelegate;
@end
#import "CTouch.h"

@implementation CTouch
@synthesize touchDelegate;

-(void)startThread {
    thread = [[NSThread alloc] initWithTarget:self
            selector:@selector(get_touch)
            object:nil];
    [thread start];
}

- (void)print_event:(xxxxx*)event {
    if (!event) return;

    if (self.touchDelegate!=nil) {
        //完成线程 调用回调函数
        [self.touchDelegate successful:event->id
              setP:event->phase
              setX:event->x
              setY:event->y
              setW:event->width
              setH:event->height];
    }
    return;
}
//......
@end
#import "TouchView.h"

@implementation TouchView

- (id)initWithCoder:(NSCoder *)coder {
    array = [NSMutableArray arrayWithCapacity:100];
    tmp = [NSNumber numberWithFloat:0.0];
    for (int i = 0; i < 100; i++) {
        [array addObject:tmp];
    }

    touch = [[CTouch alloc] init];
    //通知调用协议
    touch.touchDelegate = self;
    [touch startThread];
    // 获取屏幕分辨率
    screenRect = [[NSScreen mainScreen] frame];//]visibleFrame];//frame];
    // NSLog(@"Screen %@", NSStringFromRect(screenRect));
    screen_width = screenRect.size.width;
    screen_height = screenRect.size.height;

    return self;
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    // Drawing code here.

    // 画布(上下文)初始化
    context = [[NSGraphicsContext currentContext] graphicsPort];
    [self drawTest:300 setY:300];
    [self cleanScreen];
    //[self drawTest:200 setY:300];
}

-(void)successful:(size_t)id setP:(size_t)phase setX:(double)x setY:(double)y setW:(double)width setH:(double)height {
    if (phase == 2) {
        [self cleanLastPoint:id];
    } else {
        [self drawLine:id setX:x setY:y];
        [self saveLastPoint:id setX:x setY:y];
    }
}

-(void)drawLine:(size_t)id setX:(double)x setY:(double)y {
    if ([array[id * 2] floatValue] > 0) {
        double lx = [array[id*2] floatValue];
        double ly = [array[id*2+1] floatValue];
        CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
        CGContextMoveToPoint(context, lx * screen_width, (1-ly) * screen_height);
        CGContextAddLineToPoint(context, x * screen_width, (1-y) * screen_height);
        CGContextStrokePath(context);
        CGContextFlush(context);
    }
}

-(void)saveLastPoint:(size_t)id setX:(double)x setY:(double)y {
    NSNumber* myx= [NSNumber numberWithFloat:x];
    NSNumber* myy= [NSNumber numberWithFloat:y];
    if (id < [array count]) {
        array[id * 2] = myx;
        array[id * 2 + 1] = myy;
    }
}

六、清除绘制的线条
这个问题也被卡了好久,就像一个那么小的功能也被卡那么久,严重怀疑自己的能力;期间老大也来找我几次,令我的自信心受到了重创;

七、NSView获取不到键盘事件和鼠标事件
事件其实跟绘图类似,绘图是在view点前提,而事件也一样;
捕捉不到到情况:只有一个mainmenu.xib和一个NSView的子类;加个xxxViewController就好了;大概就是一个事件依赖于一个实体(这里应该就是ViewController),实体不存在,事件也不存在,事件才能由NSResponse 传递到View中,并在view中得到响应;

八、清屏乱刷
这也是自己程序的问题吧,在AppDelegate中定义了一个捕捉事件到循环,在循环中捕捉键盘事件,当读到esc时程序退出;另外非esc按键多响应则说CGContextFlush(context);也是因为这句导致清屏凌乱、甚至无法清屏的bug、按下任意键后无法绘图bug的原因;

在View中也有捕捉键盘事件到响应,ViewController也有,太蛋疼了;删除所有键盘响应的代码,只保留View中的响应,用于清屏(真正的清屏功能是在View里面实现的)

九、xxx-touch库的bug
当所有功能都做好了,发现了一个bug,某个条件内没有数据;觉得库的问题应该是写库的那个人改吧;汇报之后调bug的任务又落在我身上了!没有看过这个库的代码,只是拿来编译使用,觉得调这个bug没有什么信心;单步调试都没有结果,却忽视了另外一条线程对存在;原来这个库也用了2条线程,一个读取设备状态生成事件到队列,另一条线程处理队列中的事件;因为这事我觉得老大对我的印象都变差了,有点伤;

十、打包
1. 打包则有很多种方法,最初用了系统自带的dmg镜像;这个很简单,把app放在一个文件夹,然后使用“磁盘工具”直接做成dmg镜像;
2. PackageManage Packages Iceberg等软件都能使用,但是觉得PackageManage很费劲,不知道什么时候开始xcode默认不自带了,系统也不自带,却集成到auxiliary tools里了,需要自己下载安装,我下了4个,有的文件不齐,有的打开时提示损坏;
3. Iceberg安装则很方便,但是打开软件一直没有反应,用不了,蛋疼;
4. Packages,下载完成后能安装,能运行,但我不会用,看着一页页长长的英文说明文档,就觉得很浮躁,旁边的人说话觉得特别刺耳,,,,,,,,,,最后还是实现了最简单的打包功能了;本机测试通过,待其他机子测试安装包和程序;

至此,这个小小点项目算是完成了;终于放下心头大石了,我庆幸我坚持住了!虽然这个纪录文档(不算技术文档,只是用来记录这个过程,那些错误的,因为过程太蛋疼了,所以需要记录,好记性不如烂笔头,下次遇到这样的问题就不怕了)。

反思:
基本知识不扎实,时间都花在解决问题上面,没有系统的去研究(还有不知道怎么去研究,也没有时间去研究——可能这是借口吧)
要学会学习、学会分析、学会百度 google bing

你可能感兴趣的:(objective-c)