Give this a try. It remaps `@` so that `g@` (plus a dummy motion `l`) is used afterwards, thus becoming the last operator and subject to repeating with `.`.

    " When . repeats g@, repeat the last macro.
    fun! AtAt(_)
        " If no count is supplied use the one saved in s:atcount.
        " Otherwise save the new count in s:atcount, so it will be
        " applied to repeats.
        let s:atcount = v:count ? v:count : s:atcount
        " feedkeys() rather than :normal allows finishing in Insert
        " mode, should the macro do that. @@ is remapped, so opfunc will
        " be correct, even if the macro changes it.
        call feedkeys(s:atcount.'@@')
    endfun

    fun! AtNext(_)
        set opfunc=AtAt
    endfun

    " Called by g@ being invoked directly for the first time. Sets
    " 'opfunc' ready for repeats with . by calling AtNext().
    fun! AtInit()
        " Make sure setting 'opfunc' happens here, after initial playback
        " of the macro recording, in case 'opfunc' is set there.
        set opfunc=AtNext
        return 'g@l'
    endfun

    " Enable calling a function within the mapping for @
    nno <expr> <plug>@init AtInit()
    " A macro could, albeit unusually, end in Insert mode.
    ino <expr> <plug>@init "\<c-o>".AtInit()

    fun! AtReg()
        let s:atcount = v:count1
        let c = nr2char(getchar())
        return '@'.c."\<plug>@init"
    endfun

    nmap <expr> @ AtReg()

I've tried to handle as many corner cases as I can think of. You can
repeat `@:` with `.`. Counts to `@` or `.` are retained for subsequent
presses of `.`.

This is tricky, and I'm not convinced that something won't break
somewhere along the way. So no guarantees, warranties, or promises with
this one.

Personally, I'm okay having a difference between the fine-grained
repeats of `.` for the last change, and the macro repeats of `@@`.

**EDIT**

I figured, having gone this far, that I may as well add some additional code that will allow pressing `.` immediately
after recording a macro to play it back.

    fun! Qdot(_)
        call feedkeys('@'.s:qreg)
    endfun

    fun! Qnext(_)
        set opfunc=Qdot
    endfun

    fun! Qstop()
        set opfunc=Qnext
        return 'g@l'
    endfun

    nno <expr> <plug>qstop Qstop()
    ino <expr> <plug>qstop "\<c-o>".Qstop()

    let s:qrec = 0
    fun! Qstart()
        if s:qrec == 1
            let s:qrec = 0
            set opfunc=Qstop
            return "q\<plug>qstop"
        endif
        let s:qrec = 1
        let s:qreg = nr2char(getchar())
        return 'q'.s:qreg
    endfun

    nmap <expr> q Qstart()