Converting an old (Swift2) NSCollectionView project to Swift5

I returned to an old Cocoa project.

Written in Swift 2… by someone very helpful and knowledgeable (he will recognize himself)

So I found a Mac to load XCode 8 to convert first to Swit3.


I stumble on a few lines


1. Deprecation of DynamicType

Swift2 code was:

In a class declared as:


@objc(AAPLLoopLayout)
class AAPLLoopLayout: AAPLSlideLayout {



    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> NSCollectionViewLayoutAttributes? {
       
        guard let count = self.collectionView?.numberOfItemsInSection(0) where count != 0 else {  return nil  }
       
        let itemIndex = indexPath.item
        let angleInRadians = (CGFloat(itemIndex) / CGFloat(count)) * (2.0 * CGFloat(M_PI))
        var subviewCenter: NSPoint = NSPoint()
        subviewCenter.x = loopCenter.x + loopSize.width * cos(angleInRadians)
        subviewCenter.y = loopCenter.y + loopSize.height * sin(2.0 * angleInRadians)
        let itemFrame = NSMakeRect(subviewCenter.x - 0.5 * itemSize.width, subviewCenter.y - 0.5 * itemSize.height, itemSize.width, itemSize.height)
       
        let attributes = self.dynamicType.layoutAttributesClass().layoutAttributesForItemWithIndexPath(indexPath) as! NSCollectionViewLayoutAttributes
        attributes.frame = NSRectToCGRect(itemFrame)
        attributes.zIndex = indexPath.item
        return attributes
    }

Line 12 does not compile, converter proposes


let attributes = type(of: self).layoutAttributesClass().layoutAttributesForItemWithIndexPath(indexPath) as! NSCollectionViewLayoutAttributes


But gives an error:

value of type AnyClass has no member layoutAttributesForItemWithIndexPath

I've tried a lot of variations, no success, because I search uphazardly in fact…


2. Use of unsafePointer


In a class defined as:


@objc(AAPLFileTreeWatcherThread)
class AAPLFileTreeWatcherThread: NSThread {


    override func main() {
        autoreleasepool {
        
            // Create our fsEventStream.
            var context: FSEventStreamContext = FSEventStreamContext()
            context.version = 0
            context.info = UnsafeMutablePointer(unsafeAddressOf(self).toOpaque())     // toOpaque added by converter
            context.retain = nil
            context.release = nil
            context.copyDescription = nil
            fsEventStream = FSEventStreamCreate(kCFAllocatorDefault, AAPLFileTreeWatcherEventStreamCallback, &context, paths, FSEventStreamEventId(kFSEventStreamEventIdSinceNow), 1.0, FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot | kFSEventStreamCreateFlagIgnoreSelf))
            if fsEventStream != nil {
            
                // Schedule the fsEventStream on our thread's run loop.
                let runLoop = NSRunLoop.currentRunLoop()
                let cfRunLoop = runLoop.getCFRunLoop()
                FSEventStreamScheduleWithRunLoop(fsEventStream, cfRunLoop, CFRunLoop.Mode.commonModes)
            
                // Open the faucet.
                FSEventStreamStart(fsEventStream)
            
                // Run until we're asked to stop.
                while !self.cancelled {
                    runLoop.runUntilDate(NSDate(timeIntervalSinceNow: 0.25))
                }
            
                // Shut off the faucet.
                FSEventStreamStop(fsEventStream)
            
                // Unschedule the fsEventStream on our thread's run loop.
                FSEventStreamUnscheduleFromRunLoop(fsEventStream, cfRunLoop, CFRunLoop.Mode.commonModes)
            
                // Invalidate and release fsEventStream.
                FSEventStreamInvalidate(fsEventStream)
                FSEventStreamRelease(fsEventStream)
                fsEventStream = nil
            }
        }
    }

I get error on line 7: Cannot invoke initializer for type 'UnsafeMutablePointer<_> with an argument list of type (UnsafeMutableRawPointer)

Changing to

context.info = UnsafeMutableRawPointer(unsafeAddressOf(self)).toOpaque())


it compiles but it crashes at runtime.


and lines 17 and 31:

Cannot convert value of type CFRunLoopModel to expected argument of type CFString.

Changing to

                FSEventStreamScheduleWithRunLoop(fsEventStream, cfRunLoop, CFRunLoop.Mode.commonModes as! CFString)

cleared the error. Is that correct ?


3. NSCopying


In a class

@objc(AAPLImageFile)
class AAPLImageFile: NSObject {


I have var declarations:

    @NSCopying var url: NSURL
    @objc dynamic var fileType: String?
    var fileSize: UInt64 = 0
    @NSCopying var dateLastUpdated: NSDate?


Which give error line 2 and 4:

@NSCopying is only valid with types that conform to NSCopying protocol.


Do I still need to declare @NSCopying ? Error disappears if I remove it, but may that cause other problems ?


Thanks for any help.

Accepted Reply

I got this to make your code compile succesfully in Xcode 11.1.

    override func main() {
        autoreleasepool {
          
            // Create our fsEventStream.
            var context: FSEventStreamContext = FSEventStreamContext()
            context.version = 0
            context.info = Unmanaged.passUnretained(self).toOpaque() //###
            context.retain = nil
            context.release = nil
            context.copyDescription = nil
            fsEventStream = FSEventStreamCreate(
                kCFAllocatorDefault,
                AAPLFileTreeWatcherEventStreamCallback, //<-(1)
                &context,
                paths as CFArray,
                FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
                1.0,
                FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes
                    | kFSEventStreamCreateFlagWatchRoot
                    | kFSEventStreamCreateFlagIgnoreSelf))
            if fsEventStream != nil {
              
                // Schedule the fsEventStream on our thread's run loop.
                let runLoop = RunLoop.current
                let cfRunLoop = runLoop.getCFRunLoop()
                FSEventStreamScheduleWithRunLoop(
                    fsEventStream!,
                    cfRunLoop,
                    CFRunLoopMode.commonModes.rawValue) //<-(2)
              
                // Open the faucet.
                FSEventStreamStart(fsEventStream!)
              
                // Run until we're asked to stop.
                while !self.isCancelled {
                    runLoop.run(until: Date(timeIntervalSinceNow: 0.25))
                }
              
                // Shut off the faucet.
                FSEventStreamStop(fsEventStream!)
              
                // Unschedule the fsEventStream on our thread's run loop.
                FSEventStreamUnscheduleFromRunLoop(
                    fsEventStream!,
                    cfRunLoop,
                    CFRunLoopMode.commonModes.rawValue) //<-(2)
              
                // Invalidate and release fsEventStream.
                FSEventStreamInvalidate(fsEventStream!)
                FSEventStreamRelease(fsEventStream!)
                fsEventStream = nil
            }
        }
    }


(1) In almost all cases, `as!` conversion of function type fails and crashes.


You need to modify the definition of `AAPLFileTreeWatcherEventStreamCallback` to match the type of the 2nd arg of `FSEventStreamCreate`,

do not cast.


(2) Again, `as!` casting always fails and crashes in this context.


`CFRunLoopMode` is a thin wrapper of `CFString`, but it cannot be converted to `CFString` by as-casting.

Use the `rawValue` property when you want to get the underlying `CFString`.

Replies

While this isn't an answer to generalize issues around dynamictype, I think you want something like this in your first set of code:


let theAttributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }

This will give you a copy of the attributes that you can then modify as needed (also avoids the cached-frame mismatch warning). As the code at hand is all class-based, I'll assume that super in this context will be what you want. I can't wrap my head around what exactly self.dynamictype.layoutAttributesForElements was doing before.

Also, I belive that in your NSCopying case, that you need to use a class since you want to represent specific identities of files and need reference semantics? If not, and this object is just more of say metadata about a file that can be copied, note that foundation has types URL, UInt64, and Date which are all value types. Thus, you choose to use a struct to wrap those value types in and then deal with value-semantics.

what exactly self.dynamictype.layoutAttributesForElements was doing before.

It was getting the existing attributes for layout at indexPath before changing its frame and zIndex.


So, it was getting the layout attributes of a single element of the NSCollectionView.

I do not have evironments to run Xcode 8, so just tested your code in Swift 5.1/Xcode 11.1.

(Xcode 11.2.x does not seem to be stable enough for production use...)


For #1, I got this:

        let attributes =  (type(of: self).layoutAttributesClass as! NSCollectionViewLayoutAttributes.Type)
            .init(forItemWith: indexPath)


`....dynamicType` has changed to `type(of: ...)`, but you need some more.


- `layoutAttributesClass` has changed from method to property

- To call class methods or initializers of some specific type from `AnyClass`, you need to cast to the metaclass.

- `layoutAttributesForItemWithIndexPath` has changed from class method to initializer


#3

I recommed you to change the properties to Swift Types.

    var url: URL

    var dateLastUpdated: Date?

Those are value types, so you have no need to add @NSCopying.


Sorry, but I do not have enough time to write something about #2 now...

By 'what exactly it was doing before', I meant what exact implementation of layoutAttributesForElements(in:) was it calling? Since AAPLLoopLayout is a subclass of AAPLSlideLayout, the new code I suggested (call to super) would either find any implementation in AAPLSlideLayout or earlier as you traverse up the inheritence chain.


Given the same exact class hierarchy then, would calling 'super.layoutAttributesForElements' just achieve the same exact thing as 'self.dynamictype.layoutAttributesForElements'? That's what I'm not sure of.

Thanks for those answers.

I'll continue to search for #2, in case… For sure, more help will be welcome.


Following:

h ttps://github.com/RxSwiftCommunity/RxFileMonitor/blob/master/RxFileMonitor/FolderContentMonitor.swift

I changed

context.info = UnsafeMutablePointer(unsafeAddressOf(self)) // Swift 2

            context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()) // Swift 3 converter proposal

into

            context.info = Unmanaged.passUnretained(self).toOpaque()


But crash in

fsEventStream = FSEventStreamCreate(…)

with crashlog:

AAPLFileTreeWatcherThread (9) EXC_BAD_INSTRUCTION


Here is the context code:

    override func main() {
        autoreleasepool {
           
            // Create our fsEventStream.
            var context: FSEventStreamContext = FSEventStreamContext()
            context.version = 0
            context.info = Unmanaged.passUnretained(self).toOpaque()
            context.retain = nil
            context.release = nil
            context.copyDescription = nil
            fsEventStream = FSEventStreamCreate(kCFAllocatorDefault, AAPLFileTreeWatcherEventStreamCallback as! FSEventStreamCallback, &context, paths as CFArray, FSEventStreamEventId(kFSEventStreamEventIdSinceNow), 1.0, FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot | kFSEventStreamCreateFlagIgnoreSelf))
            if fsEventStream != nil {
               
                // Schedule the fsEventStream on our thread's run loop.
                let runLoop = RunLoop.current
                let cfRunLoop = runLoop.getCFRunLoop()
                FSEventStreamScheduleWithRunLoop(fsEventStream!, cfRunLoop, CFRunLoopMode.commonModes as! CFString)
               
                // Open the faucet.
                FSEventStreamStart(fsEventStream!)
               
                // Run until we're asked to stop.
                while !self.isCancelled {
                    runLoop.run(until: Date(timeIntervalSinceNow: 0.25))
                }
               
                // Shut off the faucet.
                FSEventStreamStop(fsEventStream!)
               
                // Unschedule the fsEventStream on our thread's run loop.
                FSEventStreamUnscheduleFromRunLoop(fsEventStream!, cfRunLoop, CFRunLoopMode.commonModes as! CFString)

               
                // Invalidate and release fsEventStream.
                FSEventStreamInvalidate(fsEventStream!)
                FSEventStreamRelease(fsEventStream!)
                fsEventStream = nil
            }
        }
    }

On the FSEvents front, check out this thread.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I got this to make your code compile succesfully in Xcode 11.1.

    override func main() {
        autoreleasepool {
          
            // Create our fsEventStream.
            var context: FSEventStreamContext = FSEventStreamContext()
            context.version = 0
            context.info = Unmanaged.passUnretained(self).toOpaque() //###
            context.retain = nil
            context.release = nil
            context.copyDescription = nil
            fsEventStream = FSEventStreamCreate(
                kCFAllocatorDefault,
                AAPLFileTreeWatcherEventStreamCallback, //<-(1)
                &context,
                paths as CFArray,
                FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
                1.0,
                FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes
                    | kFSEventStreamCreateFlagWatchRoot
                    | kFSEventStreamCreateFlagIgnoreSelf))
            if fsEventStream != nil {
              
                // Schedule the fsEventStream on our thread's run loop.
                let runLoop = RunLoop.current
                let cfRunLoop = runLoop.getCFRunLoop()
                FSEventStreamScheduleWithRunLoop(
                    fsEventStream!,
                    cfRunLoop,
                    CFRunLoopMode.commonModes.rawValue) //<-(2)
              
                // Open the faucet.
                FSEventStreamStart(fsEventStream!)
              
                // Run until we're asked to stop.
                while !self.isCancelled {
                    runLoop.run(until: Date(timeIntervalSinceNow: 0.25))
                }
              
                // Shut off the faucet.
                FSEventStreamStop(fsEventStream!)
              
                // Unschedule the fsEventStream on our thread's run loop.
                FSEventStreamUnscheduleFromRunLoop(
                    fsEventStream!,
                    cfRunLoop,
                    CFRunLoopMode.commonModes.rawValue) //<-(2)
              
                // Invalidate and release fsEventStream.
                FSEventStreamInvalidate(fsEventStream!)
                FSEventStreamRelease(fsEventStream!)
                fsEventStream = nil
            }
        }
    }


(1) In almost all cases, `as!` conversion of function type fails and crashes.


You need to modify the definition of `AAPLFileTreeWatcherEventStreamCallback` to match the type of the 2nd arg of `FSEventStreamCreate`,

do not cast.


(2) Again, `as!` casting always fails and crashes in this context.


`CFRunLoopMode` is a thin wrapper of `CFString`, but it cannot be converted to `CFString` by as-casting.

Use the `rawValue` property when you want to get the underlying `CFString`.

Thanks a lot. Converter could not make it.


It works in Swift 3 on XCode 8. I will now upgrade to Swift 4…