Absolutely perfect 2019 solution

Without further ado, here's exactly how you do this.
- Don't actually use the "basic" layer that comes with the view
 
- Make a new layer only for the image. You can now mask this (circularly) without affecting the next layer
 
- Make a new layer for the border as such. It will safely not be masked by the picture layer.
 
The key facts are
- With a CALayer, you can indeed apply a .mask and it only affects that layer
 
- When drawing a circle (or indeed any border), have to attend very carefully to the fact that you only get "half the width" - in short never crop using the same path you draw with.
 
- Notice the original cat image is exactly as wide as the horizontal yellow arrow. You have to be careful to paint the image so that the whole image appears in the roundel, which is smaller than the overall custom control.
 
So, setup in the usual way
import UIKit
@IBDesignable class GreenCirclePerson: UIView {
    
    @IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
    @IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
    @IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }
    
    @IBInspectable var picture: UIImage? = nil { didSet { setup() } }
    
    override func layoutSubviews() { setup() }
    
    var imageLayer: CALayer? = nil
    var border: CAShapeLayer? = nil
    
    func setup() {
        
        if (imageLayer == nil) {
            imageLayer = CALayer()
            self.layer.addSublayer(imageLayer!)
        }
        if (border == nil) {
            border = CAShapeLayer()
            self.layer.addSublayer(border!)
        }
        
Now carefully make the layer for the circularly-cropped image:
        // the ultimate size of our custom control:
        let box = self.bounds.aspectFit()
        
        let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness
        
        let boxInWhichImageSits = box.inset(by:
           UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
           bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))
        
        // just a note. that version of inset#by is much clearer than the
        // confusing dx/dy variant, so best to use that one
        
        imageLayer!.frame = boxInWhichImageSits
        imageLayer!.contents = picture?.cgImage
        imageLayer?.contentsGravity = .resizeAspectFill
        
        let halfImageSize = boxInWhichImageSits.width / 2.0
        
        let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
           cornerRadius:halfImageSize)
        let maskLayer = CAShapeLayer()
        maskLayer.path = maskPath.cgPath
        imageLayer!.mask = maskLayer
        
Next as a completely separate layer, draw the border as you wish:
        // now create the border
        
        border!.frame = bounds
        
        // To draw the border, you must inset it by half the width of the border,
        // otherwise you'll be drawing only half the border. (Indeed, as an additional
        // subtle problem you are clipping rather than rendering the outside edge.)
        
        let halfWidth = trueBorderThickness / 2.0
        let borderCenterlineBox = box.inset(by:
            UIEdgeInsets(top: halfWidth, left: halfWidth,
            bottom: halfWidth, right: halfWidth))
        
        let halfBorderBoxSize = borderCenterlineBox.width / 2.0
        
        let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
          cornerRadius:halfBorderBoxSize)
        
        border!.path = borderPath.cgPath
        border!.fillColor = UIColor.clear.cgColor
        
        border!.strokeColor = borderColor.cgColor
        border!.lineWidth = trueBorderThickness
    }
}
Everything works perfectly as in iOS standard controls:

Everything which is invisible is invisible; you can see-through the overall custom control to any material behind, there are no "half thickness" problems or missing image material, you can set the custom control background color in the usual way, etc etc. The inspector controls all work properly. (Phew!)
Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers